feat: cashu wallet setup
feat: wallet send/receive pages
This commit is contained in:
parent
1e08702072
commit
7bc00b4624
@ -1,47 +0,0 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEApyUVkYJVwV7XgluUnllgCtrsdq1ctRICm5gQy8nd+aEdDQjA
|
||||
CKPOWh5miLl/fAQVZGZy/JxavzXulwXo8238E6n6bmNB1Us2nuw7a0aW4iUSQ1Pt
|
||||
P4ZhPpcrqeqMf+hp7iBW0nAHFy/aa2UR84d7tBmSk5J3NNrfBsZdUex/7FqF1EVv
|
||||
mEzlc8kepU9lRXWFQDtZCllEZ1kY3SBJPm10h0g9saI8YIVRxUuNII5GHDYAE3hb
|
||||
EmoY6fuSEoiXA8u0Yt9soBQxgxIhQVKSRPPoIPjGFOxsGHY6h8R9nx1kxhHKFRuV
|
||||
nwsn0uWl/7yjhwyHanogJu73/WgelPcgP/hMDQIDAQABAoIBAAru+xU0oGVwzcoi
|
||||
MXuWPxkWrwcoWfsiPXduIBMklleg+WSD4QPvqyzr9isVb0huf/O8W+M4WxtM7NmG
|
||||
MnHSDP5ATThxV7obHGyS6WQgDvimEibDU66nHK9adim8RQqM6nkANo23dE9I+xGx
|
||||
X9Y9U5M5ZQQwPYoAkzw/N5WHUerk+cSEYWYV8jDtO7wJhYOMu5qliPeuNOaWZ1W6
|
||||
1uwr8A4ih69WwzugPuBSgBrPAW1c84zWIFN+njAugqPF5x8xp2uM3tUO9s5UlHJC
|
||||
FWEuU40KcDT2utSUY+2HXSHbycF4KLKT5jAKSa4sPziLfo+YifrlN0Y3rhofUlZT
|
||||
jCaeZ8ECgYEA5/xpk8aVhCEvv5iCghv0p/IHcjdXjx5+PCWh3Adx0fF91UvU5oqn
|
||||
okdyYZDShZMuLDfJ0lG+OMKZd01JapnbTtiVNceVRMnraIdoWEM2/4bTXTSZGtdA
|
||||
8gh/Kc/PMbPf5ppVWwqTCbUkPOSyGHyGc7+DQquq1w6yZu04A3x9vHECgYEAuHJk
|
||||
uz8YKY5ZUR7CZ3y7YFuwq5Lcpl43AfiiCasjRch0P8yLrITc/6fORsXyy64XW9fC
|
||||
h3YmXvEPaM03W2dxw2aQDvXEvXiEITzmILs7SE3UmZR9m7OMy7Jeqr3+JOc0ckZe
|
||||
Rz5FfuMt1IvNB6lrpfHVtoVrpCOXpzHgC/k/x10CgYA6lU18GfwL/+107uiWPsUL
|
||||
3FzxBPTBmau7OK2lSOP/ZoKmaJ39Eiq/GlfSN6ZSQRa55+S5jhcBcnMa45OUrgHp
|
||||
6VvU1u/lDTC7luZM07yBzuR1dyDq3Ez0Uhz6zBXAsXHrZDIF6ae0HeBm2EH5WQkD
|
||||
Fevp3DwqTvXSdDle+AMwoQKBgQCBSlaH1rNmNc0wCsK07f8ejUcrDZgz2mjurc1P
|
||||
v7HK8bdjHUtvE/ciEguLGqiV06O2EmjesZg2Bv4JNYivPrTFBrjGc8qEEd10uw6J
|
||||
NRVaGoyDV04w/UwdYRvwzZs/XP4reF4PzHvEdRSkH5cJ3t2BhiKLfby1YumkHlbx
|
||||
rbbiVQKBgB02jyZUiB6pPTCP8vXZCJbZELgqNyS04ALhBBpdfGMcU1+0hRLJFBaE
|
||||
tClJPGARFXl+MPkY032vmJZOuH3LrcTCm8DmMLzM/hT1EWawQ8BJkkwiIokE4lqc
|
||||
Bi8CrkvuQs2cuCStK6C3Nkyr1lTkDge46trsb7KTcfHdtLsS7EPj
|
||||
-----END RSA PRIVATE KEY-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDWzCCAkOgAwIBAgIJDji8iiceMvQlMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV
|
||||
BAMTCWxvY2FsaG9zdDAeFw0yMzEwMTYwOTI0MThaFw0yMzExMTUxMDI0MThaMBQx
|
||||
EjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
|
||||
ggEBAKclFZGCVcFe14JblJ5ZYAra7HatXLUSApuYEMvJ3fmhHQ0IwAijzloeZoi5
|
||||
f3wEFWRmcvycWr817pcF6PNt/BOp+m5jQdVLNp7sO2tGluIlEkNT7T+GYT6XK6nq
|
||||
jH/oae4gVtJwBxcv2mtlEfOHe7QZkpOSdzTa3wbGXVHsf+xahdRFb5hM5XPJHqVP
|
||||
ZUV1hUA7WQpZRGdZGN0gST5tdIdIPbGiPGCFUcVLjSCORhw2ABN4WxJqGOn7khKI
|
||||
lwPLtGLfbKAUMYMSIUFSkkTz6CD4xhTsbBh2OofEfZ8dZMYRyhUblZ8LJ9Llpf+8
|
||||
o4cMh2p6ICbu9/1oHpT3ID/4TA0CAwEAAaOBrzCBrDAMBgNVHRMEBTADAQH/MAsG
|
||||
A1UdDwQEAwIC9DAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUF
|
||||
BwMDBggrBgEFBQcDCDBcBgNVHREEVTBTgglsb2NhbGhvc3SCFWxvY2FsaG9zdC5s
|
||||
b2NhbGRvbWFpboIGbHZoLm1lgggqLmx2aC5tZYIFWzo6MV2HBH8AAAGHEP6AAAAA
|
||||
AAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggEBABY0rgWuzLYvVtvoVvWKS9cg
|
||||
8rVhBRIFvpYO814ocN1iaxYQ9t9uLRsJXj0K+z1BHWf0zBiw4mB3dD9VpiKpuliL
|
||||
4tRT+vATA96OYCd9G5k7DFQascAau40H3jxckh9rimIWa45FUSd7FIcddo1jeciv
|
||||
gdAdiNUuHBen82O8KHJb+1PCBdA8RYeO5EGKfJM2yrOovu7dAFilf1ZPkXWgXnfG
|
||||
nN6YfDDo9rAVDbvNXImrkwmGqEcN3Pq909IHiM/VETlU5lP4AbTNgrDa/aaZ+I+b
|
||||
1MC1p87MvnibyXs+rTlK5+j8E6noNcD7tsHNd4ufkVCqr+pvSpuA3OvnXjbbm54=
|
||||
-----END CERTIFICATE-----
|
@ -2,10 +2,10 @@
|
||||
"name": "@snort/app",
|
||||
"version": "0.1.24",
|
||||
"dependencies": {
|
||||
"@cashu/cashu-ts": "^0.6.1",
|
||||
"@cashu/cashu-ts": "0.6.1",
|
||||
"@lightninglabs/lnc-web": "^0.2.3-alpha",
|
||||
"@noble/curves": "^1.0.0",
|
||||
"@noble/hashes": "^1.2.0",
|
||||
"@noble/hashes": "^1.3.3",
|
||||
"@scure/base": "^1.1.1",
|
||||
"@scure/bip32": "^1.3.0",
|
||||
"@scure/bip39": "^1.1.1",
|
||||
|
@ -21,9 +21,9 @@
|
||||
<symbol id="bookmark" viewBox="0 0 12 14" fill="none">
|
||||
<path d="M1.3335 4.2C1.3335 3.0799 1.3335 2.51984 1.55148 2.09202C1.74323 1.71569 2.04919 1.40973 2.42552 1.21799C2.85334 1 3.41339 1 4.5335 1H7.46683C8.58693 1 9.14699 1 9.57481 1.21799C9.95114 1.40973 10.2571 1.71569 10.4488 2.09202C10.6668 2.51984 10.6668 3.0799 10.6668 4.2V13L6.00016 10.3333L1.3335 13V4.2Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</symbol>
|
||||
<svg id="check" viewBox="0 0 24 25" fill="none">
|
||||
<symbol id="check" viewBox="0 0 24 25" fill="none">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.7071 5.79289C21.0976 6.18342 21.0976 6.81658 20.7071 7.20711L9.70711 18.2071C9.31658 18.5976 8.68342 18.5976 8.29289 18.2071L3.29289 13.2071C2.90237 12.8166 2.90237 12.1834 3.29289 11.7929C3.68342 11.4024 4.31658 11.4024 4.70711 11.7929L9 16.0858L19.2929 5.79289C19.6834 5.40237 20.3166 5.40237 20.7071 5.79289Z" fill="currentColor"/>
|
||||
</svg>
|
||||
</symbol>
|
||||
<symbol id="chevronDown" viewBox="0 0 24 24" fill="none">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.29289 8.29289C5.68342 7.90237 6.31658 7.90237 6.70711 8.29289L12 13.5858L17.2929 8.29289C17.6834 7.90237 18.3166 7.90237 18.7071 8.29289C19.0976 8.68342 19.0976 9.31658 18.7071 9.70711L12.7071 15.7071C12.3166 16.0976 11.6834 16.0976 11.2929 15.7071L5.29289 9.70711C4.90237 9.31658 4.90237 8.68342 5.29289 8.29289Z" fill="currentColor" />
|
||||
</symbol>
|
||||
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 129 KiB |
@ -1,16 +0,0 @@
|
||||
.wallet-history-item {
|
||||
}
|
||||
|
||||
.wallet-history-item time {
|
||||
font-size: small;
|
||||
color: var(--font-tertiary-color);
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
.pending {
|
||||
color: var(--font-tertiary-color);
|
||||
}
|
||||
|
||||
.wallet-buttons > button {
|
||||
margin: 10px;
|
||||
}
|
@ -10,7 +10,7 @@ import { useNavigate } from "react-router-dom";
|
||||
const ConnectCashu = () => {
|
||||
const navigate = useNavigate();
|
||||
const { formatMessage } = useIntl();
|
||||
const [mintUrl, setMintUrl] = useState<string>();
|
||||
const [mintUrl, setMintUrl] = useState<string>("https://8333.space:3338");
|
||||
const [error, setError] = useState<string>();
|
||||
|
||||
async function tryConnect(config: string) {
|
||||
@ -20,7 +20,12 @@ const ConnectCashu = () => {
|
||||
}
|
||||
|
||||
const { CashuWallet } = await import("@/Wallet/Cashu");
|
||||
const connection = new CashuWallet(config);
|
||||
const connection = new CashuWallet({
|
||||
url: config,
|
||||
keys: {},
|
||||
proofs: [],
|
||||
keysets: []
|
||||
}, () => { });
|
||||
await connection.login();
|
||||
const info = await connection.getInfo();
|
||||
const newWallet = {
|
||||
@ -28,7 +33,7 @@ const ConnectCashu = () => {
|
||||
kind: WalletKind.Cashu,
|
||||
active: true,
|
||||
info,
|
||||
data: mintUrl,
|
||||
data: JSON.stringify(connection.getConfig()),
|
||||
} as WalletConfig;
|
||||
Wallets.add(newWallet);
|
||||
navigate("/settings/wallet");
|
||||
|
@ -1,5 +1,3 @@
|
||||
import "./WalletPage.css";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { FormattedMessage, FormattedNumber, useIntl } from "react-intl";
|
||||
@ -91,10 +89,16 @@ export default function WalletPage(props: { showHistory: boolean }) {
|
||||
|
||||
function walletList() {
|
||||
if (walletState.configs.length === 0) {
|
||||
return (
|
||||
<button onClick={() => navigate("/settings/wallet")}>
|
||||
<FormattedMessage defaultMessage="Connect Wallet" id="cg1VJ2" />
|
||||
</button>
|
||||
return (<div className="flex flex-col gap-4">
|
||||
<div>
|
||||
<button onClick={() => navigate("/settings/wallet")}>
|
||||
<FormattedMessage defaultMessage="Connect Wallet" id="cg1VJ2" />
|
||||
</button>
|
||||
</div>
|
||||
<small>
|
||||
<FormattedMessage defaultMessage="Connect a wallet to send instant payments" id="Yf3DwC" />
|
||||
</small>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
@ -125,6 +129,9 @@ export default function WalletPage(props: { showHistory: boolean }) {
|
||||
<h3>
|
||||
<FormattedMessage defaultMessage="Payments" id="pukxg/" description="Wallet transation history" />
|
||||
</h3>
|
||||
{history === undefined && <small>
|
||||
<FormattedMessage defaultMessage="Your sent and received payments will show up here." id="i5gBFz" />
|
||||
</small>}
|
||||
{history?.map(a => {
|
||||
const dirClassname = {
|
||||
"text-[--success]": a.direction === "in",
|
||||
@ -206,9 +213,11 @@ export default function WalletPage(props: { showHistory: boolean }) {
|
||||
}
|
||||
|
||||
function walletInfo() {
|
||||
if (!wallet) return;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col items-center px-6 py-4 bg-[--gray-superdark] rounded-2xl gap-1">
|
||||
<div className="flex flex-col items-center px-6 py-4 bg-[--gray-ultradark] rounded-2xl gap-1">
|
||||
{walletBalance()}
|
||||
<div className="text-secondary">
|
||||
<FormattedMessage
|
||||
@ -221,6 +230,16 @@ export default function WalletPage(props: { showHistory: boolean }) {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
{wallet?.canCreateInvoice() && <AsyncButton className="secondary" onClick={() => navigate("/wallet/receive")}>
|
||||
<FormattedMessage defaultMessage="Receive" id="ULXFfP" />
|
||||
<Icon name="arrow-up-right" className="rotate-180" />
|
||||
</AsyncButton>}
|
||||
{wallet?.canPayInvoice() && <AsyncButton onClick={() => navigate("/wallet/send")}>
|
||||
<FormattedMessage defaultMessage="Send" id="9WRlF4" />
|
||||
<Icon name="arrow-up-right" />
|
||||
</AsyncButton>}
|
||||
</div>
|
||||
</div>
|
||||
{walletHistory()}
|
||||
</>
|
55
packages/app/src/Pages/wallet/receive.tsx
Normal file
55
packages/app/src/Pages/wallet/receive.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
import AsyncButton from "@/Components/Button/AsyncButton";
|
||||
import Copy from "@/Components/Copy/Copy";
|
||||
import QrCode from "@/Components/QrCode";
|
||||
import { useWallet } from "@/Wallet";
|
||||
import { useState } from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
export function WalletReceivePage() {
|
||||
const wallets = useWallet();
|
||||
const { formatMessage } = useIntl();
|
||||
const [invoice, setInvoice] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
const [amount, setAmount] = useState(0);
|
||||
const [comment, setComment] = useState("");
|
||||
|
||||
return <div className="p flex flex-col gap-4">
|
||||
<div className="text-2xl font-bold">
|
||||
<FormattedMessage defaultMessage="Receive" id="ULXFfP" />
|
||||
</div>
|
||||
<p>
|
||||
<FormattedMessage defaultMessage="Receiving to <b>{wallet}</b>" id="PXQ0z0" values={{
|
||||
b: b => <b>"{b}"</b>,
|
||||
wallet: wallets.config?.info.alias
|
||||
}} />
|
||||
</p>
|
||||
<input type="text" placeholder={formatMessage({ defaultMessage: "Comment", id: 'LgbKvU' })} value={comment} onChange={e => setComment(e.target.value)} />
|
||||
<div className="flex flex-col">
|
||||
<small>
|
||||
<FormattedMessage defaultMessage="Amount in sats" id="djLctd" />
|
||||
</small>
|
||||
<input type="number" value={amount} onChange={e => setAmount(Number(e.target.value))} />
|
||||
</div>
|
||||
<AsyncButton onClick={async () => {
|
||||
try {
|
||||
if (wallets.wallet) {
|
||||
const inv = await wallets.wallet.createInvoice({
|
||||
amount: amount,
|
||||
memo: comment,
|
||||
expiry: 600
|
||||
});
|
||||
setInvoice(inv.pr);
|
||||
}
|
||||
} catch (e) {
|
||||
setError((e as Error).message);
|
||||
}
|
||||
}}>
|
||||
<FormattedMessage defaultMessage="Generate Invoice" id="ipHVx5" />
|
||||
</AsyncButton>
|
||||
{error && <b className="warning">{error}</b>}
|
||||
{invoice && <div className="flex flex-col gap-2 items-center">
|
||||
<QrCode data={invoice} link={`lightning:${invoice}`} />
|
||||
<Copy text={invoice} />
|
||||
</div>}
|
||||
</div>
|
||||
}
|
75
packages/app/src/Pages/wallet/send.tsx
Normal file
75
packages/app/src/Pages/wallet/send.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
import AsyncButton from "@/Components/Button/AsyncButton";
|
||||
import Icon from "@/Components/Icons/Icon";
|
||||
import { formatShort } from "@/Utils/Number";
|
||||
import { WalletInvoice, useWallet } from "@/Wallet"
|
||||
import { LNURL } from "@snort/shared";
|
||||
import { useEffect, useState } from "react";
|
||||
import { FormattedMessage, FormattedNumber, useIntl } from "react-intl";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
export function WalletSendPage() {
|
||||
const wallets = useWallet();
|
||||
const { formatMessage } = useIntl();
|
||||
const [invoice, setInvoice] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
const [lnurl, isLnurl] = useState(true);
|
||||
const [amount, setAmount] = useState(0);
|
||||
const [comment, setComment] = useState("");
|
||||
const [result, setResult] = useState<WalletInvoice>();
|
||||
|
||||
useEffect(() => {
|
||||
isLnurl(!invoice.startsWith("lnbc"))
|
||||
}, [invoice]);
|
||||
|
||||
return <div className="p flex flex-col gap-4">
|
||||
<div className="text-2xl font-bold">
|
||||
<FormattedMessage defaultMessage="Send" id="9WRlF4" />
|
||||
</div>
|
||||
<p>
|
||||
<FormattedMessage defaultMessage="Sending from <b>{wallet}</b>" id="Xnimz0" values={{
|
||||
b: b => <b>"{b}"</b>,
|
||||
wallet: wallets.config?.info.alias
|
||||
}} />
|
||||
</p>
|
||||
<input type="text" placeholder={formatMessage({ defaultMessage: "Invoice / Lightning Address", id: 'EHqHsu' })} value={invoice} onChange={e => setInvoice(e.target.value)} />
|
||||
{lnurl && <>
|
||||
<input type="text" placeholder={formatMessage({ defaultMessage: "Comment", id: 'LgbKvU' })} value={comment} onChange={e => setComment(e.target.value)} />
|
||||
<div className="flex flex-col">
|
||||
<small>
|
||||
<FormattedMessage defaultMessage="Amount in sats" id="djLctd" />
|
||||
</small>
|
||||
<input type="number" value={amount} onChange={e => setAmount(Number(e.target.value))} />
|
||||
</div>
|
||||
</>}
|
||||
<AsyncButton onClick={async () => {
|
||||
try {
|
||||
if (wallets.wallet) {
|
||||
if (!isLnurl) {
|
||||
const res = await wallets.wallet.payInvoice(invoice);
|
||||
setResult(res);
|
||||
} else {
|
||||
const lnurl = new LNURL(invoice);
|
||||
await lnurl.load();
|
||||
const pr = await lnurl.getInvoice(amount, comment);
|
||||
if (pr.pr) {
|
||||
const res = await wallets.wallet.payInvoice(pr.pr);
|
||||
setResult(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
setError((e as Error).message);
|
||||
}
|
||||
}}>
|
||||
<FormattedMessage defaultMessage="Pay" id="lD3+8a" />
|
||||
</AsyncButton>
|
||||
{error && <b className="warning">{error}</b>}
|
||||
{result && <div className="flex gap-2">
|
||||
<Icon name="check" className="success" />
|
||||
<FormattedMessage defaultMessage="Paid {amount} sats, fee {fee} sats" id="aRex7h" values={{
|
||||
amount: <FormattedNumber value={result.amount / 1000} />,
|
||||
fee: <FormattedNumber value={result.fees / 1000} />
|
||||
}} />
|
||||
</div>}
|
||||
</div>
|
||||
}
|
@ -15,7 +15,7 @@ export default class AlbyWallet implements LNWallet {
|
||||
#token: OAuthToken;
|
||||
constructor(
|
||||
token: OAuthToken,
|
||||
readonly onChange: () => void,
|
||||
readonly onChange: (data?: object) => void,
|
||||
) {
|
||||
this.#token = token;
|
||||
}
|
||||
@ -23,15 +23,26 @@ export default class AlbyWallet implements LNWallet {
|
||||
isReady() {
|
||||
return true;
|
||||
}
|
||||
|
||||
canAutoLogin() {
|
||||
return true;
|
||||
}
|
||||
|
||||
canGetInvoices() {
|
||||
return this.#token.scope.includes("invoices:read");
|
||||
}
|
||||
|
||||
canGetBalance() {
|
||||
return this.#token.scope.includes("balance:read");
|
||||
}
|
||||
|
||||
canCreateInvoice() {
|
||||
return true;
|
||||
}
|
||||
|
||||
canPayInvoice() {
|
||||
return true;
|
||||
}
|
||||
|
||||
async getInfo() {
|
||||
const me = await this.#fetch<GetUserResponse>("/user/me");
|
||||
|
@ -1,36 +1,67 @@
|
||||
import { LNWallet, Sats, WalletError, WalletErrorCode, WalletInfo, WalletInvoice } from "@/Wallet";
|
||||
import { CashuMint, CashuWallet as TheCashuWallet, Proof } from "@cashu/cashu-ts";
|
||||
import { InvoiceRequest, LNWallet, WalletInfo, WalletInvoice } from "@/Wallet";
|
||||
import { CashuMint, Proof } from "@cashu/cashu-ts";
|
||||
|
||||
export type CashuWalletConfig = {
|
||||
url: string;
|
||||
keys: Record<string, string>;
|
||||
keysets: Array<string>;
|
||||
proofs: Array<Proof>;
|
||||
};
|
||||
|
||||
export class CashuWallet implements LNWallet {
|
||||
#mint: string;
|
||||
#wallet?: TheCashuWallet;
|
||||
#wallet: CashuWalletConfig;
|
||||
#mint: CashuMint;
|
||||
|
||||
constructor(mint: string) {
|
||||
this.#mint = mint;
|
||||
constructor(
|
||||
wallet: CashuWalletConfig,
|
||||
readonly onChange: (data?: object) => void,
|
||||
) {
|
||||
this.#wallet = wallet;
|
||||
this.#mint = new CashuMint(this.#wallet.url);
|
||||
}
|
||||
|
||||
canAutoLogin(): boolean {
|
||||
getConfig() {
|
||||
return { ...this.#wallet };
|
||||
}
|
||||
|
||||
canGetInvoices() {
|
||||
return false;
|
||||
}
|
||||
|
||||
canGetBalance() {
|
||||
return true;
|
||||
}
|
||||
|
||||
isReady(): boolean {
|
||||
return this.#wallet !== undefined;
|
||||
canAutoLogin() {
|
||||
return true;
|
||||
}
|
||||
|
||||
async getInfo(): Promise<WalletInfo> {
|
||||
if (!this.#wallet) {
|
||||
throw new WalletError(WalletErrorCode.GeneralError, "Wallet not initialized");
|
||||
}
|
||||
isReady() {
|
||||
return true;
|
||||
}
|
||||
|
||||
canCreateInvoice() {
|
||||
return true;
|
||||
}
|
||||
|
||||
canPayInvoice() {
|
||||
return true;
|
||||
}
|
||||
|
||||
async getInfo() {
|
||||
return {
|
||||
nodePubKey: "asdd",
|
||||
alias: "Cashu mint: " + this.#mint,
|
||||
alias: "Cashu mint: " + this.#wallet.url,
|
||||
} as WalletInfo;
|
||||
}
|
||||
|
||||
async login(): Promise<boolean> {
|
||||
const m = new CashuMint(this.#mint);
|
||||
const keys = await m.getKeys();
|
||||
this.#wallet = new TheCashuWallet(keys, m);
|
||||
if (this.#wallet.keysets.length === 0) {
|
||||
const keys = await this.#mint.getKeys();
|
||||
this.#wallet.keys = keys;
|
||||
this.#wallet.keysets = [""];
|
||||
this.onChange(this.#wallet);
|
||||
}
|
||||
await this.#checkProofs();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -38,25 +69,36 @@ export class CashuWallet implements LNWallet {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
getBalance(): Promise<Sats> {
|
||||
throw new Error("Method not implemented.");
|
||||
async getBalance() {
|
||||
return this.#wallet.proofs.reduce((acc, v) => (acc += v.amount), 0);
|
||||
}
|
||||
createInvoice(): Promise<WalletInvoice> {
|
||||
throw new Error("Method not implemented.");
|
||||
|
||||
async createInvoice(req: InvoiceRequest) {
|
||||
const rsp = await this.#mint.requestMint(req.amount);
|
||||
return {
|
||||
pr: rsp.pr,
|
||||
} as WalletInvoice;
|
||||
}
|
||||
|
||||
payInvoice(): Promise<WalletInvoice> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
getInvoices(): Promise<WalletInvoice[]> {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
}
|
||||
|
||||
export interface NutStashBackup {
|
||||
proofs: Array<Proof>;
|
||||
mints: [
|
||||
{
|
||||
mintURL: string;
|
||||
},
|
||||
];
|
||||
async #checkProofs() {
|
||||
if (this.#wallet.proofs.length == 0) return;
|
||||
|
||||
const checks = await this.#mint.check({
|
||||
proofs: this.#wallet.proofs.map(a => ({ secret: a.secret })),
|
||||
});
|
||||
|
||||
const filteredProofs = this.#wallet.proofs.filter((_, i) => checks.spendable[i]);
|
||||
this.#wallet.proofs = filteredProofs;
|
||||
if (filteredProofs.length !== checks.spendable.length) {
|
||||
this.onChange(this.#wallet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +44,14 @@ export class LNCWallet implements LNWallet {
|
||||
return true;
|
||||
}
|
||||
|
||||
canCreateInvoice() {
|
||||
return true;
|
||||
}
|
||||
|
||||
canPayInvoice() {
|
||||
return true;
|
||||
}
|
||||
|
||||
isReady(): boolean {
|
||||
return this.#lnc.isReady;
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ export default class LNDHubWallet implements LNWallet {
|
||||
|
||||
constructor(
|
||||
url: string,
|
||||
readonly changed: () => void,
|
||||
readonly changed: (data?: object) => void,
|
||||
) {
|
||||
if (url.startsWith("lndhub://")) {
|
||||
const regex = /^lndhub:\/\/([\S-]+):([\S-]+)@(.*)$/i;
|
||||
@ -58,6 +58,14 @@ export default class LNDHubWallet implements LNWallet {
|
||||
return true;
|
||||
}
|
||||
|
||||
canCreateInvoice() {
|
||||
return true;
|
||||
}
|
||||
|
||||
canPayInvoice() {
|
||||
return true;
|
||||
}
|
||||
|
||||
close(): Promise<boolean> {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ export class NostrConnectWallet implements LNWallet {
|
||||
|
||||
constructor(
|
||||
cfg: string,
|
||||
readonly changed: () => void,
|
||||
readonly changed: (data?: object) => void,
|
||||
) {
|
||||
this.#config = NostrConnectWallet.parseConfigUrl(cfg);
|
||||
this.#commandQueue = new Map();
|
||||
@ -106,6 +106,22 @@ export class NostrConnectWallet implements LNWallet {
|
||||
return this.#conn !== undefined;
|
||||
}
|
||||
|
||||
canGetInvoices() {
|
||||
return this.#supported_methods.includes("list_transactions");
|
||||
}
|
||||
|
||||
canGetBalance() {
|
||||
return this.#supported_methods.includes("get_balance");
|
||||
}
|
||||
|
||||
canCreateInvoice() {
|
||||
return this.#supported_methods.includes("make_invoice");
|
||||
}
|
||||
|
||||
canPayInvoice() {
|
||||
return this.#supported_methods.includes("pay_invoice");
|
||||
}
|
||||
|
||||
async getInfo() {
|
||||
await this.login();
|
||||
if (this.#info) return this.#info;
|
||||
@ -247,14 +263,6 @@ export class NostrConnectWallet implements LNWallet {
|
||||
}
|
||||
}
|
||||
|
||||
canGetInvoices() {
|
||||
return this.#supported_methods.includes("list_transactions");
|
||||
}
|
||||
|
||||
canGetBalance() {
|
||||
return this.#supported_methods.includes("get_balance");
|
||||
}
|
||||
|
||||
async #onReply(sub: string, e: NostrEvent) {
|
||||
if (sub === "info") {
|
||||
const pending = this.#commandQueue.get("info");
|
||||
|
@ -44,6 +44,22 @@ export class WebLNWallet implements LNWallet {
|
||||
return window.webln !== undefined && window.webln !== null;
|
||||
}
|
||||
|
||||
canCreateInvoice() {
|
||||
return true;
|
||||
}
|
||||
|
||||
canPayInvoice() {
|
||||
return true;
|
||||
}
|
||||
|
||||
canGetInvoices() {
|
||||
return false;
|
||||
}
|
||||
|
||||
canGetBalance() {
|
||||
return window.webln?.getBalance !== undefined;
|
||||
}
|
||||
|
||||
canAutoLogin(): boolean {
|
||||
return true;
|
||||
}
|
||||
@ -76,6 +92,7 @@ export class WebLNWallet implements LNWallet {
|
||||
}
|
||||
|
||||
async getBalance(): Promise<Sats> {
|
||||
await this.login();
|
||||
if (window.webln?.getBalance) {
|
||||
const rsp = await barrierQueue(WebLNQueue, async () => await unwrap(window.webln?.getBalance).call(window.webln));
|
||||
return rsp.balance;
|
||||
@ -116,6 +133,7 @@ export class WebLNWallet implements LNWallet {
|
||||
if (rsp) {
|
||||
invoice.state = WalletInvoiceState.Paid;
|
||||
invoice.preimage = rsp.preimage;
|
||||
invoice.fees = "route" in rsp ? (rsp.route as { total_fees: number }).total_fees : 0;
|
||||
return invoice;
|
||||
} else {
|
||||
invoice.state = WalletInvoiceState.Failed;
|
||||
@ -128,12 +146,4 @@ export class WebLNWallet implements LNWallet {
|
||||
getInvoices(): Promise<WalletInvoice[]> {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
canGetInvoices() {
|
||||
return false;
|
||||
}
|
||||
|
||||
canGetBalance() {
|
||||
return window.webln?.getBalance !== undefined;
|
||||
}
|
||||
}
|
||||
|
@ -116,6 +116,8 @@ export interface LNWallet {
|
||||
canAutoLogin: () => boolean;
|
||||
canGetInvoices: () => boolean;
|
||||
canGetBalance: () => boolean;
|
||||
canCreateInvoice: () => boolean;
|
||||
canPayInvoice: () => boolean;
|
||||
}
|
||||
|
||||
export interface WalletConfig {
|
||||
@ -237,16 +239,31 @@ export class WalletStore extends ExternalStore<WalletStoreSnapshot> {
|
||||
return new WebLNWallet();
|
||||
}
|
||||
case WalletKind.LNDHub: {
|
||||
return new LNDHubWallet(unwrap(cfg.data), () => this.notifyChange());
|
||||
return new LNDHubWallet(unwrap(cfg.data), d => this.#onWalletChange(cfg, d));
|
||||
}
|
||||
case WalletKind.NWC: {
|
||||
return new NostrConnectWallet(unwrap(cfg.data), () => this.notifyChange());
|
||||
return new NostrConnectWallet(unwrap(cfg.data), d => this.#onWalletChange(cfg, d));
|
||||
}
|
||||
case WalletKind.Alby: {
|
||||
return new AlbyWallet(JSON.parse(unwrap(cfg.data)), () => this.notifyChange());
|
||||
return new AlbyWallet(JSON.parse(unwrap(cfg.data)), d => this.#onWalletChange(cfg, d));
|
||||
}
|
||||
case WalletKind.Cashu: {
|
||||
return import("./Cashu").then(
|
||||
({ CashuWallet }) => new CashuWallet(JSON.parse(unwrap(cfg.data)), d => this.#onWalletChange(cfg, d)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#onWalletChange(cfg: WalletConfig, data?: object) {
|
||||
if (data) {
|
||||
const activeConfig = this.#configs.find(a => a.id === cfg.id);
|
||||
if (activeConfig) {
|
||||
activeConfig.data = JSON.stringify(data);
|
||||
}
|
||||
}
|
||||
this.notifyChange();
|
||||
}
|
||||
}
|
||||
|
||||
export const Wallets = new WalletStore();
|
||||
|
@ -38,9 +38,11 @@ import { OnboardingRoutes } from "@/Pages/onboarding";
|
||||
import { setupWebLNWalletConfig } from "@/Wallet/WebLN";
|
||||
import { Wallets } from "@/Wallet";
|
||||
import NetworkGraph from "@/Pages/NetworkGraph";
|
||||
import WalletPage from "./Pages/WalletPage";
|
||||
import WalletPage from "./Pages/wallet";
|
||||
import { hasWasm, wasmInit, WasmPath } from "@/Utils/wasm";
|
||||
import { System } from "@/system";
|
||||
import { WalletSendPage } from "./Pages/wallet/send";
|
||||
import { WalletReceivePage } from "./Pages/wallet/receive";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@ -146,6 +148,14 @@ const mainRoutes = [
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/wallet/send",
|
||||
element: <WalletSendPage />
|
||||
},
|
||||
{
|
||||
path: "/wallet/receive",
|
||||
element: <WalletReceivePage />
|
||||
},
|
||||
...OnboardingRoutes,
|
||||
...SettingsRoutes,
|
||||
] as Array<RouteObject>;
|
||||
|
26
yarn.lock
26
yarn.lock
@ -1421,7 +1421,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@cashu/cashu-ts@npm:^0.6.1":
|
||||
"@cashu/cashu-ts@npm:0.6.1":
|
||||
version: 0.6.1
|
||||
resolution: "@cashu/cashu-ts@npm:0.6.1"
|
||||
dependencies:
|
||||
@ -2589,14 +2589,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@noble/hashes@npm:1.3.2, @noble/hashes@npm:^1.2.0, @noble/hashes@npm:^1.3.2, @noble/hashes@npm:~1.3.0, @noble/hashes@npm:~1.3.2":
|
||||
"@noble/hashes@npm:1.3.2, @noble/hashes@npm:^1.3.2, @noble/hashes@npm:~1.3.0, @noble/hashes@npm:~1.3.2":
|
||||
version: 1.3.2
|
||||
resolution: "@noble/hashes@npm:1.3.2"
|
||||
checksum: fe23536b436539d13f90e4b9be843cc63b1b17666a07634a2b1259dded6f490be3d050249e6af98076ea8f2ea0d56f578773c2197f2aa0eeaa5fba5bc18ba474
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@noble/hashes@npm:~1.3.1":
|
||||
"@noble/hashes@npm:^1.3.3, @noble/hashes@npm:~1.3.1":
|
||||
version: 1.3.3
|
||||
resolution: "@noble/hashes@npm:1.3.3"
|
||||
checksum: 8a6496d1c0c64797339bc694ad06cdfaa0f9e56cd0c3f68ae3666cfb153a791a55deb0af9c653c7ed2db64d537aa3e3054629740d2f2338bb1dcb7ab60cd205b
|
||||
@ -2923,11 +2923,11 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@snort/app@workspace:packages/app"
|
||||
dependencies:
|
||||
"@cashu/cashu-ts": ^0.6.1
|
||||
"@cashu/cashu-ts": 0.6.1
|
||||
"@formatjs/cli": ^6.1.3
|
||||
"@lightninglabs/lnc-web": ^0.2.3-alpha
|
||||
"@noble/curves": ^1.0.0
|
||||
"@noble/hashes": ^1.2.0
|
||||
"@noble/hashes": ^1.3.3
|
||||
"@scure/base": ^1.1.1
|
||||
"@scure/bip32": ^1.3.0
|
||||
"@scure/bip39": ^1.1.1
|
||||
@ -4446,13 +4446,13 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"axios@npm:^1.2.1":
|
||||
version: 1.6.2
|
||||
resolution: "axios@npm:1.6.2"
|
||||
version: 1.6.4
|
||||
resolution: "axios@npm:1.6.4"
|
||||
dependencies:
|
||||
follow-redirects: ^1.15.0
|
||||
follow-redirects: ^1.15.4
|
||||
form-data: ^4.0.0
|
||||
proxy-from-env: ^1.1.0
|
||||
checksum: 4a7429e2b784be0f2902ca2680964391eae7236faa3967715f30ea45464b98ae3f1c6f631303b13dfe721b17126b01f486c7644b9ef276bfc63112db9fd379f8
|
||||
checksum: 48d8af8488ac7402fae312437c0189b3b609a472fca2f7fc796129c804d98520589b6317096eba8509711d49f855a3f620b6a24ff23acd73ac26433d0383b8f9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -6273,13 +6273,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"follow-redirects@npm:^1.15.0":
|
||||
version: 1.15.3
|
||||
resolution: "follow-redirects@npm:1.15.3"
|
||||
"follow-redirects@npm:^1.15.4":
|
||||
version: 1.15.4
|
||||
resolution: "follow-redirects@npm:1.15.4"
|
||||
peerDependenciesMeta:
|
||||
debug:
|
||||
optional: true
|
||||
checksum: 584da22ec5420c837bd096559ebfb8fe69d82512d5585004e36a3b4a6ef6d5905780e0c74508c7b72f907d1fa2b7bd339e613859e9c304d0dc96af2027fd0231
|
||||
checksum: e178d1deff8b23d5d24ec3f7a94cde6e47d74d0dc649c35fc9857041267c12ec5d44650a0c5597ef83056ada9ea6ca0c30e7c4f97dbf07d035086be9e6a5b7b6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user