feat: upgrade wallet support
This commit is contained in:
parent
6951383045
commit
b41e8a919a
@ -91,7 +91,7 @@
|
||||
"@typescript-eslint/eslint-plugin": "^6.1.0",
|
||||
"@typescript-eslint/parser": "^6.1.0",
|
||||
"@vitejs/plugin-react": "^4.2.0",
|
||||
"@webbtc/webln-types": "^1.0.10",
|
||||
"@webbtc/webln-types": "^2.1.0",
|
||||
"@webscopeio/react-textarea-autocomplete": "^4.9.2",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"config": "^3.3.9",
|
||||
|
@ -1,23 +1,15 @@
|
||||
import "./WalletPage.css";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { RouteObject, useNavigate } from "react-router-dom";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { FormattedMessage, FormattedNumber, useIntl } from "react-intl";
|
||||
|
||||
import NoteTime from "@/Element/Event/NoteTime";
|
||||
import { WalletInvoice, Sats, WalletInfo, WalletInvoiceState, useWallet, LNWallet, Wallets } from "@/Wallet";
|
||||
import AsyncButton from "@/Element/Button/AsyncButton";
|
||||
import { unwrap } from "@/SnortUtils";
|
||||
import { WebLNWallet } from "@/Wallet/WebLN";
|
||||
import Icon from "@/Icons/Icon";
|
||||
|
||||
export const WalletRoutes: RouteObject[] = [
|
||||
{
|
||||
path: "/wallet",
|
||||
element: <WalletPage />,
|
||||
},
|
||||
];
|
||||
|
||||
export default function WalletPage() {
|
||||
const navigate = useNavigate();
|
||||
const { formatMessage } = useIntl();
|
||||
@ -33,10 +25,14 @@ export default function WalletPage() {
|
||||
try {
|
||||
const i = await wallet.getInfo();
|
||||
setInfo(i);
|
||||
const b = await wallet.getBalance();
|
||||
setBalance(b as Sats);
|
||||
const h = await wallet.getInvoices();
|
||||
setHistory((h as WalletInvoice[]).sort((a, b) => b.timestamp - a.timestamp));
|
||||
if (wallet.canGetBalance()) {
|
||||
const b = await wallet.getBalance();
|
||||
setBalance(b as Sats);
|
||||
}
|
||||
if (wallet.canGetInvoices()) {
|
||||
const h = await wallet.getInvoices();
|
||||
setHistory((h as WalletInvoice[]).sort((a, b) => b.timestamp - a.timestamp));
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
setError((e as Error).message);
|
||||
@ -62,11 +58,11 @@ export default function WalletPage() {
|
||||
function stateIcon(s: WalletInvoiceState) {
|
||||
switch (s) {
|
||||
case WalletInvoiceState.Pending:
|
||||
return <Icon name="clock" className="mr5" size={15} />;
|
||||
return <Icon name="clock" size={15} />;
|
||||
case WalletInvoiceState.Paid:
|
||||
return <Icon name="check" className="mr5" size={15} />;
|
||||
return <Icon name="check" size={15} />;
|
||||
case WalletInvoiceState.Expired:
|
||||
return <Icon name="close" className="mr5" size={15} />;
|
||||
return <Icon name="close" size={15} />;
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,7 +128,7 @@ export default function WalletPage() {
|
||||
}
|
||||
|
||||
function walletHistory() {
|
||||
if (wallet instanceof WebLNWallet) return null;
|
||||
if (!wallet?.canGetInvoices()) return;
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -141,12 +137,12 @@ export default function WalletPage() {
|
||||
</h3>
|
||||
{history?.map(a => (
|
||||
<div className="card flex wallet-history-item" key={a.timestamp}>
|
||||
<div className="grow flex-col">
|
||||
<div className="grow">
|
||||
<NoteTime from={a.timestamp * 1000} fallback={formatMessage({ defaultMessage: "now", id: "kaaf1E" })} />
|
||||
<div>{(a.memo ?? "").length === 0 ? <> </> : a.memo}</div>
|
||||
</div>
|
||||
<div
|
||||
className={`nowrap ${(() => {
|
||||
className={`flex gap-2 items-center ${(() => {
|
||||
switch (a.state) {
|
||||
case WalletInvoiceState.Paid:
|
||||
return "success";
|
||||
@ -158,14 +154,16 @@ export default function WalletPage() {
|
||||
return "pending";
|
||||
}
|
||||
})()}`}>
|
||||
{stateIcon(a.state)}
|
||||
<FormattedMessage
|
||||
defaultMessage="{amount} sats"
|
||||
id="vrTOHJ"
|
||||
values={{
|
||||
amount: <FormattedNumber value={a.amount / 1e3} />,
|
||||
}}
|
||||
/>
|
||||
<div>{stateIcon(a.state)}</div>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
defaultMessage="{amount} sats"
|
||||
id="vrTOHJ"
|
||||
values={{
|
||||
amount: <FormattedNumber value={a.amount / 1e3} />,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@ -174,7 +172,7 @@ export default function WalletPage() {
|
||||
}
|
||||
|
||||
function walletBalance() {
|
||||
if (wallet instanceof WebLNWallet) return null;
|
||||
if (!wallet?.canGetBalance()) return;
|
||||
return (
|
||||
<small>
|
||||
<FormattedMessage
|
||||
@ -189,18 +187,13 @@ export default function WalletPage() {
|
||||
}
|
||||
|
||||
function walletInfo() {
|
||||
if (!wallet?.isReady()) return null;
|
||||
if (!wallet?.isReady()) return;
|
||||
return (
|
||||
<>
|
||||
<div className="card">
|
||||
<h3>{info?.alias}</h3>
|
||||
<div className="p br b">
|
||||
<div>{info?.alias}</div>
|
||||
{walletBalance()}
|
||||
</div>
|
||||
{/*<div className="flex wallet-buttons">
|
||||
<AsyncButton onClick={createInvoice}>
|
||||
<FormattedMessage defaultMessage="Receive" description="Receive sats by generating LN invoice" />
|
||||
</AsyncButton>
|
||||
</div>*/}
|
||||
{walletHistory()}
|
||||
</>
|
||||
);
|
||||
@ -208,8 +201,8 @@ export default function WalletPage() {
|
||||
|
||||
return (
|
||||
<div className="main-content p">
|
||||
{error && <b className="error">{error}</b>}
|
||||
{walletList()}
|
||||
{error && <b className="warning">{error}</b>}
|
||||
{unlockWallet()}
|
||||
{walletInfo()}
|
||||
</div>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import "./WalletSettings.css";
|
||||
import LndLogo from "@/lnd-logo.png";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { Link, RouteObject, useNavigate } from "react-router-dom";
|
||||
import { RouteObject, useNavigate } from "react-router-dom";
|
||||
|
||||
import BlueWallet from "@/Icons/BlueWallet";
|
||||
import ConnectLNC from "@/Pages/settings/wallet/LNC";
|
||||
@ -10,16 +10,13 @@ import ConnectNostrWallet from "@/Pages/settings/wallet/NWC";
|
||||
import ConnectCashu from "@/Pages/settings/wallet/Cashu";
|
||||
|
||||
import NostrIcon from "@/Icons/Nostrich";
|
||||
import WalletPage from "../WalletPage";
|
||||
|
||||
const WalletSettings = () => {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<>
|
||||
<Link to="/wallet">
|
||||
<button type="button">
|
||||
<FormattedMessage defaultMessage="View Wallets" id="VvaJst" />
|
||||
</button>
|
||||
</Link>
|
||||
<WalletPage />
|
||||
<h3>
|
||||
<FormattedMessage defaultMessage="Connect Wallet" id="cg1VJ2" />
|
||||
</h3>
|
||||
|
@ -32,10 +32,18 @@ export class LNCWallet implements LNWallet {
|
||||
});
|
||||
}
|
||||
|
||||
canAutoLogin(): boolean {
|
||||
canAutoLogin() {
|
||||
return false;
|
||||
}
|
||||
|
||||
canGetInvoices() {
|
||||
return true;
|
||||
}
|
||||
|
||||
canGetBalance() {
|
||||
return true;
|
||||
}
|
||||
|
||||
isReady(): boolean {
|
||||
return this.#lnc.isReady;
|
||||
}
|
||||
|
@ -43,7 +43,15 @@ export default class LNDHubWallet implements LNWallet {
|
||||
return this.auth !== undefined;
|
||||
}
|
||||
|
||||
canAutoLogin(): boolean {
|
||||
canAutoLogin() {
|
||||
return true;
|
||||
}
|
||||
|
||||
canGetInvoices() {
|
||||
return true;
|
||||
}
|
||||
|
||||
canGetBalance() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -106,18 +114,21 @@ export default class LNDHubWallet implements LNWallet {
|
||||
|
||||
async getInvoices(): Promise<WalletInvoice[]> {
|
||||
const rsp = await this.getJson<UserInvoicesResponse[]>("GET", "/getuserinvoices");
|
||||
return (rsp as UserInvoicesResponse[]).map(a => {
|
||||
const decodedInvoice = prToWalletInvoice(a.payment_request);
|
||||
if (!decodedInvoice) {
|
||||
throw new WalletError(WalletErrorCode.InvalidInvoice, "Failed to parse invoice");
|
||||
}
|
||||
return {
|
||||
...decodedInvoice,
|
||||
state: a.ispaid ? WalletInvoiceState.Paid : decodedInvoice.state,
|
||||
paymentHash: a.payment_hash,
|
||||
memo: a.description,
|
||||
} as WalletInvoice;
|
||||
});
|
||||
return (rsp as UserInvoicesResponse[])
|
||||
.sort((a, b) => (a.timestamp > b.timestamp ? -1 : 1))
|
||||
.slice(0, 50)
|
||||
.map(a => {
|
||||
const decodedInvoice = prToWalletInvoice(a.payment_request);
|
||||
if (!decodedInvoice) {
|
||||
throw new WalletError(WalletErrorCode.InvalidInvoice, "Failed to parse invoice");
|
||||
}
|
||||
return {
|
||||
...decodedInvoice,
|
||||
state: a.ispaid ? WalletInvoiceState.Paid : decodedInvoice.state,
|
||||
paymentHash: a.payment_hash,
|
||||
memo: a.description,
|
||||
} as WalletInvoice;
|
||||
});
|
||||
}
|
||||
|
||||
private async getJson<T>(method: "GET" | "POST", path: string, body?: unknown): Promise<T> {
|
||||
|
@ -1,6 +1,15 @@
|
||||
import { Connection, EventKind, NostrEvent, EventBuilder, PrivateKeySigner } from "@snort/system";
|
||||
import { LNWallet, WalletError, WalletErrorCode, WalletInfo, WalletInvoice, WalletInvoiceState } from "@/Wallet";
|
||||
import {
|
||||
InvoiceRequest,
|
||||
LNWallet,
|
||||
WalletError,
|
||||
WalletErrorCode,
|
||||
WalletInfo,
|
||||
WalletInvoice,
|
||||
WalletInvoiceState,
|
||||
} from "@/Wallet";
|
||||
import debug from "debug";
|
||||
import { dedupe } from "@snort/shared";
|
||||
|
||||
interface WalletConnectConfig {
|
||||
relayUrl: string;
|
||||
@ -30,10 +39,45 @@ interface WalletConnectResponse<T> {
|
||||
};
|
||||
}
|
||||
|
||||
interface GetInfoResponse {
|
||||
alias?: string;
|
||||
color?: string;
|
||||
pubkey?: string;
|
||||
network?: string;
|
||||
block_height?: number;
|
||||
block_hash?: string;
|
||||
methods?: Array<string>;
|
||||
}
|
||||
|
||||
interface ListTransactionsResponse {
|
||||
transactions: Array<{
|
||||
type: "incoming" | "outgoing";
|
||||
invoice: string;
|
||||
description?: string;
|
||||
description_hash?: string;
|
||||
preimage?: string;
|
||||
payment_hash?: string;
|
||||
amount: number;
|
||||
feed_paid: number;
|
||||
settled_at?: number;
|
||||
metadata?: object;
|
||||
}>;
|
||||
}
|
||||
|
||||
interface MakeInvoiceResponse {
|
||||
invoice: string;
|
||||
payment_hash: string;
|
||||
}
|
||||
|
||||
const DefaultSupported = ["get_info", "pay_invoice"];
|
||||
|
||||
export class NostrConnectWallet implements LNWallet {
|
||||
#log = debug("NWC");
|
||||
#config: WalletConnectConfig;
|
||||
#conn?: Connection;
|
||||
#commandQueue: Map<string, QueueObj>;
|
||||
#info?: WalletInfo;
|
||||
#supported_methods: Array<string> = DefaultSupported;
|
||||
|
||||
constructor(cfg: string) {
|
||||
this.#config = NostrConnectWallet.parseConfigUrl(cfg);
|
||||
@ -59,20 +103,43 @@ export class NostrConnectWallet implements LNWallet {
|
||||
|
||||
async getInfo() {
|
||||
await this.login();
|
||||
return await new Promise<WalletInfo>((resolve, reject) => {
|
||||
this.#commandQueue.set("info", {
|
||||
resolve: (o: string) => {
|
||||
resolve({
|
||||
alias: "NWC",
|
||||
chains: o.split(" "),
|
||||
} as WalletInfo);
|
||||
},
|
||||
reject,
|
||||
if (this.#info) return this.#info;
|
||||
|
||||
const rsp = await this.#rpc<WalletConnectResponse<GetInfoResponse>>("get_info", {});
|
||||
if (!rsp.error) {
|
||||
this.#supported_methods = dedupe(["get_info", ...(rsp.result?.methods ?? DefaultSupported)]);
|
||||
this.#log("Supported methods: %o", this.#supported_methods);
|
||||
const info = {
|
||||
nodePubKey: rsp.result?.pubkey,
|
||||
alias: rsp.result?.alias,
|
||||
blockHeight: rsp.result?.block_height,
|
||||
blockHash: rsp.result?.block_hash,
|
||||
chains: rsp.result?.network ? [rsp.result.network] : undefined,
|
||||
} as WalletInfo;
|
||||
this.#info = info;
|
||||
return info;
|
||||
} else if (rsp.error.code === "NOT_IMPLEMENTED") {
|
||||
// legacy get_info uses event kind 13_194
|
||||
return await new Promise<WalletInfo>((resolve, reject) => {
|
||||
this.#commandQueue.set("info", {
|
||||
resolve: (o: string) => {
|
||||
this.#supported_methods = dedupe(["get_info", ...o.split(",")]);
|
||||
this.#log("Supported methods: %o", this.#supported_methods);
|
||||
const info = {
|
||||
alias: "NWC",
|
||||
} as WalletInfo;
|
||||
this.#info = info;
|
||||
resolve(info);
|
||||
},
|
||||
reject,
|
||||
});
|
||||
this.#conn?.QueueReq(["REQ", "info", { kinds: [13194], limit: 1 }], () => {
|
||||
// ignored
|
||||
});
|
||||
});
|
||||
this.#conn?.QueueReq(["REQ", "info", { kinds: [13194], limit: 1 }], () => {
|
||||
// ignored
|
||||
});
|
||||
});
|
||||
} else {
|
||||
throw new WalletError(WalletErrorCode.GeneralError, rsp.error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async login() {
|
||||
@ -100,11 +167,33 @@ export class NostrConnectWallet implements LNWallet {
|
||||
}
|
||||
|
||||
async getBalance() {
|
||||
return 0;
|
||||
await this.login();
|
||||
const rsp = await this.#rpc<WalletConnectResponse<{ balance: number }>>("get_balance", {});
|
||||
if (!rsp.error) {
|
||||
return (rsp.result?.balance ?? 0) / 1000;
|
||||
} else {
|
||||
throw new WalletError(WalletErrorCode.GeneralError, rsp.error.message);
|
||||
}
|
||||
}
|
||||
|
||||
createInvoice() {
|
||||
return Promise.reject(new WalletError(WalletErrorCode.GeneralError, "Not implemented"));
|
||||
async createInvoice(req: InvoiceRequest) {
|
||||
await this.login();
|
||||
const rsp = await this.#rpc<WalletConnectResponse<MakeInvoiceResponse>>("make_invoice", {
|
||||
amount: req.amount * 1000,
|
||||
description: req.memo,
|
||||
expiry: req.expiry,
|
||||
});
|
||||
if (!rsp.error) {
|
||||
return {
|
||||
pr: rsp.result?.invoice,
|
||||
paymentHash: rsp.result?.payment_hash,
|
||||
memo: req.memo,
|
||||
amount: req.amount * 1000,
|
||||
state: WalletInvoiceState.Pending,
|
||||
} as WalletInvoice;
|
||||
} else {
|
||||
throw new WalletError(WalletErrorCode.GeneralError, rsp.error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async payInvoice(pr: string) {
|
||||
@ -123,8 +212,38 @@ export class NostrConnectWallet implements LNWallet {
|
||||
}
|
||||
}
|
||||
|
||||
getInvoices() {
|
||||
return Promise.resolve([]);
|
||||
async getInvoices() {
|
||||
await this.login();
|
||||
const rsp = await this.#rpc<WalletConnectResponse<ListTransactionsResponse>>("list_transactions", {
|
||||
limit: 50,
|
||||
});
|
||||
if (!rsp.error) {
|
||||
return (
|
||||
rsp.result?.transactions.map(
|
||||
a =>
|
||||
({
|
||||
pr: a.invoice,
|
||||
paymentHash: a.payment_hash,
|
||||
memo: a.description,
|
||||
amount: a.amount,
|
||||
fees: a.feed_paid,
|
||||
timestamp: a.settled_at,
|
||||
preimage: a.preimage,
|
||||
state: WalletInvoiceState.Paid,
|
||||
}) as WalletInvoice,
|
||||
) ?? []
|
||||
);
|
||||
} else {
|
||||
throw new WalletError(WalletErrorCode.GeneralError, rsp.error.message);
|
||||
}
|
||||
}
|
||||
|
||||
canGetInvoices() {
|
||||
return this.#supported_methods.includes("list_transactions");
|
||||
}
|
||||
|
||||
canGetBalance() {
|
||||
return this.#supported_methods.includes("get_balance");
|
||||
}
|
||||
|
||||
async #onReply(sub: string, e: NostrEvent) {
|
||||
@ -157,8 +276,19 @@ export class NostrConnectWallet implements LNWallet {
|
||||
this.#conn?.CloseReq(sub);
|
||||
}
|
||||
|
||||
async #rpc<T>(method: string, params: Record<string, string>) {
|
||||
async #rpc<T>(method: string, params: Record<string, string | number | undefined>) {
|
||||
if (!this.#conn) throw new WalletError(WalletErrorCode.GeneralError, "Not implemented");
|
||||
this.#log("> %o", { method, params });
|
||||
if (!this.#supported_methods.includes(method)) {
|
||||
const ret = {
|
||||
error: {
|
||||
code: "NOT_IMPLEMENTED",
|
||||
message: `get_info claims the method "${method}" is not supported`,
|
||||
},
|
||||
} as T;
|
||||
this.#log("< %o", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
const payload = JSON.stringify({
|
||||
method,
|
||||
@ -190,7 +320,7 @@ export class NostrConnectWallet implements LNWallet {
|
||||
this.#commandQueue.set(evCommand.id, {
|
||||
resolve: async (o: string) => {
|
||||
const reply = JSON.parse(await signer.nip4Decrypt(o, this.#config.walletPubkey));
|
||||
debug("NWC")("%o", reply);
|
||||
this.#log("< %o", reply);
|
||||
resolve(reply);
|
||||
},
|
||||
reject,
|
||||
|
@ -12,7 +12,7 @@ import {
|
||||
WalletKind,
|
||||
WalletStore,
|
||||
} from "@/Wallet";
|
||||
import { barrierQueue, processWorkQueue, WorkQueueItem } from "@snort/shared";
|
||||
import { barrierQueue, processWorkQueue, unwrap, WorkQueueItem } from "@snort/shared";
|
||||
|
||||
const WebLNQueue: Array<WorkQueueItem> = [];
|
||||
processWorkQueue(WebLNQueue);
|
||||
@ -75,8 +75,12 @@ export class WebLNWallet implements LNWallet {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
getBalance(): Promise<Sats> {
|
||||
return Promise.resolve(0);
|
||||
async getBalance(): Promise<Sats> {
|
||||
if (window.webln?.getBalance) {
|
||||
const rsp = await barrierQueue(WebLNQueue, async () => await unwrap(window.webln?.getBalance).call(window.webln));
|
||||
return rsp.balance;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
async createInvoice(req: InvoiceRequest): Promise<WalletInvoice> {
|
||||
@ -124,4 +128,12 @@ export class WebLNWallet implements LNWallet {
|
||||
getInvoices(): Promise<WalletInvoice[]> {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
canGetInvoices() {
|
||||
return false;
|
||||
}
|
||||
|
||||
canGetBalance() {
|
||||
return window.webln?.getBalance !== undefined;
|
||||
}
|
||||
}
|
||||
|
@ -101,7 +101,6 @@ export type MilliSats = number;
|
||||
|
||||
export interface LNWallet {
|
||||
isReady(): boolean;
|
||||
canAutoLogin(): boolean;
|
||||
getInfo: () => Promise<WalletInfo>;
|
||||
login: (password?: string) => Promise<boolean>;
|
||||
close: () => Promise<boolean>;
|
||||
@ -109,6 +108,10 @@ export interface LNWallet {
|
||||
createInvoice: (req: InvoiceRequest) => Promise<WalletInvoice>;
|
||||
payInvoice: (pr: string) => Promise<WalletInvoice>;
|
||||
getInvoices: () => Promise<WalletInvoice[]>;
|
||||
|
||||
canAutoLogin: () => boolean;
|
||||
canGetInvoices: () => boolean;
|
||||
canGetBalance: () => boolean;
|
||||
}
|
||||
|
||||
export interface WalletConfig {
|
||||
|
@ -39,7 +39,6 @@ import MessagesPage from "@/Pages/Messages/MessagesPage";
|
||||
import DonatePage from "@/Pages/DonatePage";
|
||||
import SearchPage from "@/Pages/SearchPage";
|
||||
import HelpPage from "@/Pages/HelpPage";
|
||||
import { WalletRoutes } from "@/Pages/WalletPage";
|
||||
import NostrLinkHandler from "@/Pages/NostrLinkHandler";
|
||||
import { ThreadRoute } from "@/Element/Event/Thread";
|
||||
import { SubscribeRoutes } from "@/Pages/subscribe";
|
||||
@ -275,7 +274,6 @@ const mainRoutes = [
|
||||
element: <NetworkGraph />,
|
||||
},
|
||||
...OnboardingRoutes,
|
||||
...WalletRoutes,
|
||||
] as Array<RouteObject>;
|
||||
|
||||
if (CONFIG.features.zapPool) {
|
||||
|
@ -894,9 +894,6 @@
|
||||
"VnXp8Z": {
|
||||
"defaultMessage": "Avatar"
|
||||
},
|
||||
"VvaJst": {
|
||||
"defaultMessage": "View Wallets"
|
||||
},
|
||||
"W1yoZY": {
|
||||
"defaultMessage": "It looks like you dont have any subscriptions, you can get one {link}"
|
||||
},
|
||||
|
@ -294,7 +294,6 @@
|
||||
"VfhYxG": "To see a full list of changes you can view the changelog {here}",
|
||||
"VlJkSk": "{n} muted",
|
||||
"VnXp8Z": "Avatar",
|
||||
"VvaJst": "View Wallets",
|
||||
"W1yoZY": "It looks like you dont have any subscriptions, you can get one {link}",
|
||||
"W2PiAr": "{n} Blocked",
|
||||
"W9355R": "Unmute",
|
||||
|
10
yarn.lock
10
yarn.lock
@ -2911,7 +2911,7 @@ __metadata:
|
||||
"@uidotdev/usehooks": ^2.4.1
|
||||
"@vitejs/plugin-react": ^4.2.0
|
||||
"@void-cat/api": ^1.0.10
|
||||
"@webbtc/webln-types": ^1.0.10
|
||||
"@webbtc/webln-types": ^2.1.0
|
||||
"@webscopeio/react-textarea-autocomplete": ^4.9.2
|
||||
autoprefixer: ^10.4.16
|
||||
classnames: ^2.3.2
|
||||
@ -3961,10 +3961,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@webbtc/webln-types@npm:^1.0.10":
|
||||
version: 1.0.14
|
||||
resolution: "@webbtc/webln-types@npm:1.0.14"
|
||||
checksum: eaa363bf3a9c278be51c93487904c04518e8812d97449d8d7866089aae74756451a48245a31b1a0fd591bc4798a96a29516ad395b8828c9f2af920cf65a305ac
|
||||
"@webbtc/webln-types@npm:^2.1.0":
|
||||
version: 2.1.0
|
||||
resolution: "@webbtc/webln-types@npm:2.1.0"
|
||||
checksum: 71c8ae3fc4e163dfa2271f19216b603f53a6910e65fdb115b1920ef9be4b7fa990d9c1c644900a7e88c69e8a8a8cc2e273aa490903e6064e2f296498cdca53ff
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user