diff --git a/components/cart-invoice-card.tsx b/components/cart-invoice-card.tsx index 1f0005e..452f57a 100644 --- a/components/cart-invoice-card.tsx +++ b/components/cart-invoice-card.tsx @@ -32,6 +32,7 @@ import { CashuWallet, getEncodedToken, Proof, + MintKeyset, } from "@cashu/cashu-ts"; import { constructGiftWrappedMessageEvent, @@ -520,16 +521,12 @@ export default function CartInvoiceCard({ while (true) { try { - const { proofs } = await wallet.requestTokens(newPrice, hash); + const proofs = await wallet.mintProofs(newPrice, hash); // Encoded proofs can be spent at the mint encoded = getEncodedToken({ - token: [ - { - mint: mints[0], - proofs, - }, - ], + mint: mints[0], + proofs, }); if (encoded) { @@ -593,16 +590,14 @@ export default function CartInvoiceCard({ const title = product.title; const pubkey = product.pubkey; let tokenAmount = totalCostsInSats[pubkey]; - const tokenToSend = await wallet.send(tokenAmount, remainingProofs); + const { keep, send } = await wallet.send(tokenAmount, remainingProofs, { + includeFees: true, + }); let encodedTokenToSend = getEncodedToken({ - token: [ - { - mint: mints[0], - proofs: tokenToSend.send, - }, - ], + mint: mints[0], + proofs: send, }); - remainingProofs = tokenToSend.returnChange; + remainingProofs = keep; let paymentMessage = ""; if (quantities[product.id] && quantities[product.id] > 1) { if (userNPub) { @@ -890,15 +885,17 @@ export default function CartInvoiceCard({ try { const mint = new CashuMint(mints[0]); const wallet = new CashuWallet(mint); - const mintKeySetResponse = await mint.getKeySets(); - const mintKeySetIds = mintKeySetResponse?.keysets; + const mintKeySetIds = await wallet.getKeySets(); const filteredProofs = tokens.filter( - (p: Proof) => mintKeySetIds?.includes(p.id), + (p: Proof) => + mintKeySetIds?.some((keysetId: MintKeyset) => keysetId.id === p.id), ); - const tokenToSend = await wallet.send(price, filteredProofs); + const { keep, send } = await wallet.send(price, filteredProofs, { + includeFees: true, + }); await sendTokens( wallet, - tokenToSend.send, + send, shippingName ? shippingName : undefined, shippingAddress ? shippingAddress : undefined, shippingUnitNo ? shippingUnitNo : undefined, @@ -910,9 +907,10 @@ export default function CartInvoiceCard({ contactType ? contactType : undefined, contactInstructions ? contactInstructions : undefined, ); - const changeProofs = tokenToSend?.returnChange; + const changeProofs = keep; const remainingProofs = tokens.filter( - (p: Proof) => !mintKeySetIds?.includes(p.id), + (p: Proof) => + mintKeySetIds?.some((keysetId: MintKeyset) => keysetId.id !== p.id), ); let proofArray; if (changeProofs.length >= 1 && changeProofs) { diff --git a/components/messages/chat-message.tsx b/components/messages/chat-message.tsx index 011388c..57cfd22 100644 --- a/components/messages/chat-message.tsx +++ b/components/messages/chat-message.tsx @@ -6,10 +6,11 @@ import { getLocalStorageData } from "../utility/nostr-helper-functions"; import ClaimButton from "../utility-components/claim-button"; import { NostrMessageEvent } from "../../utils/types/types"; import { timeSinceMessageDisplayText } from "../../utils/messages/utils"; +import { getDecodedToken } from "@cashu/cashu-ts"; function isDecodableToken(token: string): boolean { try { - atob(token); + getDecodedToken(token); return true; } catch (e) { return false; @@ -65,14 +66,16 @@ export const ChatMessage = ({ } }, [messageEvent]); - const tokenAfterCashuA = messageEvent.content.includes("cashuA") - ? messageEvent.content.split("cashuA")[1] + const cashuMatch = messageEvent.content.match(/cashu[A-Za-z]/); + const cashuPrefix = cashuMatch ? cashuMatch[0] : null; + const tokenAfterCashuVersion = cashuPrefix + ? messageEvent.content.split(cashuPrefix)[1] : null; - const canDecodeToken = tokenAfterCashuA - ? isDecodableToken(tokenAfterCashuA) + const canDecodeToken = tokenAfterCashuVersion + ? isDecodableToken(cashuPrefix + tokenAfterCashuVersion) : false; - const contentBeforeCashuA = messageEvent.content.includes("cashuA") - ? messageEvent.content.split("cashuA")[0] + const contentBeforeCashu = cashuPrefix + ? messageEvent.content.split(cashuPrefix)[0] : messageEvent.content; const { userPubkey } = getLocalStorageData(); @@ -128,15 +131,18 @@ export const ChatMessage = ({ }`} >

- {messageEvent.content.includes("cashuA") && - canDecodeToken && - tokenAfterCashuA ? ( + {cashuPrefix && canDecodeToken && tokenAfterCashuVersion ? ( <> - {renderMessageContent(contentBeforeCashuA)} + {renderMessageContent(contentBeforeCashu)}

- + handleCopyToken("cashuA" + tokenAfterCashuA)} + onClick={() => + handleCopyToken(cashuPrefix + tokenAfterCashuVersion) + } className={`ml-2 mt-1 h-5 w-5 cursor-pointer text-light-text ${ copiedToClipboard ? "hidden" : "" }`} diff --git a/components/product-invoice-card.tsx b/components/product-invoice-card.tsx index 4430136..7988602 100644 --- a/components/product-invoice-card.tsx +++ b/components/product-invoice-card.tsx @@ -31,6 +31,7 @@ import { CashuMint, CashuWallet, getEncodedToken, + MintKeyset, Proof, } from "@cashu/cashu-ts"; import { @@ -483,19 +484,12 @@ export default function ProductInvoiceCard({ while (true) { try { - const { proofs } = await wallet.requestTokens(newPrice, hash); - - // Encoded proofs can be spent at the mint + const proofs = await wallet.mintProofs(newPrice, hash); encoded = getEncodedToken({ - token: [ - { - mint: mints[0], - proofs, - }, - ], + mint: mints[0], + proofs, }); - - if (encoded) { + if (encoded !== "" && encoded !== undefined) { await sendTokens( encoded, shippingName ? shippingName : undefined, @@ -812,19 +806,17 @@ export default function ProductInvoiceCard({ try { const mint = new CashuMint(mints[0]); const wallet = new CashuWallet(mint); - const mintKeySetResponse = await mint.getKeySets(); - const mintKeySetIds = mintKeySetResponse?.keysets; + const mintKeySetIds = await wallet.getKeySets(); const filteredProofs = tokens.filter( - (p: Proof) => mintKeySetIds?.includes(p.id), + (p: Proof) => + mintKeySetIds?.some((keysetId: MintKeyset) => keysetId.id === p.id), ); - const tokenToSend = await wallet.send(price, filteredProofs); + const { keep, send } = await wallet.send(price, filteredProofs, { + includeFees: true, + }); const encodedSendToken = getEncodedToken({ - token: [ - { - mint: mints[0], - proofs: tokenToSend.send, - }, - ], + mint: mints[0], + proofs: send, }); await sendTokens( encodedSendToken, @@ -845,9 +837,10 @@ export default function ProductInvoiceCard({ .catch((error) => { console.error(error); }); - const changeProofs = tokenToSend?.returnChange; + const changeProofs = keep; const remainingProofs = tokens.filter( - (p: Proof) => !mintKeySetIds?.includes(p.id), + (p: Proof) => + mintKeySetIds?.some((keysetId: MintKeyset) => keysetId.id !== p.id), ); let proofArray; if (changeProofs.length >= 1 && changeProofs) { diff --git a/components/utility-components/claim-button.tsx b/components/utility-components/claim-button.tsx index 5212436..d0821ca 100644 --- a/components/utility-components/claim-button.tsx +++ b/components/utility-components/claim-button.tsx @@ -25,23 +25,15 @@ import { } from "../utility/nostr-helper-functions"; import { SHOPSTRBUTTONCLASSNAMES } from "../utility/STATIC-VARIABLES"; import { LightningAddress } from "@getalby/lightning-tools"; -import { CashuMint, CashuWallet, Proof } from "@cashu/cashu-ts"; +import { + CashuMint, + CashuWallet, + Proof, + getDecodedToken, +} from "@cashu/cashu-ts"; import RedemptionModal from "./redemption-modal"; import { formatWithCommas } from "./display-monetary-info"; -function decodeBase64ToJson(base64: string): any { - // Step 1: Decode the base64 string to a regular string - const decodedString = atob(base64); - // Step 2: Parse the decoded string as JSON - try { - const json = JSON.parse(decodedString); - return json; - } catch (error) { - console.error("Error parsing JSON from base64", error); - throw new Error("Invalid JSON format in base64 string."); - } -} - export default function ClaimButton({ token, passphrase, @@ -51,7 +43,7 @@ export default function ClaimButton({ }) { const [lnurl, setLnurl] = useState(""); const profileContext = useContext(ProfileMapContext); - const { userNPub, userPubkey } = getLocalStorageData(); + const { userPubkey } = getLocalStorageData(); const [openClaimTypeModal, setOpenClaimTypeModal] = useState(false); const [openRedemptionModal, setOpenRedemptionModal] = useState(false); @@ -59,7 +51,7 @@ export default function ClaimButton({ const [isRedeemed, setIsRedeemed] = useState(false); const [isRedeeming, setIsRedeeming] = useState(false); const [wallet, setWallet] = useState(); - const [proofs, setProofs] = useState([]); + const [proofs, setProofs] = useState([]); const [tokenMint, setTokenMint] = useState(""); const [tokenAmount, setTokenAmount] = useState(0); const [formattedTokenAmount, setFormattedTokenAmount] = useState(""); @@ -77,15 +69,13 @@ export default function ClaimButton({ const { mints, tokens, history } = getLocalStorageData(); - const [name, setName] = useState(""); - - const { theme, setTheme } = useTheme(); + const { theme } = useTheme(); useEffect(() => { - const decodedToken = decodeBase64ToJson(token); - const mint = decodedToken.token[0].mint; + const decodedToken = getDecodedToken(token); + const mint = decodedToken.mint; setTokenMint(mint); - const proofs = decodedToken.token[0].proofs; + const proofs = decodedToken.proofs; setProofs(proofs); const newWallet = new CashuWallet(new CashuMint(mint)); setWallet(newWallet); @@ -103,8 +93,17 @@ export default function ClaimButton({ const checkProofsSpent = async () => { try { if (proofs.length > 0) { - const spentProofs = await wallet?.checkProofsSpent(proofs); - if (spentProofs && spentProofs.length > 0) setIsRedeemed(true); + let proofsStates = await wallet?.checkProofsStates(proofs); + if (proofsStates) { + const spentYs = new Set( + proofsStates + .filter((state) => state.state === "SPENT") + .map((state) => state.Y), + ); + if (spentYs.size > 0) { + setIsRedeemed(true); + } + } } } catch (error) { console.error(error); @@ -126,11 +125,6 @@ export default function ClaimButton({ ? sellerProfile.content.lud16 : "invalid", ); - setName( - sellerProfile && sellerProfile.content.name - ? sellerProfile.content.name - : userNPub, - ); }, [profileContext, tokenMint]); useEffect(() => { @@ -164,8 +158,15 @@ export default function ClaimButton({ setIsInvalidToken(false); setIsRedeeming(true); try { - const spentProofs = await wallet?.checkProofsSpent(proofs); - if (spentProofs?.length === 0) { + let proofsStates = await wallet?.checkProofsStates(proofs); + const spentYs = proofsStates + ? new Set( + proofsStates + .filter((state) => state.state === "SPENT") + .map((state) => state.Y), + ) + : new Set(); + if (spentYs.size === 0) { const uniqueProofs = proofs.filter( (proof: Proof) => !tokens.some((token: Proof) => token.C === proof.C), ); @@ -223,28 +224,37 @@ export default function ClaimButton({ const newAmount = Math.floor(tokenAmount * 0.98 - 2); const ln = new LightningAddress(lnurl); try { - await ln.fetch(); - const invoice = await ln.requestInvoice({ satoshi: newAmount }); - const invoicePaymentRequest = invoice.paymentRequest; - const response = await wallet?.payLnInvoice( - invoicePaymentRequest, - proofs, - ); - const changeProofs = response?.change; - const changeAmount = - Array.isArray(changeProofs) && changeProofs.length > 0 - ? changeProofs.reduce( - (acc, current: Proof) => acc + current.amount, - 0, - ) - : 0; - if (changeAmount >= 1 && changeProofs) { - setClaimChangeAmount(changeAmount); - setClaimChangeProofs(changeProofs); + if (wallet) { + await wallet.loadMint(); + await ln.fetch(); + const invoice = await ln.requestInvoice({ satoshi: newAmount }); + const invoicePaymentRequest = invoice.paymentRequest; + const meltQuote = await wallet.createMeltQuote(invoicePaymentRequest); + if (meltQuote) { + const meltQuoteTotal = meltQuote.amount + meltQuote.fee_reserve; + const { keep, send } = await wallet.send(meltQuoteTotal, proofs, { + includeFees: true, + }); + const meltResponse = await wallet.meltProofs(meltQuote, send); + const changeProofs = [...keep, ...meltResponse.change]; + const changeAmount = + Array.isArray(changeProofs) && changeProofs.length > 0 + ? changeProofs.reduce( + (acc, current: Proof) => acc + current.amount, + 0, + ) + : 0; + if (changeAmount >= 1 && changeProofs) { + setClaimChangeAmount(changeAmount); + setClaimChangeProofs(changeProofs); + } + setIsPaid(true); + setOpenRedemptionModal(true); + setIsRedeeming(false); + } + } else { + throw new Error("Wallet not initialized"); } - setIsPaid(true); - setOpenRedemptionModal(true); - setIsRedeeming(false); } catch (error) { console.log(error); setIsPaid(false); diff --git a/components/utility-components/redemption-modal.tsx b/components/utility-components/redemption-modal.tsx index 89e6a06..4064157 100644 --- a/components/utility-components/redemption-modal.tsx +++ b/components/utility-components/redemption-modal.tsx @@ -41,7 +41,7 @@ export default function RedemptionModal({ changeMint: string; }) { const [showModal, setShowModal] = useState(false); - const { userPubkey, relays } = getLocalStorageData(); + const { userPubkey } = getLocalStorageData(); const [formattedChangeAmount, setFormattedChangeAmount] = useState(""); @@ -92,12 +92,8 @@ export default function RedemptionModal({ let decodedRandomPubkeyForReceiver = nip19.decode(randomNpubForReceiver); let decodedRandomPrivkeyForReceiver = nip19.decode(randomNsecForReceiver); let encodedChange = getEncodedToken({ - token: [ - { - mint: changeMint, - proofs: changeProofs, - }, - ], + mint: changeMint, + proofs: changeProofs, }); const paymentMessage = "Overpaid fee change: " + encodedChange; let giftWrappedMessageEvent = await constructGiftWrappedMessageEvent( diff --git a/components/wallet/mint-button.tsx b/components/wallet/mint-button.tsx index 6caaf44..e7ba19f 100644 --- a/components/wallet/mint-button.tsx +++ b/components/wallet/mint-button.tsx @@ -129,7 +129,7 @@ const MintButton = ({ passphrase }: { passphrase?: string }) => { ) { while (true) { try { - const { proofs } = await wallet.requestTokens(numSats, hash); + const proofs = await wallet.mintProofs(numSats, hash); if (proofs) { const proofArray = [...tokens, ...proofs]; diff --git a/components/wallet/pay-button.tsx b/components/wallet/pay-button.tsx index 00caba5..0b5c4fe 100644 --- a/components/wallet/pay-button.tsx +++ b/components/wallet/pay-button.tsx @@ -23,7 +23,7 @@ import { publishSpendingHistoryEvent, } from "../utility/nostr-helper-functions"; import { SHOPSTRBUTTONCLASSNAMES } from "../utility/STATIC-VARIABLES"; -import { CashuMint, CashuWallet, Proof } from "@cashu/cashu-ts"; +import { CashuMint, CashuWallet, MintKeyset, Proof } from "@cashu/cashu-ts"; import { formatWithCommas } from "../utility-components/display-monetary-info"; import { CashuWalletContext } from "../../utils/context/context"; @@ -34,7 +34,7 @@ const PayButton = ({ passphrase }: { passphrase?: string }) => { const [isRedeeming, setIsRedeeming] = useState(false); // const [totalAmount, setTotalAmount] = useState(0); - const [feeAmount, setFeeAmount] = useState(""); + const [feeReserveAmount, setFeeReserveAmount] = useState(""); const { mints, tokens, history } = getLocalStorageData(); @@ -49,9 +49,6 @@ const PayButton = ({ passphrase }: { passphrase?: string }) => { reset: payReset, } = useForm(); - const getMint = () => new CashuMint(mints[0]); - const getWallet = () => new CashuWallet(getMint()); - useEffect(() => { const walletEvent = walletContext.mostRecentWalletEvent; if (walletEvent?.tags) { @@ -73,20 +70,18 @@ const PayButton = ({ passphrase }: { passphrase?: string }) => { }; const calculateFee = async (invoice: string) => { - setFeeAmount(""); + setFeeReserveAmount(""); if (invoice && /^lnbc/.test(invoice)) { - const fee = await getWallet().getFee(invoice); - if (fee) { - setFeeAmount(formatWithCommas(fee, "sats")); - // const invoiceValue = new Invoice({ invoice }); - // const { satoshi } = invoiceValue; - // const total = satoshi + fee; - // setTotalAmount(total); + const mint = new CashuMint(mints[0]); + const wallet = new CashuWallet(mint); + const meltQuote = await wallet?.createMeltQuote(invoice); + if (meltQuote) { + setFeeReserveAmount(formatWithCommas(meltQuote.fee_reserve, "sats")); } else { - setFeeAmount(""); + setFeeReserveAmount(""); } } else { - setFeeAmount(""); + setFeeReserveAmount(""); } }; @@ -95,16 +90,19 @@ const PayButton = ({ passphrase }: { passphrase?: string }) => { setPaymentFailed(false); setIsRedeeming(true); try { - const mintKeySetResponse = await getMint().getKeySets(); - const mintKeySetIds = mintKeySetResponse?.keysets; - const filteredProofs = tokens.filter( - (p: Proof) => mintKeySetIds?.includes(p.id), - ); - const response = await getWallet().payLnInvoice( - invoiceString, - filteredProofs, + const mint = new CashuMint(mints[0]); + const wallet = new CashuWallet(mint); + const mintKeySetIds = await wallet.getKeySets(); + const filteredProofs = tokens.filter((p: Proof) => + mintKeySetIds.some((keyset: MintKeyset) => keyset.id === p.id), ); - const changeProofs = response?.change; + const meltQuote = await wallet.createMeltQuote(invoiceString); + const meltQuoteTotal = meltQuote.amount + meltQuote.fee_reserve; + const { keep, send } = await wallet.send(meltQuoteTotal, filteredProofs, { + includeFees: true, + }); + const meltResponse = await wallet.meltProofs(meltQuote, send); + const changeProofs = [...keep, ...meltResponse.change]; const changeAmount = Array.isArray(changeProofs) && changeProofs.length > 0 ? changeProofs.reduce( @@ -113,7 +111,8 @@ const PayButton = ({ passphrase }: { passphrase?: string }) => { ) : 0; const remainingProofs = tokens.filter( - (p: Proof) => !mintKeySetIds?.includes(p.id), + (p: Proof) => + mintKeySetIds?.some((keysetId: MintKeyset) => keysetId.id !== p.id), ); let proofArray; if (changeAmount >= 1 && changeProofs) { @@ -235,9 +234,9 @@ const PayButton = ({ passphrase }: { passphrase?: string }) => { onBlur={onBlur} // notify when input is touched/blur value={value} /> - {feeAmount && ( + {feeReserveAmount && (
- Estimated Fee: {feeAmount} + Fee Reserve: {feeReserveAmount}
)} {/* {totalAmount && totalAmount >= 1 && ( diff --git a/components/wallet/receive-button.tsx b/components/wallet/receive-button.tsx index 98e4f5d..008e03a 100644 --- a/components/wallet/receive-button.tsx +++ b/components/wallet/receive-button.tsx @@ -73,12 +73,16 @@ const ReceiveButton = ({ passphrase }: { passphrase?: string }) => { setIsInvalidToken(false); try { const token = getDecodedToken(tokenString); - const tokenEntry = token.token; - const tokenMint = tokenEntry[0].mint; - const tokenProofs = tokenEntry[0].proofs; + const tokenMint = token.mint; + const tokenProofs = token.proofs; const wallet = new CashuWallet(new CashuMint(tokenMint)); - const spentProofs = await wallet?.checkProofsSpent(tokenProofs); - if (spentProofs.length === 0) { + let proofsStates = await wallet.checkProofsStates(tokenProofs); + const spentYs = new Set( + proofsStates + .filter((state) => state.state === "SPENT") + .map((state) => state.Y), + ); + if (spentYs.size === 0) { const uniqueProofs = tokenProofs.filter( (proof: Proof) => !tokens.some((token: Proof) => token.C === proof.C), ); @@ -165,10 +169,10 @@ const ReceiveButton = ({ passphrase }: { passphrase?: string }) => { rules={{ required: "A Cashu token string is required.", validate: (value) => - /^(web\+cashu:\/\/|cashu:\/\/|cashu:|cashuA)/.test( + /^(web\+cashu:\/\/|cashu:\/\/|cashu:|cashu[a-zA-Z])/.test( value, ) || - "The token must start with 'web+cashu://', 'cashu://', 'cashu:', or 'cashuA'.", + "The token must start with 'web+cashu://', 'cashu://', 'cashu:', or 'cashu' followed by a versioning letter.", }} render={({ field: { onChange, onBlur, value }, diff --git a/components/wallet/send-button.tsx b/components/wallet/send-button.tsx index c825577..567e985 100644 --- a/components/wallet/send-button.tsx +++ b/components/wallet/send-button.tsx @@ -31,6 +31,7 @@ import { CashuMint, CashuWallet, getEncodedToken, + MintKeyset, Proof, } from "@cashu/cashu-ts"; import { CashuWalletContext } from "../../utils/context/context"; @@ -81,25 +82,25 @@ const SendButton = ({ passphrase }: { passphrase?: string }) => { try { const mint = new CashuMint(mints[0]); const wallet = new CashuWallet(mint); - const mintKeySetResponse = await mint.getKeySets(); - const mintKeySetIds = mintKeySetResponse?.keysets; + const mintKeySetIds = await wallet.getKeySets(); const filteredProofs = tokens.filter( - (p: Proof) => mintKeySetIds?.includes(p.id), + (p: Proof) => + mintKeySetIds?.some((keysetId: MintKeyset) => keysetId.id === p.id), ); - const tokenToSend = await wallet.send(numSats, filteredProofs); + let sendTotal = (numSats / 10) * 10; + const { keep, send } = await wallet.send(sendTotal, filteredProofs, { + includeFees: true, + }); const encodedSendToken = getEncodedToken({ - token: [ - { - mint: mints[0], - proofs: tokenToSend.send, - }, - ], + mint: mints[0], + proofs: send, }); setShowTokenCard(true); setNewToken(encodedSendToken); - const changeProofs = tokenToSend?.returnChange; + const changeProofs = keep; const remainingProofs = tokens.filter( - (p: Proof) => !mintKeySetIds?.includes(p.id), + (p: Proof) => + mintKeySetIds?.some((keysetId: MintKeyset) => keysetId.id !== p.id), ); let proofArray; if (changeProofs.length >= 1 && changeProofs) { diff --git a/components/wallet/transactions.tsx b/components/wallet/transactions.tsx index 369f3a1..25b1900 100644 --- a/components/wallet/transactions.tsx +++ b/components/wallet/transactions.tsx @@ -27,7 +27,7 @@ const Transactions = () => { // Set up polling with setInterval const interval = setInterval(() => { fetchAndUpdateTransactions(); - }, 1000); // Polling every 1000 milliseconds (1 seconds) + }, 2100); // Polling every 2100 milliseconds (2.1 seconds) // Clean up on component unmount return () => clearInterval(interval); }, []); diff --git a/package-lock.json b/package-lock.json index a4ee92c..fac397b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "dependencies": { "@braintree/sanitize-url": "^7.1.0", - "@cashu/cashu-ts": "^0.8.1", + "@cashu/cashu-ts": "^2.1.0", "@getalby/lightning-tools": "^5.0.1", "@heroicons/react": "^2.1.1", "@itseasy21/react-elastic-carousel": "^0.12.3", @@ -1831,16 +1831,168 @@ "integrity": "sha512-o+UlMLt49RvtCASlOMW0AkHnabN9wR9rwCCherxO0yG4Npy34GkvrAqdXQvrhNs+jh+gkK8gB8Lf05qL/O7KWg==" }, "node_modules/@cashu/cashu-ts": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cashu/cashu-ts/-/cashu-ts-0.8.1.tgz", - "integrity": "sha512-36+e5jJagwhzxxSW+WcVnjuiQQIp/rCabqURyEIce//+L3LuQmQ2xC32WskPUmxSFKhN8Nnngsma/pH6XzTWkw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@cashu/cashu-ts/-/cashu-ts-2.1.0.tgz", + "integrity": "sha512-qFfFz1dx9keJxumjk5FyTvI1j0Yp/P5LXDy0cGO4Xlp3WYKOI1nykNOTPd+bTY9vSkvIM+xuXRer9BtQxqHtwA==", "license": "MIT", "dependencies": { - "@gandlaf21/bolt11-decode": "^3.0.6", - "@noble/curves": "^1.0.0", + "@cashu/crypto": "^0.3.4", + "@noble/curves": "^1.3.0", + "@noble/hashes": "^1.3.3", + "@scure/bip32": "^1.3.3", "buffer": "^6.0.3" } }, + "node_modules/@cashu/cashu-ts/node_modules/@noble/curves": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.7.0.tgz", + "integrity": "sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.6.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@cashu/cashu-ts/node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.0.tgz", + "integrity": "sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@cashu/cashu-ts/node_modules/@noble/hashes": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.1.tgz", + "integrity": "sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@cashu/cashu-ts/node_modules/@scure/base": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.1.tgz", + "integrity": "sha512-DGmGtC8Tt63J5GfHgfl5CuAXh96VF/LD8K9Hr/Gv0J2lAoRGlPOMpqMpMbCTOoOJMZCk2Xt+DskdDyn6dEFdzQ==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@cashu/cashu-ts/node_modules/@scure/bip32": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.6.0.tgz", + "integrity": "sha512-82q1QfklrUUdXJzjuRU7iG7D7XiFx5PHYVS0+oeNKhyDLT7WPqs6pBcM2W5ZdwOwKCwoE1Vy1se+DHjcXwCYnA==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.7.0", + "@noble/hashes": "~1.6.0", + "@scure/base": "~1.2.1" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@cashu/crypto": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@cashu/crypto/-/crypto-0.3.4.tgz", + "integrity": "sha512-mfv1Pj4iL1PXzUj9NKIJbmncCLMqYfnEDqh/OPxAX0nNBt6BOnVJJLjLWFlQeYxlnEfWABSNkrqPje1t5zcyhA==", + "license": "MIT", + "dependencies": { + "@noble/curves": "^1.6.0", + "@noble/hashes": "^1.5.0", + "@scure/bip32": "^1.5.0", + "@scure/bip39": "^1.4.0", + "buffer": "^6.0.3" + } + }, + "node_modules/@cashu/crypto/node_modules/@noble/curves": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.7.0.tgz", + "integrity": "sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.6.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@cashu/crypto/node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.0.tgz", + "integrity": "sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@cashu/crypto/node_modules/@noble/hashes": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.1.tgz", + "integrity": "sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@cashu/crypto/node_modules/@scure/base": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.1.tgz", + "integrity": "sha512-DGmGtC8Tt63J5GfHgfl5CuAXh96VF/LD8K9Hr/Gv0J2lAoRGlPOMpqMpMbCTOoOJMZCk2Xt+DskdDyn6dEFdzQ==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@cashu/crypto/node_modules/@scure/bip32": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.6.0.tgz", + "integrity": "sha512-82q1QfklrUUdXJzjuRU7iG7D7XiFx5PHYVS0+oeNKhyDLT7WPqs6pBcM2W5ZdwOwKCwoE1Vy1se+DHjcXwCYnA==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.7.0", + "@noble/hashes": "~1.6.0", + "@scure/base": "~1.2.1" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@cashu/crypto/node_modules/@scure/bip39": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.5.0.tgz", + "integrity": "sha512-Dop+ASYhnrwm9+HA/HwXg7j2ZqM6yk2fyLWb5znexjctFY3+E+eU8cIWI0Pql0Qx4hPZCijlGq4OL71g+Uz30A==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.6.0", + "@scure/base": "~1.2.1" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -2034,16 +2186,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@gandlaf21/bolt11-decode": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@gandlaf21/bolt11-decode/-/bolt11-decode-3.0.6.tgz", - "integrity": "sha512-KUcAK2b9or8J47hzNTM2A+xdU0jCGIL4oC4TDyUlRYMfS5dBVOh4ywg9r3TZD8C/eVx7r14Hp4F79CSDjyCWTQ==", - "dependencies": { - "bech32": "^1.1.2", - "bn.js": "^4.11.8", - "buffer": "^6.0.3" - } - }, "node_modules/@getalby/lightning-tools": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@getalby/lightning-tools/-/lightning-tools-5.0.1.tgz", @@ -5836,12 +5978,8 @@ "type": "consulting", "url": "https://feross.org/support" } - ] - }, - "node_modules/bech32": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", - "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" + ], + "license": "MIT" }, "node_modules/big.js": { "version": "5.2.2", @@ -5864,11 +6002,6 @@ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, - "node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -5938,6 +6071,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -7948,7 +8082,8 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "BSD-3-Clause" }, "node_modules/ignore": { "version": "5.2.4", @@ -8951,6 +9086,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -13422,13 +13558,110 @@ "integrity": "sha512-o+UlMLt49RvtCASlOMW0AkHnabN9wR9rwCCherxO0yG4Npy34GkvrAqdXQvrhNs+jh+gkK8gB8Lf05qL/O7KWg==" }, "@cashu/cashu-ts": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cashu/cashu-ts/-/cashu-ts-0.8.1.tgz", - "integrity": "sha512-36+e5jJagwhzxxSW+WcVnjuiQQIp/rCabqURyEIce//+L3LuQmQ2xC32WskPUmxSFKhN8Nnngsma/pH6XzTWkw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@cashu/cashu-ts/-/cashu-ts-2.1.0.tgz", + "integrity": "sha512-qFfFz1dx9keJxumjk5FyTvI1j0Yp/P5LXDy0cGO4Xlp3WYKOI1nykNOTPd+bTY9vSkvIM+xuXRer9BtQxqHtwA==", "requires": { - "@gandlaf21/bolt11-decode": "^3.0.6", - "@noble/curves": "^1.0.0", + "@cashu/crypto": "^0.3.4", + "@noble/curves": "^1.3.0", + "@noble/hashes": "^1.3.3", + "@scure/bip32": "^1.3.3", "buffer": "^6.0.3" + }, + "dependencies": { + "@noble/curves": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.7.0.tgz", + "integrity": "sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw==", + "requires": { + "@noble/hashes": "1.6.0" + }, + "dependencies": { + "@noble/hashes": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.0.tgz", + "integrity": "sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==" + } + } + }, + "@noble/hashes": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.1.tgz", + "integrity": "sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w==" + }, + "@scure/base": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.1.tgz", + "integrity": "sha512-DGmGtC8Tt63J5GfHgfl5CuAXh96VF/LD8K9Hr/Gv0J2lAoRGlPOMpqMpMbCTOoOJMZCk2Xt+DskdDyn6dEFdzQ==" + }, + "@scure/bip32": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.6.0.tgz", + "integrity": "sha512-82q1QfklrUUdXJzjuRU7iG7D7XiFx5PHYVS0+oeNKhyDLT7WPqs6pBcM2W5ZdwOwKCwoE1Vy1se+DHjcXwCYnA==", + "requires": { + "@noble/curves": "~1.7.0", + "@noble/hashes": "~1.6.0", + "@scure/base": "~1.2.1" + } + } + } + }, + "@cashu/crypto": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@cashu/crypto/-/crypto-0.3.4.tgz", + "integrity": "sha512-mfv1Pj4iL1PXzUj9NKIJbmncCLMqYfnEDqh/OPxAX0nNBt6BOnVJJLjLWFlQeYxlnEfWABSNkrqPje1t5zcyhA==", + "requires": { + "@noble/curves": "^1.6.0", + "@noble/hashes": "^1.5.0", + "@scure/bip32": "^1.5.0", + "@scure/bip39": "^1.4.0", + "buffer": "^6.0.3" + }, + "dependencies": { + "@noble/curves": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.7.0.tgz", + "integrity": "sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw==", + "requires": { + "@noble/hashes": "1.6.0" + }, + "dependencies": { + "@noble/hashes": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.0.tgz", + "integrity": "sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==" + } + } + }, + "@noble/hashes": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.1.tgz", + "integrity": "sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w==" + }, + "@scure/base": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.1.tgz", + "integrity": "sha512-DGmGtC8Tt63J5GfHgfl5CuAXh96VF/LD8K9Hr/Gv0J2lAoRGlPOMpqMpMbCTOoOJMZCk2Xt+DskdDyn6dEFdzQ==" + }, + "@scure/bip32": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.6.0.tgz", + "integrity": "sha512-82q1QfklrUUdXJzjuRU7iG7D7XiFx5PHYVS0+oeNKhyDLT7WPqs6pBcM2W5ZdwOwKCwoE1Vy1se+DHjcXwCYnA==", + "requires": { + "@noble/curves": "~1.7.0", + "@noble/hashes": "~1.6.0", + "@scure/base": "~1.2.1" + } + }, + "@scure/bip39": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.5.0.tgz", + "integrity": "sha512-Dop+ASYhnrwm9+HA/HwXg7j2ZqM6yk2fyLWb5znexjctFY3+E+eU8cIWI0Pql0Qx4hPZCijlGq4OL71g+Uz30A==", + "requires": { + "@noble/hashes": "~1.6.0", + "@scure/base": "~1.2.1" + } + } } }, "@cspotcode/source-map-support": { @@ -13597,16 +13830,6 @@ "tslib": "^2.4.0" } }, - "@gandlaf21/bolt11-decode": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@gandlaf21/bolt11-decode/-/bolt11-decode-3.0.6.tgz", - "integrity": "sha512-KUcAK2b9or8J47hzNTM2A+xdU0jCGIL4oC4TDyUlRYMfS5dBVOh4ywg9r3TZD8C/eVx7r14Hp4F79CSDjyCWTQ==", - "requires": { - "bech32": "^1.1.2", - "bn.js": "^4.11.8", - "buffer": "^6.0.3" - } - }, "@getalby/lightning-tools": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@getalby/lightning-tools/-/lightning-tools-5.0.1.tgz", @@ -16469,11 +16692,6 @@ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, - "bech32": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", - "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" - }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -16489,11 +16707,6 @@ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", diff --git a/package.json b/package.json index 47e7b27..a86e1ff 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@braintree/sanitize-url": "^7.1.0", - "@cashu/cashu-ts": "^0.8.1", + "@cashu/cashu-ts": "^2.1.0", "@getalby/lightning-tools": "^5.0.1", "@heroicons/react": "^2.1.1", "@itseasy21/react-elastic-carousel": "^0.12.3", diff --git a/pages/api/cashu/request-mint.ts b/pages/api/cashu/request-mint.ts index 701f687..21e5eb0 100644 --- a/pages/api/cashu/request-mint.ts +++ b/pages/api/cashu/request-mint.ts @@ -14,7 +14,7 @@ const requestMint = async (req: NextApiRequest, res: NextApiResponse) => { const wallet = new CashuWallet(new CashuMint(mintUrl)); - const { pr, hash } = await wallet.requestMint(total); + const { request, quote } = await wallet.createMintQuote(total); const id = uuid(); @@ -23,9 +23,9 @@ const requestMint = async (req: NextApiRequest, res: NextApiResponse) => { date_time: DateTime.now().toUTC().toSQL(), total, currency, - hash, + hash: quote, }); - return res.status(200).json({ pr, hash, id }); + return res.status(200).json({ pr: request, hash: quote, id }); } catch (error) { console.error(error); return res.status(500).json({ error }); diff --git a/pages/api/metrics/post-invoice-status.ts b/pages/api/metrics/post-invoice-status.ts index b068296..58b759e 100644 --- a/pages/api/metrics/post-invoice-status.ts +++ b/pages/api/metrics/post-invoice-status.ts @@ -42,7 +42,7 @@ const UpdateInvoice = async (req: NextApiRequest, res: NextApiResponse) => { } = response[0]; try { - await wallet.requestTokens(total, hash); + await wallet.mintProofs(total, hash); } catch (error: any) { console.error(error); if (error.message.includes("quote already issued")) { diff --git a/pages/api/nostr/fetch-service.ts b/pages/api/nostr/fetch-service.ts index c954063..e1b012d 100644 --- a/pages/api/nostr/fetch-service.ts +++ b/pages/api/nostr/fetch-service.ts @@ -26,6 +26,7 @@ import { } from "@/components/utility/product-parser-functions"; import { calculateWeightedScore } from "@/components/utility/review-parser-functions"; import { DeleteEvent } from "../../../pages/api/nostr/crud-service"; +import { hashToCurve } from "@cashu/crypto/modules/common"; function getUniqueProofs(proofs: Proof[]): Proof[] { const uniqueProofs = new Set(); @@ -1048,6 +1049,7 @@ export const fetchCashuWallet = async ( }> => { return new Promise(async function (resolve, reject) { const { userPubkey, signInMethod, tokens } = getLocalStorageData(); + const enc = new TextEncoder(); try { let mostRecentWalletEvent: NostrEvent[] = []; let proofEvents: any[] = []; @@ -1201,15 +1203,21 @@ export const fetchCashuWallet = async ( let wallet = new CashuWallet( new CashuMint(cashuWalletEventContent?.mint), ); - let spentProofs = await wallet?.checkProofsSpent( + const Ys = cashuWalletEventContent?.proofs.map((p: Proof) => + hashToCurve(enc.encode(p.secret)).toHex(true), + ); + let proofsStates = await wallet?.checkProofsStates( cashuWalletEventContent?.proofs, ); - if ( - spentProofs && - spentProofs.length > 0 && - JSON.stringify(spentProofs) === - JSON.stringify(cashuWalletEventContent?.proofs) - ) { + const spentYs = new Set( + proofsStates + .filter((state) => state.state === "SPENT") + .map((state) => state.Y), + ); + const allYsMatch = + Ys.length === spentYs.size && + Ys.every((y: string) => spentYs.has(y)); + if (proofsStates && proofsStates.length > 0 && allYsMatch) { await DeleteEvent([event.id], passphrase); } else if (cashuWalletEventContent.proofs) { let allProofs = [ @@ -1235,11 +1243,19 @@ export const fetchCashuWallet = async ( try { let wallet = new CashuWallet(new CashuMint(mint)); if (cashuProofs.length > 0) { - let spentProofs = - await wallet?.checkProofsSpent(cashuProofs); - if (spentProofs.length > 0) { + const Ys = cashuProofs.map((p: Proof) => + hashToCurve(enc.encode(p.secret)).toHex(true), + ); + let proofsStates = + await wallet?.checkProofsStates(cashuProofs); + const spentYs = new Set( + proofsStates + .filter((state) => state.state === "SPENT") + .map((state) => state.Y), + ); + if (spentYs.size > 0) { cashuProofs = cashuProofs.filter( - (proof) => !spentProofs.includes(proof), + (proof, index) => !spentYs.has(Ys[index]), ); } } diff --git a/pages/wallet/index.tsx b/pages/wallet/index.tsx index b5f84b5..e9c2557 100644 --- a/pages/wallet/index.tsx +++ b/pages/wallet/index.tsx @@ -9,7 +9,7 @@ import ReceiveButton from "../../components/wallet/receive-button"; import SendButton from "../../components/wallet/send-button"; import PayButton from "../../components/wallet/pay-button"; import Transactions from "../../components/wallet/transactions"; -import { CashuMint, Proof } from "@cashu/cashu-ts"; +import { CashuMint, CashuWallet, MintKeyset, Proof } from "@cashu/cashu-ts"; import RequestPassphraseModal from "@/components/utility-components/request-passphrase-modal"; const Wallet = () => { @@ -19,7 +19,8 @@ const Wallet = () => { const [totalBalance, setTotalBalance] = useState(0); const [walletBalance, setWalletBalance] = useState(0); const [mint, setMint] = useState(""); - const [mintKeySetIds, setMintKeySetIds] = useState([]); + const [wallet, setWallet] = useState(); + const [mintKeySetIds, setMintKeySetIds] = useState([]); const router = useRouter(); const { signInMethod, mints, tokens } = getLocalStorageData(); @@ -30,20 +31,24 @@ const Wallet = () => { } }, [signInMethod, passphrase]); + useEffect(() => { + let currentMint = new CashuMint(mints[0]); + setMint(mints[0]); + let cashuWallet = new CashuWallet(currentMint); + setWallet(cashuWallet); + }, [mints]); + useEffect(() => { const fetchLocalKeySet = async () => { - if (mints && mints.length > 0) { - const currentMint = new CashuMint(mints[0]); - setMint(mints[0]); - const mintKeySetResponse = await currentMint.getKeySets(); - const mintKeySet = mintKeySetResponse?.keysets; - if (mintKeySet) { - setMintKeySetIds(mintKeySet); + if (wallet) { + const mintKeySetIdsArray = await wallet.getKeySets(); + if (mintKeySetIdsArray) { + setMintKeySetIds(mintKeySetIdsArray); } } }; fetchLocalKeySet(); - }, [mints]); + }, [wallet]); useEffect(() => { // Function to fetch and update balances @@ -57,7 +62,8 @@ const Wallet = () => { } if (mints && tokens && mintKeySetIds) { const filteredProofs = tokens.filter( - (p: Proof) => mintKeySetIds?.includes(p.id), + (p: Proof) => + mintKeySetIds?.some((keysetId: MintKeyset) => keysetId.id === p.id), ); let walletTotal = filteredProofs && filteredProofs.length >= 1 @@ -71,7 +77,7 @@ const Wallet = () => { // Set up polling with setInterval const interval = setInterval(() => { fetchAndUpdateBalances(); - }, 1000); // Polling every 1000 milliseconds (1 seconds) + }, 2100); // Polling every 2100 milliseconds (2.1 seconds) // Clean up on component unmount return () => clearInterval(interval); }, [mintKeySetIds, mints, tokens]);