From cd0f2956af7aa57b56dc03914a2c2574e431f2d4 Mon Sep 17 00:00:00 2001 From: Ishaan Lahoti <113637618+Quaakee@users.noreply.github.com> Date: Wed, 8 Apr 2026 07:38:25 -0500 Subject: [PATCH 01/22] chore: link message-box-client feat/request-payment branch Temporarily using npm link to local clone of message-box-client with payment request methods. Will switch to published version once bsv-blockchain/message-box-client#38 is merged. --- package-lock.json | 91 ++--------------------------------------------- package.json | 1 - 2 files changed, 3 insertions(+), 89 deletions(-) diff --git a/package-lock.json b/package-lock.json index 53633ae..23147b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,6 @@ "@bsv/btms-permission-module": "^1.0.1", "@bsv/btms-permission-module-ui": "^1.0.0", "@bsv/identity-react": "^1.1.14", - "@bsv/message-box-client": "^2.0.4", "@bsv/sdk": "^2.0.7", "@bsv/uhrp-react": "^1.0.6", "@bsv/wallet-toolbox": "^2.1.5", @@ -678,16 +677,6 @@ "node": ">= 0.6" } }, - "node_modules/@bsv/authsocket-client": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@bsv/authsocket-client/-/authsocket-client-2.0.2.tgz", - "integrity": "sha512-da7ON4zqdShM9QFYxQzcuJoVfau1sm1dwrpsYtU3JowwDXMYYzcbvwvWzYBJS1380ijn1T50n1IAJlAaLqVNgg==", - "license": "SEE LICENSE IN LICENSE.txt", - "dependencies": { - "@bsv/sdk": "^2.0.4", - "socket.io-client": "^4.8.1" - } - }, "node_modules/@bsv/btms": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@bsv/btms/-/btms-1.0.1.tgz", @@ -749,16 +738,6 @@ "undici-types": "~6.21.0" } }, - "node_modules/@bsv/message-box-client": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@bsv/message-box-client/-/message-box-client-2.0.4.tgz", - "integrity": "sha512-XvnNrchW6gZB1U3pskIld8rie8ds2ifxrOhfO4Ot+/kHaMm8osYky7KK6cxCnIdPtOLc8G1yiV41M13p+BzY/A==", - "license": "SEE LICENSE IN LICENSE.txt", - "dependencies": { - "@bsv/authsocket-client": "^2.0.2", - "@bsv/sdk": "^2.0.4" - } - }, "node_modules/@bsv/payment-express-middleware": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@bsv/payment-express-middleware/-/payment-express-middleware-2.0.1.tgz", @@ -1076,9 +1055,9 @@ } }, "node_modules/@bsv/sdk": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@bsv/sdk/-/sdk-2.0.7.tgz", - "integrity": "sha512-zh3DtbGIAeojf/TYlhVTIOIFeUWboq7jyWSEmiPmKxp8MnprZM+jino1Lm0KbjEnwn56pAPCZt8eb+5tTWtHaA==", + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@bsv/sdk/-/sdk-2.0.13.tgz", + "integrity": "sha512-VV25sV6oB1Piu7tTEMta+B29IRcq7Xpj1ohuk8RZRT656onop1YbA3oTnYf6stleWnbvsZeOciwH7Xppvs1QWw==", "license": "SEE LICENSE IN LICENSE.txt" }, "node_modules/@bsv/uhrp-react": { @@ -3475,12 +3454,6 @@ "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "node_modules/@socket.io/component-emitter": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", - "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", - "license": "MIT" - }, "node_modules/@szmarczak/http-timer": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", @@ -6109,28 +6082,6 @@ "once": "^1.4.0" } }, - "node_modules/engine.io-client": { - "version": "6.6.4", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.4.tgz", - "integrity": "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==", - "license": "MIT", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.4.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.18.3", - "xmlhttprequest-ssl": "~2.1.1" - } - }, - "node_modules/engine.io-parser": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", - "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -10143,34 +10094,6 @@ "npm": ">= 3.0.0" } }, - "node_modules/socket.io-client": { - "version": "4.8.3", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz", - "integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==", - "license": "MIT", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.4.1", - "engine.io-client": "~6.6.1", - "socket.io-parser": "~4.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/socket.io-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz", - "integrity": "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==", - "license": "MIT", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.4.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/socks": { "version": "2.8.7", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", @@ -11248,14 +11171,6 @@ "node": ">=8.0" } }, - "node_modules/xmlhttprequest-ssl": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", - "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index e4138d0..2ae5c72 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ "@bsv/btms-permission-module": "^1.0.1", "@bsv/btms-permission-module-ui": "^1.0.0", "@bsv/identity-react": "^1.1.14", - "@bsv/message-box-client": "^2.0.4", "@bsv/sdk": "^2.0.7", "@bsv/uhrp-react": "^1.0.6", "@bsv/wallet-toolbox": "^2.1.5", From 600f054e84b408431fee9bcf1f1d3ed58b2e9974 Mon Sep 17 00:00:00 2001 From: Ishaan Lahoti <113637618+Quaakee@users.noreply.github.com> Date: Wed, 8 Apr 2026 07:43:29 -0500 Subject: [PATCH 02/22] feat: add RequestPaymentForm component with outgoing request tracker Co-Authored-By: Claude Sonnet 4.6 --- .../Dashboard/Payments/RequestPaymentForm.tsx | 476 ++++++++++++++++++ 1 file changed, 476 insertions(+) create mode 100644 src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx diff --git a/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx b/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx new file mode 100644 index 0000000..fe20f14 --- /dev/null +++ b/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx @@ -0,0 +1,476 @@ +// src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx +import React, { useCallback, useContext, useEffect, useRef, useState } from 'react' +import { + Autocomplete, + Avatar, + Box, + Button, + Chip, + CircularProgress, + FormControl, + InputAdornment, + InputLabel, + MenuItem, + Paper, + Select, + Stack, + TextField, + Typography, +} from '@mui/material' +import { PublicKey, WalletInterface } from '@bsv/sdk' +import { WalletContext } from '../../../WalletContext' +import { CurrencyConverter } from '@bsv/amountinator' +import { toast } from 'react-toastify' +import useAsyncEffect from 'use-async-effect' +import { useIdentitySearch } from '@bsv/identity-react' +import { PaymentRequestResponse } from '@bsv/message-box-client' + +/* ------------------------------------------------------------------ */ +/* Types */ +/* ------------------------------------------------------------------ */ + +export type OutgoingRequest = { + requestId: string + recipient: string + recipientLabel: string + amount: number + description: string + expiresAt: number + status: 'pending' | 'paid' | 'declined' | 'expired' | 'cancelled' + amountPaid?: number + note?: string +} + +type Props = { + wallet: WalletInterface + onRequestSent?: () => void +} + +type IdentityOption = { + identityKey: string + name?: string + avatarURL?: string + badgeLabel?: string +} + +/* ------------------------------------------------------------------ */ +/* Expiry helpers */ +/* ------------------------------------------------------------------ */ + +const EXPIRY_OPTIONS = [ + { label: '1 hour', ms: 60 * 60 * 1000 }, + { label: '1 day', ms: 24 * 60 * 60 * 1000 }, + { label: '3 days', ms: 3 * 24 * 60 * 60 * 1000 }, + { label: '7 days', ms: 7 * 24 * 60 * 60 * 1000 }, + { label: '30 days', ms: 30 * 24 * 60 * 60 * 1000 }, +] as const + +function getInitials(name: string, identityKey: string): string { + if (!name || name.trim() === '') return identityKey.slice(0, 2).toUpperCase() + const words = name.trim().split(/\s+/) + if (words.length >= 2) return (words[0][0] + words[words.length - 1][0]).toUpperCase() + return name.slice(0, 2).toUpperCase() +} + +function statusColor(status: OutgoingRequest['status']): 'success' | 'info' | 'error' | 'default' { + switch (status) { + case 'pending': return 'success' + case 'paid': return 'info' + case 'declined': return 'error' + default: return 'default' + } +} + +/* ------------------------------------------------------------------ */ +/* Component */ +/* ------------------------------------------------------------------ */ + +export default function RequestPaymentForm({ wallet, onRequestSent }: Props) { + const { managers, adminOriginator, peerPayClient, messageBoxUrl } = useContext(WalletContext) + + // Form state + const [recipient, setRecipient] = useState('') + const [publicKeyInput, setPublicKeyInput] = useState('') + const [amount, setAmount] = useState(0) + const [amountInput, setAmountInput] = useState('') + const [description, setDescription] = useState('') + const [expiryMs, setExpiryMs] = useState(EXPIRY_OPTIONS[2].ms) // 3 days default + const [sending, setSending] = useState(false) + const [currencySymbol, setCurrencySymbol] = useState('$') + + // Outgoing tracker + const [outgoing, setOutgoing] = useState([]) + const [cancellingId, setCancellingId] = useState(null) + const pollRef = useRef | null>(null) + + const currencyConverter = new CurrencyConverter(undefined, managers?.settingsManager as any) + + const identitySearch = useIdentitySearch({ + originator: adminOriginator, + wallet, + onIdentitySelected: (identity) => { + if (identity) setRecipient(identity.identityKey) + } + }) + + useAsyncEffect(async () => { + await currencyConverter.initialize() + setCurrencySymbol(currencyConverter.getCurrencySymbol()) + }, []) + + // Poll for responses every 15 seconds when there are pending requests + const pollResponses = useCallback(async () => { + if (!peerPayClient || !messageBoxUrl) return + const hasPending = outgoing.some(r => r.status === 'pending' && r.expiresAt > Date.now()) + if (!hasPending) return + try { + const responses: PaymentRequestResponse[] = await peerPayClient.listPaymentRequestResponses(messageBoxUrl) + if (responses.length === 0) return + setOutgoing(prev => prev.map(req => { + const match = responses.find(r => r.requestId === req.requestId) + if (!match) return req + return { + ...req, + status: match.status === 'paid' ? 'paid' : 'declined', + amountPaid: match.amountPaid, + note: match.note, + } + })) + } catch { + // Silently ignore poll errors + } + }, [peerPayClient, messageBoxUrl, outgoing]) + + // Mark expired requests + useEffect(() => { + const tick = setInterval(() => { + setOutgoing(prev => + prev.map(r => + r.status === 'pending' && r.expiresAt < Date.now() + ? { ...r, status: 'expired' } + : r + ) + ) + }, 30_000) + return () => clearInterval(tick) + }, []) + + // Set up polling interval when pending requests exist + useEffect(() => { + const hasPending = outgoing.some(r => r.status === 'pending') + if (hasPending) { + if (!pollRef.current) { + pollRef.current = setInterval(pollResponses, 15_000) + } + } else { + if (pollRef.current) { + clearInterval(pollRef.current) + pollRef.current = null + } + } + return () => { + if (pollRef.current) { + clearInterval(pollRef.current) + pollRef.current = null + } + } + }, [outgoing, pollResponses]) + + const handleAmountChange = useCallback(async (e: React.ChangeEvent) => { + const val = e.target.value.replace(/[^0-9.]/g, '') + setAmountInput(val) + const sats = await currencyConverter.convertToSatoshis(parseFloat(val) || 0) + setAmount(sats ?? 0) + }, []) + + const canRequest = + recipient.trim().length > 0 && + amount > 0 && + description.trim().length > 0 && + !sending + + const handleSubmit = async () => { + if (!canRequest || !peerPayClient) return + try { + setSending(true) + const expiresAt = Date.now() + expiryMs + const { requestId } = await peerPayClient.requestPayment( + { recipient: recipient.trim(), amount, description: description.trim(), expiresAt }, + messageBoxUrl || undefined + ) + const label = + identitySearch.selectedIdentity + ? (identitySearch.selectedIdentity as IdentityOption).name || + (identitySearch.selectedIdentity as IdentityOption).identityKey.slice(0, 14) + : recipient.slice(0, 14) + setOutgoing(prev => [ + { + requestId, + recipient: recipient.trim(), + recipientLabel: label, + amount, + description: description.trim(), + expiresAt, + status: 'pending', + }, + ...prev, + ]) + toast.success('Payment request sent!') + setAmountInput('') + setAmount(0) + setDescription('') + setRecipient('') + setPublicKeyInput('') + identitySearch.handleSelect(null, null) + onRequestSent?.() + } catch (e) { + toast.error('Failed to send payment request') + alert((e as Error)?.message ?? 'Failed to send payment request') + } finally { + setSending(false) + } + } + + const handleCancel = async (req: OutgoingRequest) => { + if (!peerPayClient) return + try { + setCancellingId(req.requestId) + await peerPayClient.cancelPaymentRequest( + { recipient: req.recipient, requestId: req.requestId }, + messageBoxUrl || undefined + ) + setOutgoing(prev => + prev.map(r => r.requestId === req.requestId ? { ...r, status: 'cancelled' } : r) + ) + toast.success('Request cancelled') + } catch (e) { + toast.error((e as Error)?.message ?? 'Failed to cancel request') + } finally { + setCancellingId(null) + } + } + + return ( + + {/* ---- Form ---- */} + + + Request Payment + + + {/* Recipient autocomplete */} + { + identitySearch.handleSelect(event, value as any) + if (value && typeof value !== 'string') { + setRecipient((value as IdentityOption).identityKey) + setPublicKeyInput((value as IdentityOption).identityKey) + } else { + setRecipient('') + setPublicKeyInput('') + } + }} + filterOptions={(options: IdentityOption[]) => + options.filter( + (identity, index, array) => + array.findIndex(i => i.identityKey === identity.identityKey) === index + ) + } + getOptionLabel={(option) => { + if (typeof option === 'string') return option + return (option as IdentityOption).name || (option as IdentityOption).identityKey.slice(0, 16) + }} + isOptionEqualToValue={(option, value) => { + if (typeof option === 'string' || typeof value === 'string') return false + return (option as IdentityOption).identityKey === (value as IdentityOption).identityKey + }} + renderInput={(params) => ( + + {identitySearch.isLoading ? : null} + {params.InputProps.endAdornment} + + ) + }} + /> + )} + renderOption={(props, option) => { + if (typeof option === 'string') return null + const opt = option as IdentityOption + const { key, ...otherProps } = props as any + return ( +
  • + + {opt.avatarURL ? ( + + ) : ( + + {getInitials(opt.name || '', opt.identityKey)} + + )} + + {opt.name || 'Unknown'} + + {opt.identityKey.slice(0, 20)}... + + + {opt.badgeLabel && } + +
  • + ) + }} + noOptionsText={identitySearch.inputValue ? 'No identities found' : 'Start typing to search'} + fullWidth + /> + + {/* Direct public key fallback */} + { + const val = e.target.value.trim() + setPublicKeyInput(val) + if (val) { + try { + PublicKey.fromString(val) + setRecipient(val) + identitySearch.handleSelect(null, null) + } catch { + setRecipient('') + } + } else { + setRecipient('') + } + }} + disabled={!!identitySearch.selectedIdentity} + error={Boolean(publicKeyInput && !recipient && !identitySearch.selectedIdentity)} + helperText={publicKeyInput && !recipient && !identitySearch.selectedIdentity ? 'Invalid public key' : ''} + /> + + {/* Amount */} + {currencySymbol} + }} + /> + + {/* Description */} + setDescription(e.target.value)} + required + /> + + {/* Expiry */} + + Expires In + + + + + + +
    +
    + + {/* ---- Outgoing tracker ---- */} + {outgoing.length > 0 && ( + + + Sent Requests + + + {outgoing.map(req => ( + + + + + To: {req.recipientLabel}… + + + {req.amount.toLocaleString()} sats + + + {req.description} + + {req.note && ( + + Note: {req.note} + + )} + {req.status === 'paid' && req.amountPaid != null && ( + + Paid: {req.amountPaid.toLocaleString()} sats + + )} + + + + {req.status === 'pending' && ( + + )} + + + + ))} + + + )} +
    + ) +} From 9de41a16650dcc055886658b6fcff3030eab9e1d Mon Sep 17 00:00:00 2001 From: Ishaan Lahoti <113637618+Quaakee@users.noreply.github.com> Date: Wed, 8 Apr 2026 07:43:33 -0500 Subject: [PATCH 03/22] feat: add IncomingRequestList component with pay/decline actions and request settings Co-Authored-By: Claude Sonnet 4.6 --- .../Payments/IncomingRequestList.tsx | 449 ++++++++++++++++++ 1 file changed, 449 insertions(+) create mode 100644 src/lib/pages/Dashboard/Payments/IncomingRequestList.tsx diff --git a/src/lib/pages/Dashboard/Payments/IncomingRequestList.tsx b/src/lib/pages/Dashboard/Payments/IncomingRequestList.tsx new file mode 100644 index 0000000..c2f54e0 --- /dev/null +++ b/src/lib/pages/Dashboard/Payments/IncomingRequestList.tsx @@ -0,0 +1,449 @@ +// src/lib/pages/Dashboard/Payments/IncomingRequestList.tsx +import React, { useCallback, useContext, useState } from 'react' +import { + Alert, + Avatar, + Box, + Button, + Chip, + CircularProgress, + Collapse, + Divider, + List, + ListItem, + ListItemSecondaryAction, + ListItemText, + Paper, + Stack, + TextField, + Typography, +} from '@mui/material' +import { WalletInterface } from '@bsv/sdk' +import { WalletContext } from '../../../WalletContext' +import { IncomingPaymentRequest } from '@bsv/message-box-client' +import { toast } from 'react-toastify' + +/* ------------------------------------------------------------------ */ +/* Helpers */ +/* ------------------------------------------------------------------ */ + +function getInitials(identityKey: string): string { + return identityKey.slice(0, 2).toUpperCase() +} + +function truncateKey(key: string, chars = 14): string { + if (key.length <= chars + 3) return key + return `${key.slice(0, chars)}…` +} + +function formatTimeRemaining(expiresAt: number): string { + const diff = expiresAt - Date.now() + if (diff <= 0) return 'Expired' + const hours = Math.floor(diff / (1000 * 60 * 60)) + const days = Math.floor(hours / 24) + if (days > 0) return `${days}d ${hours % 24}h remaining` + const mins = Math.floor(diff / (1000 * 60)) + if (hours > 0) return `${hours}h ${mins % 60}m remaining` + return `${mins}m remaining` +} + +/* ------------------------------------------------------------------ */ +/* Types */ +/* ------------------------------------------------------------------ */ + +type Props = { + requests: IncomingPaymentRequest[] + onRefresh: () => void + wallet: WalletInterface +} + +type LoadingMap = Record + +/* ------------------------------------------------------------------ */ +/* Component */ +/* ------------------------------------------------------------------ */ + +export default function IncomingRequestList({ requests, onRefresh }: Props) { + const { peerPayClient, messageBoxUrl } = useContext(WalletContext) + + // Per-card state + const [payAmounts, setPayAmounts] = useState>({}) + const [notes, setNotes] = useState>({}) + const [paying, setPaying] = useState({}) + const [declining, setDeclining] = useState({}) + + // Settings panel + const [settingsOpen, setSettingsOpen] = useState(false) + const [settingsLoaded, setSettingsLoaded] = useState(false) + const [settingsLoading, setSettingsLoading] = useState(false) + const [permissions, setPermissions] = useState>([]) + const [newAllowKey, setNewAllowKey] = useState('') + const [allowError, setAllowError] = useState('') + const [minAmount, setMinAmount] = useState(() => + localStorage.getItem('payReq_minAmount') ?? '1000' + ) + const [maxAmount, setMaxAmount] = useState(() => + localStorage.getItem('payReq_maxAmount') ?? '10000000' + ) + const [limitsSaved, setLimitsSaved] = useState(false) + + /* ---------------------------------------------------------------- */ + /* Settings helpers */ + /* ---------------------------------------------------------------- */ + + const loadPermissions = useCallback(async () => { + if (!peerPayClient) return + setSettingsLoading(true) + try { + const list = await peerPayClient.listPaymentRequestPermissions() + setPermissions(list) + setSettingsLoaded(true) + } catch (e) { + toast.error((e as Error)?.message ?? 'Failed to load permissions') + } finally { + setSettingsLoading(false) + } + }, [peerPayClient]) + + const handleToggleSettings = async () => { + const next = !settingsOpen + setSettingsOpen(next) + if (next && !settingsLoaded) { + await loadPermissions() + } + } + + const handleAllow = async () => { + const key = newAllowKey.trim() + if (!key) { setAllowError('Identity key is required'); return } + if (!peerPayClient) return + setAllowError('') + try { + await peerPayClient.allowPaymentRequestsFrom({ identityKey: key }) + setNewAllowKey('') + await loadPermissions() + toast.success('Identity allowed') + } catch (e) { + setAllowError((e as Error)?.message ?? 'Failed to allow identity') + } + } + + const handleBlock = async (identityKey: string) => { + if (!peerPayClient) return + try { + await peerPayClient.blockPaymentRequestsFrom({ identityKey }) + setPermissions(prev => prev.filter(p => p.identityKey !== identityKey)) + toast.success('Identity removed from whitelist') + } catch (e) { + toast.error((e as Error)?.message ?? 'Failed to block identity') + } + } + + const saveLimits = () => { + localStorage.setItem('payReq_minAmount', minAmount) + localStorage.setItem('payReq_maxAmount', maxAmount) + setLimitsSaved(true) + setTimeout(() => setLimitsSaved(false), 2000) + toast.success('Amount limits saved') + onRefresh() + } + + /* ---------------------------------------------------------------- */ + /* Pay / Decline */ + /* ---------------------------------------------------------------- */ + + const handlePay = async (req: IncomingPaymentRequest) => { + if (!peerPayClient) return + const id = req.messageId + setPaying(prev => ({ ...prev, [id]: true })) + try { + const rawAmt = payAmounts[id] + const overrideAmount = rawAmt ? parseInt(rawAmt, 10) : undefined + const note = notes[id] || undefined + await peerPayClient.fulfillPaymentRequest( + { request: req, amount: overrideAmount, note }, + messageBoxUrl || undefined + ) + window.dispatchEvent(new CustomEvent('balance-changed')) + toast.success('Payment sent!') + onRefresh() + } catch (e) { + toast.error((e as Error)?.message ?? 'Failed to send payment') + } finally { + setPaying(prev => { const n = { ...prev }; delete n[id]; return n }) + } + } + + const handleDecline = async (req: IncomingPaymentRequest) => { + if (!peerPayClient) return + const id = req.messageId + setDeclining(prev => ({ ...prev, [id]: true })) + try { + const note = notes[id] || undefined + await peerPayClient.declinePaymentRequest( + { request: req, note }, + messageBoxUrl || undefined + ) + toast.info('Request declined') + onRefresh() + } catch (e) { + toast.error((e as Error)?.message ?? 'Failed to decline request') + } finally { + setDeclining(prev => { const n = { ...prev }; delete n[id]; return n }) + } + } + + /* ---------------------------------------------------------------- */ + /* Render */ + /* ---------------------------------------------------------------- */ + + return ( + + {/* ---- Settings panel ---- */} + + + Incoming Requests + + + + + + + + + + {settingsLoading && ( + + + + )} + + {!settingsLoading && settingsLoaded && ( + + {/* Whitelist management */} + + Whitelist Management + + + { setNewAllowKey(e.target.value); setAllowError('') }} + error={!!allowError} + helperText={allowError} + /> + + + + {permissions.length === 0 ? ( + + No whitelisted identities yet. + + ) : ( + + {permissions.filter(p => p.allowed).map(p => ( + + + {truncateKey(p.identityKey, 24)} + + } + /> + + + + + ))} + + )} + + + + {/* Amount limits */} + + Amount Limits (satoshis) + + + setMinAmount(e.target.value)} + inputProps={{ min: 0 }} + fullWidth + /> + setMaxAmount(e.target.value)} + inputProps={{ min: 0 }} + fullWidth + /> + + + + )} + + + + {/* ---- Request cards ---- */} + {requests.length === 0 ? ( + + No incoming payment requests. + + ) : ( + requests.map(req => { + const id = req.messageId + const isExpired = req.expiresAt < Date.now() + const isPaying = !!paying[id] + const isDeclining = !!declining[id] + const isBusy = isPaying || isDeclining + + return ( + + + {/* Header row */} + + + {getInitials(req.sender)} + + + + {truncateKey(req.sender)} + + + {formatTimeRemaining(req.expiresAt)} + + + + + + {/* Amount — prominent */} + + {req.amount.toLocaleString()} sats + + + {/* Description quote block */} + + + "{req.description}" + + + + {!isExpired && ( + <> + {/* Inline amount override */} + setPayAmounts(prev => ({ ...prev, [id]: e.target.value.replace(/[^0-9]/g, '') }))} + type="number" + inputProps={{ min: 1 }} + helperText="Leave blank to pay requested amount" + disabled={isBusy} + /> + + {/* Optional note */} + setNotes(prev => ({ ...prev, [id]: e.target.value }))} + disabled={isBusy} + /> + + {/* Actions */} + + + + + + )} + + {isExpired && ( + + This request has expired and can no longer be fulfilled. + + )} + + + ) + }) + )} + + ) +} From dc3b1a9191c5285ccb0135a09b75a5b0b6b51ae5 Mon Sep 17 00:00:00 2001 From: Ishaan Lahoti <113637618+Quaakee@users.noreply.github.com> Date: Wed, 8 Apr 2026 07:43:37 -0500 Subject: [PATCH 04/22] feat: integrate request payment tabs into Payments page Co-Authored-By: Claude Sonnet 4.6 --- src/lib/pages/Dashboard/Payments/index.tsx | 170 ++++++++++++++------- 1 file changed, 116 insertions(+), 54 deletions(-) diff --git a/src/lib/pages/Dashboard/Payments/index.tsx b/src/lib/pages/Dashboard/Payments/index.tsx index c870dd3..b56120e 100644 --- a/src/lib/pages/Dashboard/Payments/index.tsx +++ b/src/lib/pages/Dashboard/Payments/index.tsx @@ -27,7 +27,9 @@ import { Link } from '@mui/material' import InputAdornment from '@mui/material/InputAdornment' -import { IncomingPayment } from '@bsv/message-box-client' +import { IncomingPayment, IncomingPaymentRequest } from '@bsv/message-box-client' +import RequestPaymentForm from './RequestPaymentForm' +import IncomingRequestList from './IncomingRequestList' import { Utils, Script, PublicKey, WalletInterface } from '@bsv/sdk' import { WalletContext } from '../../../WalletContext' import { toast } from 'react-toastify' @@ -480,6 +482,8 @@ export default function PeerPayRoute() { const wallet = managers?.permissionsManager || null const [payments, setPayments] = useState([]) + const [incomingRequests, setIncomingRequests] = useState([]) + const [activeTab, setActiveTab] = useState(0) const [loading, setLoading] = useState(false) const [transactions, setTransactions] = useState([]) const [snack, setSnack] = useState<{ open: boolean; msg: string; severity: 'success' | 'info' | 'warning' | 'error' }>({ @@ -501,6 +505,21 @@ export default function PeerPayRoute() { } }, [peerPayClient, messageBoxUrl]) + const fetchRequests = useCallback(async () => { + try { + if (!peerPayClient || !messageBoxUrl) return + const min = parseInt(localStorage.getItem('payReq_minAmount') ?? '1000', 10) + const max = parseInt(localStorage.getItem('payReq_maxAmount') ?? '10000000', 10) + const list = await peerPayClient.listIncomingPaymentRequests(messageBoxUrl, { + minAmount: isNaN(min) ? 1000 : min, + maxAmount: isNaN(max) ? 10000000 : max + }) + setIncomingRequests(list) + } catch (e) { + setSnack({ open: true, msg: (e as Error)?.message ?? 'Failed to load requests', severity: 'error' }) + } + }, [peerPayClient, messageBoxUrl]) + const getPastTransactions = async () => { if (!wallet) return @@ -567,65 +586,108 @@ export default function PeerPayRoute() { Payments - - {!isHostAnointed && ( - - Your message box host is not yet anointed. You can send payments, but others cannot find you to send payments to you until you anoint the host in Settings. - - )} + {!isHostAnointed && ( + + Your message box host is not yet anointed. You can send payments, but others cannot find you to send payments to you until you anoint the host in Settings. + + )} - + { + setActiveTab(v) + if (v === 2) fetchRequests() + if (v === 3) fetchPayments() + }} + variant="scrollable" + scrollButtons="auto" + sx={{ mb: 2, borderBottom: 1, borderColor: 'divider' }} + > + + + + + + + {/* Tab 0: Send Payment */} + {activeTab === 0 && ( + + - {loading && } + {loading && } - + {/* Transaction History Section */} + + + Transaction History + + + + + + {transactions.length === 0 ? ( + + No transactions yet... + + ) : ( + + {transactions.map((tx, index) => ( + + + + TXID:{' '} + + {tx.txid} + + + + To: {tx.to || 'N/A'} + + + Amount: {tx.amount || 'N/A'} BSV + + + + ))} + + )} + + + )} - {/* Transaction History Section */} - - - Transaction History - - + {/* Tab 1: Request Payment */} + {activeTab === 1 && ( + + )} - + {/* Tab 2: Incoming Requests */} + {activeTab === 2 && ( + + )} - {transactions.length === 0 ? ( - - No transactions yet... - - ) : ( - - {transactions.map((tx, index) => ( - - - - TXID:{' '} - - {tx.txid} - - - - To: {tx.to || 'N/A'} - - - Amount: {tx.amount || 'N/A'} BSV - - - - ))} - - )} - - + {/* Tab 3: Pending Payments */} + {activeTab === 3 && ( + + {loading && } + + + )} Date: Wed, 8 Apr 2026 08:22:10 -0500 Subject: [PATCH 05/22] =?UTF-8?q?feat:=20improve=20whitelist=20UX=20?= =?UTF-8?q?=E2=80=94=20toggle,=20identity=20search,=20full-width=20tabs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add on/off toggle for whitelist with visual ON/OFF chip indicator - Add identity search (useIdentitySearch) to whitelist management - Show full whitelisted identity keys with delete button - Direct key paste fallback when identity not found in search - Change tab variant from scrollable to fullWidth so all labels visible - Whitelist enabled state persisted in localStorage --- .../Payments/IncomingRequestList.tsx | 241 ++++++++++++++---- src/lib/pages/Dashboard/Payments/index.tsx | 3 +- 2 files changed, 186 insertions(+), 58 deletions(-) diff --git a/src/lib/pages/Dashboard/Payments/IncomingRequestList.tsx b/src/lib/pages/Dashboard/Payments/IncomingRequestList.tsx index c2f54e0..d4bb067 100644 --- a/src/lib/pages/Dashboard/Payments/IncomingRequestList.tsx +++ b/src/lib/pages/Dashboard/Payments/IncomingRequestList.tsx @@ -1,7 +1,8 @@ // src/lib/pages/Dashboard/Payments/IncomingRequestList.tsx -import React, { useCallback, useContext, useState } from 'react' +import React, { useCallback, useContext, useEffect, useState } from 'react' import { Alert, + Autocomplete, Avatar, Box, Button, @@ -9,19 +10,22 @@ import { CircularProgress, Collapse, Divider, + IconButton, List, ListItem, - ListItemSecondaryAction, ListItemText, Paper, Stack, + Switch, TextField, Typography, } from '@mui/material' -import { WalletInterface } from '@bsv/sdk' +import DeleteIcon from '@mui/icons-material/Delete' +import { PublicKey, WalletInterface } from '@bsv/sdk' import { WalletContext } from '../../../WalletContext' import { IncomingPaymentRequest } from '@bsv/message-box-client' import { toast } from 'react-toastify' +import { useIdentitySearch } from '@bsv/identity-react' /* ------------------------------------------------------------------ */ /* Helpers */ @@ -63,8 +67,8 @@ type LoadingMap = Record /* Component */ /* ------------------------------------------------------------------ */ -export default function IncomingRequestList({ requests, onRefresh }: Props) { - const { peerPayClient, messageBoxUrl } = useContext(WalletContext) +export default function IncomingRequestList({ requests, onRefresh, wallet }: Props) { + const { peerPayClient, messageBoxUrl, adminOriginator } = useContext(WalletContext) // Per-card state const [payAmounts, setPayAmounts] = useState>({}) @@ -77,7 +81,7 @@ export default function IncomingRequestList({ requests, onRefresh }: Props) { const [settingsLoaded, setSettingsLoaded] = useState(false) const [settingsLoading, setSettingsLoading] = useState(false) const [permissions, setPermissions] = useState>([]) - const [newAllowKey, setNewAllowKey] = useState('') + const [whitelistKeyInput, setWhitelistKeyInput] = useState('') const [allowError, setAllowError] = useState('') const [minAmount, setMinAmount] = useState(() => localStorage.getItem('payReq_minAmount') ?? '1000' @@ -87,6 +91,22 @@ export default function IncomingRequestList({ requests, onRefresh }: Props) { ) const [limitsSaved, setLimitsSaved] = useState(false) + // Whitelist toggle (on/off) + const [whitelistEnabled, setWhitelistEnabled] = useState(() => + localStorage.getItem('payReq_whitelistEnabled') !== 'false' + ) + + // Identity search for whitelist + const whitelistIdentitySearch = useIdentitySearch({ + originator: adminOriginator, + wallet, + onIdentitySelected: (identity) => { + if (identity) { + setWhitelistKeyInput(identity.identityKey) + } + } + }) + /* ---------------------------------------------------------------- */ /* Settings helpers */ /* ---------------------------------------------------------------- */ @@ -113,14 +133,20 @@ export default function IncomingRequestList({ requests, onRefresh }: Props) { } } + const handleToggleWhitelist = (enabled: boolean) => { + setWhitelistEnabled(enabled) + localStorage.setItem('payReq_whitelistEnabled', String(enabled)) + } + const handleAllow = async () => { - const key = newAllowKey.trim() + const key = whitelistKeyInput.trim() if (!key) { setAllowError('Identity key is required'); return } if (!peerPayClient) return setAllowError('') try { await peerPayClient.allowPaymentRequestsFrom({ identityKey: key }) - setNewAllowKey('') + setWhitelistKeyInput('') + whitelistIdentitySearch.handleSelect(null, null) await loadPermissions() toast.success('Identity allowed') } catch (e) { @@ -222,55 +248,160 @@ export default function IncomingRequestList({ requests, onRefresh }: Props) { {!settingsLoading && settingsLoaded && ( - {/* Whitelist management */} - - Whitelist Management - - - { setNewAllowKey(e.target.value); setAllowError('') }} - error={!!allowError} - helperText={allowError} - /> - - + {/* Whitelist toggle */} + + + + Identity Whitelist + + + When enabled, only whitelisted identities can send you requests + + + + + handleToggleWhitelist(e.target.checked)} + /> + + + + {/* Whitelist management — only shown when enabled */} + + + {/* Identity search for adding */} + { + whitelistIdentitySearch.handleSelect(event, value as any) + if (value && typeof value !== 'string') { + setWhitelistKeyInput(value.identityKey) + } else { + setWhitelistKeyInput('') + } + }} + getOptionLabel={(option) => + typeof option === 'string' ? option : option.name || option.identityKey.slice(0, 16) + } + isOptionEqualToValue={(option, value) => { + if (typeof option === 'string' || typeof value === 'string') return false + return option.identityKey === value.identityKey + }} + renderInput={(params) => ( + + )} + renderOption={(props, option) => { + if (typeof option === 'string') return null + const { key, ...otherProps } = props + return ( +
  • + + + {getInitials(option.identityKey)} + + + {option.name || 'Unknown'} + + {option.identityKey.slice(0, 20)}... + + + +
  • + ) + }} + noOptionsText={whitelistIdentitySearch.inputValue ? 'No identities found' : 'Start typing to search'} + fullWidth + size="small" + /> - {permissions.length === 0 ? ( - - No whitelisted identities yet. - - ) : ( - - {permissions.filter(p => p.allowed).map(p => ( - - - {truncateKey(p.identityKey, 24)} - + {/* Direct key input fallback */} + + { + const val = e.target.value.trim() + setWhitelistKeyInput(val) + setAllowError('') + if (!val) return + try { + PublicKey.fromString(val) + whitelistIdentitySearch.handleSelect(null, null) + } catch { + // Not a valid key yet — allow typing } - /> - - + + + {/* Whitelisted identities list */} + {permissions.filter(p => p.allowed).length === 0 ? ( + + No whitelisted identities yet. Add identities above to receive payment requests. + + ) : ( + + {permissions.filter(p => p.allowed).map(p => ( + handleBlock(p.identityKey)} + color="error" + > + + + } > - Remove - - - - ))} - - )} + + {truncateKey(p.identityKey, 24)} + + } + secondary={ + + {p.identityKey} + + } + /> + + ))} + + )} +
    +
    @@ -285,7 +416,6 @@ export default function IncomingRequestList({ requests, onRefresh }: Props) { type="number" value={minAmount} onChange={(e) => setMinAmount(e.target.value)} - inputProps={{ min: 0 }} fullWidth /> setMaxAmount(e.target.value)} - inputProps={{ min: 0 }} fullWidth />
    - )} From 8c62cbf15134fe637af3a5fcc8399c2903453bdb Mon Sep 17 00:00:00 2001 From: Ishaan Lahoti <113637618+Quaakee@users.noreply.github.com> Date: Wed, 8 Apr 2026 08:48:12 -0500 Subject: [PATCH 07/22] fix: restore @bsv/message-box-client dependency in package.json The dependency was accidentally removed during npm link setup. Restored with ^2.0.6 (current registry version). This PR requires bsv-blockchain/message-box-client#38 to be merged and a new version published before the build will pass. --- package-lock.json | 85 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 86 insertions(+) diff --git a/package-lock.json b/package-lock.json index 23147b1..0477f4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@bsv/btms-permission-module": "^1.0.1", "@bsv/btms-permission-module-ui": "^1.0.0", "@bsv/identity-react": "^1.1.14", + "@bsv/message-box-client": "^2.0.6", "@bsv/sdk": "^2.0.7", "@bsv/uhrp-react": "^1.0.6", "@bsv/wallet-toolbox": "^2.1.5", @@ -677,6 +678,16 @@ "node": ">= 0.6" } }, + "node_modules/@bsv/authsocket-client": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@bsv/authsocket-client/-/authsocket-client-2.0.2.tgz", + "integrity": "sha512-da7ON4zqdShM9QFYxQzcuJoVfau1sm1dwrpsYtU3JowwDXMYYzcbvwvWzYBJS1380ijn1T50n1IAJlAaLqVNgg==", + "license": "SEE LICENSE IN LICENSE.txt", + "dependencies": { + "@bsv/sdk": "^2.0.4", + "socket.io-client": "^4.8.1" + } + }, "node_modules/@bsv/btms": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@bsv/btms/-/btms-1.0.1.tgz", @@ -738,6 +749,16 @@ "undici-types": "~6.21.0" } }, + "node_modules/@bsv/message-box-client": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@bsv/message-box-client/-/message-box-client-2.0.6.tgz", + "integrity": "sha512-8dkQ7Qo/DkdQmJOg8XdenvdBRmkcm5lDIMpT8ap4V9mQ1kDvWgkUcD6P5SIOcvfv8r0P2fDdOYW8kH8OnASJnA==", + "license": "SEE LICENSE IN LICENSE.txt", + "dependencies": { + "@bsv/authsocket-client": "^2.0.2", + "@bsv/sdk": "^2.0.11" + } + }, "node_modules/@bsv/payment-express-middleware": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@bsv/payment-express-middleware/-/payment-express-middleware-2.0.1.tgz", @@ -3454,6 +3475,12 @@ "url": "https://github.com/sindresorhus/is?sponsor=1" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, "node_modules/@szmarczak/http-timer": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", @@ -6082,6 +6109,28 @@ "once": "^1.4.0" } }, + "node_modules/engine.io-client": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.4.tgz", + "integrity": "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.18.3", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -10094,6 +10143,34 @@ "npm": ">= 3.0.0" } }, + "node_modules/socket.io-client": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz", + "integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz", + "integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/socks": { "version": "2.8.7", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", @@ -11171,6 +11248,14 @@ "node": ">=8.0" } }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 2ae5c72..d640784 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@bsv/btms-permission-module": "^1.0.1", "@bsv/btms-permission-module-ui": "^1.0.0", "@bsv/identity-react": "^1.1.14", + "@bsv/message-box-client": "^2.0.6", "@bsv/sdk": "^2.0.7", "@bsv/uhrp-react": "^1.0.6", "@bsv/wallet-toolbox": "^2.1.5", From 67ef421b3dfec4000463e7515331ca958161d146 Mon Sep 17 00:00:00 2001 From: Ishaan Lahoti <113637618+Quaakee@users.noreply.github.com> Date: Wed, 8 Apr 2026 09:00:37 -0500 Subject: [PATCH 08/22] chore: bump version to 2.0.9, update CHANGELOG Add changelog entry for Request Payment feature: - Request/fulfill/decline payment requests - Outgoing request tracker with live status - Identity whitelist with toggle - Min/max amount limits - 4-tab Payments page layout --- CHANGELOG.md | 19 +++++++++++++++++++ package.json | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5de9537..bfba40c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## [2.0.9] - 2026-04-08 + +### Features +- **Request Payment**: Users can now request Bitcoin payments from other identities via the Payments page + - New "Request Payment" tab with recipient search, amount, description, and configurable expiry + - New "Incoming Requests" tab showing payment request cards with Pay/Decline actions + - Editable payment amount — payer can modify the amount before fulfilling + - Optional notes on payments and declines + - Outgoing request tracker with live status updates (Pending/Paid/Declined/Expired/Cancelled) + - Cancel pending outgoing requests +- **Request Security Settings** (in Incoming Requests tab): + - Identity whitelist with on/off toggle — only whitelisted identities can send requests when enabled + - Identity search integration for adding to whitelist + - Configurable min/max amount limits to filter dust and oversized requests +- Payments page reorganized into 4 full-width tabs: Send Payment, Request Payment, Incoming Requests, Pending Payments + +### Dependencies +- `@bsv/message-box-client` ^2.0.6 (requires >=2.0.7 with payment request methods once published) + ## [2.0.0] - 2026-02-10 ### Breaking Changes diff --git a/package.json b/package.json index d640784..c95170f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bsv-desktop-electron", - "version": "2.0.8", + "version": "2.0.9", "description": "BSV Desktop Wallet - Electron Edition", "main": "dist-electron/main.js", "type": "module", From bd87ce97811491e0a7aa82e52be1af4fd034010a Mon Sep 17 00:00:00 2001 From: Ishaan Lahoti <113637618+Quaakee@users.noreply.github.com> Date: Wed, 8 Apr 2026 09:04:03 -0500 Subject: [PATCH 09/22] chore: bump version to 2.1.0 to avoid conflict with wallet-diagnosis PR (#33) --- CHANGELOG.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfba40c..3234f34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [2.0.9] - 2026-04-08 +## [2.1.0] - 2026-04-08 ### Features - **Request Payment**: Users can now request Bitcoin payments from other identities via the Payments page diff --git a/package.json b/package.json index c95170f..78874ba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bsv-desktop-electron", - "version": "2.0.9", + "version": "2.1.0", "description": "BSV Desktop Wallet - Electron Edition", "main": "dist-electron/main.js", "type": "module", From 66889fa567c2a0094fed38cafc6887f3eca976ed Mon Sep 17 00:00:00 2001 From: Ishaan Lahoti <113637618+Quaakee@users.noreply.github.com> Date: Wed, 8 Apr 2026 09:16:31 -0500 Subject: [PATCH 10/22] feat: add Receive button to fulfilled payment requests Fulfilled requests now show a Receive button in the outgoing tracker to accept/internalize the payment directly, instead of requiring the user to go to the Pending Payments tab. Displays the fulfiller's note in a quote block and shows the paid amount prominently. --- .../Dashboard/Payments/RequestPaymentForm.tsx | 98 ++++++++++++++++--- 1 file changed, 84 insertions(+), 14 deletions(-) diff --git a/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx b/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx index fe20f14..54c6037 100644 --- a/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx +++ b/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx @@ -23,7 +23,7 @@ import { CurrencyConverter } from '@bsv/amountinator' import { toast } from 'react-toastify' import useAsyncEffect from 'use-async-effect' import { useIdentitySearch } from '@bsv/identity-react' -import { PaymentRequestResponse } from '@bsv/message-box-client' +import { PaymentRequestResponse, IncomingPayment } from '@bsv/message-box-client' /* ------------------------------------------------------------------ */ /* Types */ @@ -36,9 +36,11 @@ export type OutgoingRequest = { amount: number description: string expiresAt: number - status: 'pending' | 'paid' | 'declined' | 'expired' | 'cancelled' + status: 'pending' | 'paid' | 'declined' | 'expired' | 'cancelled' | 'received' amountPaid?: number note?: string + /** The incoming payment associated with a fulfilled request, used for accepting funds. */ + incomingPayment?: IncomingPayment } type Props = { @@ -76,6 +78,7 @@ function statusColor(status: OutgoingRequest['status']): 'success' | 'info' | 'e switch (status) { case 'pending': return 'success' case 'paid': return 'info' + case 'received': return 'success' case 'declined': return 'error' default: return 'default' } @@ -101,6 +104,7 @@ export default function RequestPaymentForm({ wallet, onRequestSent }: Props) { // Outgoing tracker const [outgoing, setOutgoing] = useState([]) const [cancellingId, setCancellingId] = useState(null) + const [receivingId, setReceivingId] = useState(null) const pollRef = useRef | null>(null) const currencyConverter = new CurrencyConverter(undefined, managers?.settingsManager as any) @@ -118,21 +122,49 @@ export default function RequestPaymentForm({ wallet, onRequestSent }: Props) { setCurrencySymbol(currencyConverter.getCurrencySymbol()) }, []) - // Poll for responses every 15 seconds when there are pending requests + // Poll for responses every 15 seconds when there are pending or paid (unreceived) requests const pollResponses = useCallback(async () => { if (!peerPayClient || !messageBoxUrl) return const hasPending = outgoing.some(r => r.status === 'pending' && r.expiresAt > Date.now()) - if (!hasPending) return + const hasPaidUnreceived = outgoing.some(r => r.status === 'paid' && !r.incomingPayment) + if (!hasPending && !hasPaidUnreceived) return try { const responses: PaymentRequestResponse[] = await peerPayClient.listPaymentRequestResponses(messageBoxUrl) - if (responses.length === 0) return + + // Also fetch incoming payments to match with fulfilled requests + let incomingPayments: IncomingPayment[] = [] + if (responses.some(r => r.status === 'paid') || hasPaidUnreceived) { + incomingPayments = await peerPayClient.listIncomingPayments(messageBoxUrl) + } + setOutgoing(prev => prev.map(req => { + // Don't update already received requests + if (req.status === 'received') return req + const match = responses.find(r => r.requestId === req.requestId) - if (!match) return req + if (!match) { + // Try to find incoming payment for already-paid requests missing their payment ref + if (req.status === 'paid' && !req.incomingPayment) { + const payment = incomingPayments.find(p => p.sender === req.recipient) + if (payment) return { ...req, incomingPayment: payment } + } + return req + } + + if (match.status === 'paid') { + // Find the matching incoming payment from this sender + const payment = incomingPayments.find(p => p.sender === req.recipient) + return { + ...req, + status: 'paid' as const, + amountPaid: match.amountPaid, + note: match.note, + incomingPayment: payment, + } + } return { ...req, - status: match.status === 'paid' ? 'paid' : 'declined', - amountPaid: match.amountPaid, + status: 'declined' as const, note: match.note, } })) @@ -250,6 +282,24 @@ export default function RequestPaymentForm({ wallet, onRequestSent }: Props) { } } + /** Accept/internalize the payment associated with a fulfilled request. */ + const handleReceive = async (req: OutgoingRequest) => { + if (!peerPayClient || !req.incomingPayment) return + try { + setReceivingId(req.requestId) + await peerPayClient.acceptPayment(req.incomingPayment) + setOutgoing(prev => + prev.map(r => r.requestId === req.requestId ? { ...r, status: 'received' as const } : r) + ) + window.dispatchEvent(new CustomEvent('balance-changed')) + toast.success('Payment received!') + } catch (e) { + toast.error((e as Error)?.message ?? 'Failed to receive payment') + } finally { + setReceivingId(null) + } + } + return ( {/* ---- Form ---- */} @@ -435,16 +485,18 @@ export default function RequestPaymentForm({ wallet, onRequestSent }: Props) { {req.description} - {req.note && ( - - Note: {req.note} - - )} {req.status === 'paid' && req.amountPaid != null && ( - + Paid: {req.amountPaid.toLocaleString()} sats )} + {req.note && ( + + + "{req.note}" + + + )} : 'Cancel'} )} + {req.status === 'paid' && req.incomingPayment && ( + + )} + {req.status === 'paid' && !req.incomingPayment && ( + + Waiting for payment… + + )} From 74cfd4e0e47834118d711d997597247feaa810bd Mon Sep 17 00:00:00 2001 From: Ishaan Lahoti <113637618+Quaakee@users.noreply.github.com> Date: Wed, 8 Apr 2026 09:18:31 -0500 Subject: [PATCH 11/22] fix: show clear error message when payment request is blocked by recipient --- src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx b/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx index 54c6037..c992d5a 100644 --- a/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx +++ b/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx @@ -256,8 +256,8 @@ export default function RequestPaymentForm({ wallet, onRequestSent }: Props) { identitySearch.handleSelect(null, null) onRequestSent?.() } catch (e) { - toast.error('Failed to send payment request') - alert((e as Error)?.message ?? 'Failed to send payment request') + const msg = (e as Error)?.message ?? 'Failed to send payment request' + toast.error(msg) } finally { setSending(false) } From 37930e503be8cf8f111571ec80e7f1db1c3f26f8 Mon Sep 17 00:00:00 2001 From: Ishaan Lahoti <113637618+Quaakee@users.noreply.github.com> Date: Wed, 8 Apr 2026 09:35:16 -0500 Subject: [PATCH 12/22] fix: persist outgoing requests to localStorage so they survive tab switches --- .../Dashboard/Payments/RequestPaymentForm.tsx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx b/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx index c992d5a..b9b4cb3 100644 --- a/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx +++ b/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx @@ -101,12 +101,24 @@ export default function RequestPaymentForm({ wallet, onRequestSent }: Props) { const [sending, setSending] = useState(false) const [currencySymbol, setCurrencySymbol] = useState('$') - // Outgoing tracker - const [outgoing, setOutgoing] = useState([]) + // Outgoing tracker — persisted to localStorage so requests survive tab switches + const [outgoing, setOutgoing] = useState(() => { + try { + const saved = localStorage.getItem('payReq_outgoing') + return saved ? JSON.parse(saved) : [] + } catch { return [] } + }) const [cancellingId, setCancellingId] = useState(null) const [receivingId, setReceivingId] = useState(null) const pollRef = useRef | null>(null) + // Persist outgoing requests to localStorage on every change. + // Exclude incomingPayment (contains transaction data too large for localStorage). + useEffect(() => { + const serializable = outgoing.map(({ incomingPayment, ...rest }) => rest) + localStorage.setItem('payReq_outgoing', JSON.stringify(serializable)) + }, [outgoing]) + const currencyConverter = new CurrencyConverter(undefined, managers?.settingsManager as any) const identitySearch = useIdentitySearch({ From 3654cbd71702d3201e79d4d4366b4e33e2e32ba7 Mon Sep 17 00:00:00 2001 From: Ishaan Lahoti <113637618+Quaakee@users.noreply.github.com> Date: Wed, 8 Apr 2026 09:44:55 -0500 Subject: [PATCH 13/22] fix: scope all payment request localStorage keys by identity All localStorage keys for the request payment feature are now suffixed with the active profile's identity key. Prevents cross-account data overwrites when users switch between multiple identities in the same wallet. Affected keys: payReq_outgoing, payReq_whitelist, payReq_minAmount, payReq_maxAmount, payReq_whitelistEnabled --- .../Payments/IncomingRequestList.tsx | 27 ++++++++++++------- .../Dashboard/Payments/RequestPaymentForm.tsx | 23 ++++++++++++---- src/lib/pages/Dashboard/Payments/index.tsx | 10 ++++--- 3 files changed, 41 insertions(+), 19 deletions(-) diff --git a/src/lib/pages/Dashboard/Payments/IncomingRequestList.tsx b/src/lib/pages/Dashboard/Payments/IncomingRequestList.tsx index de6a5ae..bf214e5 100644 --- a/src/lib/pages/Dashboard/Payments/IncomingRequestList.tsx +++ b/src/lib/pages/Dashboard/Payments/IncomingRequestList.tsx @@ -68,7 +68,14 @@ type LoadingMap = Record /* ------------------------------------------------------------------ */ export default function IncomingRequestList({ requests, onRefresh, wallet }: Props) { - const { peerPayClient, messageBoxUrl, adminOriginator } = useContext(WalletContext) + const { peerPayClient, messageBoxUrl, adminOriginator, activeProfile } = useContext(WalletContext) + + // Storage keys scoped to the current user's identity to prevent cross-account overwrites. + const idSuffix = activeProfile?.identityKey ? `_${activeProfile.identityKey}` : '' + const whitelistKey = `payReq_whitelist${idSuffix}` + const minAmountKey = `payReq_minAmount${idSuffix}` + const maxAmountKey = `payReq_maxAmount${idSuffix}` + const whitelistEnabledKey = `payReq_whitelistEnabled${idSuffix}` // Per-card state const [payAmounts, setPayAmounts] = useState>({}) @@ -80,22 +87,22 @@ export default function IncomingRequestList({ requests, onRefresh, wallet }: Pro const [settingsOpen, setSettingsOpen] = useState(false) const [permissions, setPermissions] = useState(() => { try { - return JSON.parse(localStorage.getItem('payReq_whitelist') ?? '[]') + return JSON.parse(localStorage.getItem(whitelistKey) ?? '[]') } catch { return [] } }) const [whitelistKeyInput, setWhitelistKeyInput] = useState('') const [allowError, setAllowError] = useState('') const [minAmount, setMinAmount] = useState(() => - localStorage.getItem('payReq_minAmount') ?? '1000' + localStorage.getItem(minAmountKey) ?? '1000' ) const [maxAmount, setMaxAmount] = useState(() => - localStorage.getItem('payReq_maxAmount') ?? '10000000' + localStorage.getItem(maxAmountKey) ?? '10000000' ) const [limitsSaved, setLimitsSaved] = useState(false) // Whitelist toggle (on/off) const [whitelistEnabled, setWhitelistEnabled] = useState(() => - localStorage.getItem('payReq_whitelistEnabled') !== 'false' + localStorage.getItem(whitelistEnabledKey) !== 'false' ) // Identity search for whitelist @@ -116,8 +123,8 @@ export default function IncomingRequestList({ requests, onRefresh, wallet }: Pro /** Persist the whitelist to localStorage. */ const saveWhitelist = useCallback((list: string[]) => { setPermissions(list) - localStorage.setItem('payReq_whitelist', JSON.stringify(list)) - }, []) + localStorage.setItem(whitelistKey, JSON.stringify(list)) + }, [whitelistKey]) const handleToggleSettings = () => { setSettingsOpen(prev => !prev) @@ -125,7 +132,7 @@ export default function IncomingRequestList({ requests, onRefresh, wallet }: Pro const handleToggleWhitelist = (enabled: boolean) => { setWhitelistEnabled(enabled) - localStorage.setItem('payReq_whitelistEnabled', String(enabled)) + localStorage.setItem(whitelistEnabledKey, String(enabled)) } const handleAllow = async () => { @@ -166,8 +173,8 @@ export default function IncomingRequestList({ requests, onRefresh, wallet }: Pro } const saveLimits = () => { - localStorage.setItem('payReq_minAmount', minAmount) - localStorage.setItem('payReq_maxAmount', maxAmount) + localStorage.setItem(minAmountKey, minAmount) + localStorage.setItem(maxAmountKey, maxAmount) setLimitsSaved(true) setTimeout(() => setLimitsSaved(false), 2000) toast.success('Amount limits saved') diff --git a/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx b/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx index b9b4cb3..d513c7a 100644 --- a/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx +++ b/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx @@ -89,7 +89,12 @@ function statusColor(status: OutgoingRequest['status']): 'success' | 'info' | 'e /* ------------------------------------------------------------------ */ export default function RequestPaymentForm({ wallet, onRequestSent }: Props) { - const { managers, adminOriginator, peerPayClient, messageBoxUrl } = useContext(WalletContext) + const { managers, adminOriginator, peerPayClient, messageBoxUrl, activeProfile } = useContext(WalletContext) + + // Storage key scoped to the current user's identity to prevent cross-account overwrites. + const storageKey = activeProfile?.identityKey + ? `payReq_outgoing_${activeProfile.identityKey}` + : 'payReq_outgoing' // Form state const [recipient, setRecipient] = useState('') @@ -101,10 +106,10 @@ export default function RequestPaymentForm({ wallet, onRequestSent }: Props) { const [sending, setSending] = useState(false) const [currencySymbol, setCurrencySymbol] = useState('$') - // Outgoing tracker — persisted to localStorage so requests survive tab switches + // Outgoing tracker — persisted to localStorage (keyed by identity) so requests survive tab switches const [outgoing, setOutgoing] = useState(() => { try { - const saved = localStorage.getItem('payReq_outgoing') + const saved = localStorage.getItem(storageKey) return saved ? JSON.parse(saved) : [] } catch { return [] } }) @@ -112,12 +117,20 @@ export default function RequestPaymentForm({ wallet, onRequestSent }: Props) { const [receivingId, setReceivingId] = useState(null) const pollRef = useRef | null>(null) + // Reload outgoing requests when identity changes (profile switch / re-login). + useEffect(() => { + try { + const saved = localStorage.getItem(storageKey) + setOutgoing(saved ? JSON.parse(saved) : []) + } catch { setOutgoing([]) } + }, [storageKey]) + // Persist outgoing requests to localStorage on every change. // Exclude incomingPayment (contains transaction data too large for localStorage). useEffect(() => { const serializable = outgoing.map(({ incomingPayment, ...rest }) => rest) - localStorage.setItem('payReq_outgoing', JSON.stringify(serializable)) - }, [outgoing]) + localStorage.setItem(storageKey, JSON.stringify(serializable)) + }, [outgoing, storageKey]) const currencyConverter = new CurrencyConverter(undefined, managers?.settingsManager as any) diff --git a/src/lib/pages/Dashboard/Payments/index.tsx b/src/lib/pages/Dashboard/Payments/index.tsx index 76714f7..26ca6a6 100644 --- a/src/lib/pages/Dashboard/Payments/index.tsx +++ b/src/lib/pages/Dashboard/Payments/index.tsx @@ -477,9 +477,11 @@ export default function PeerPayRoute() { isHostAnointed, anointCurrentHost, anointmentLoading, - adminOriginator + adminOriginator, + activeProfile } = useContext(WalletContext) const wallet = managers?.permissionsManager || null + const idSuffix = activeProfile?.identityKey ? `_${activeProfile.identityKey}` : '' const [payments, setPayments] = useState([]) const [incomingRequests, setIncomingRequests] = useState([]) @@ -508,8 +510,8 @@ export default function PeerPayRoute() { const fetchRequests = useCallback(async () => { try { if (!peerPayClient || !messageBoxUrl) return - const min = parseInt(localStorage.getItem('payReq_minAmount') ?? '1000', 10) - const max = parseInt(localStorage.getItem('payReq_maxAmount') ?? '10000000', 10) + const min = parseInt(localStorage.getItem(`payReq_minAmount${idSuffix}`) ?? '1000', 10) + const max = parseInt(localStorage.getItem(`payReq_maxAmount${idSuffix}`) ?? '10000000', 10) const list = await peerPayClient.listIncomingPaymentRequests(messageBoxUrl, { minAmount: isNaN(min) ? 1000 : min, maxAmount: isNaN(max) ? 10000000 : max @@ -518,7 +520,7 @@ export default function PeerPayRoute() { } catch (e) { setSnack({ open: true, msg: (e as Error)?.message ?? 'Failed to load requests', severity: 'error' }) } - }, [peerPayClient, messageBoxUrl]) + }, [peerPayClient, messageBoxUrl, idSuffix]) const getPastTransactions = async () => { if (!wallet) return From 05d11098e2a3b27b09008e6861e2c29f619202a3 Mon Sep 17 00:00:00 2001 From: Ishaan Lahoti <113637618+Quaakee@users.noreply.github.com> Date: Wed, 8 Apr 2026 09:52:24 -0500 Subject: [PATCH 14/22] fix: poll for responses immediately on mount and for paid-unreceived requests Previously polling only started after 15 seconds and only checked for pending requests. Now polls immediately when the component mounts with actionable requests, and also polls for paid requests that still need their incomingPayment reference fetched for the Receive button. --- .../pages/Dashboard/Payments/RequestPaymentForm.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx b/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx index d513c7a..28b80a2 100644 --- a/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx +++ b/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx @@ -212,10 +212,17 @@ export default function RequestPaymentForm({ wallet, onRequestSent }: Props) { return () => clearInterval(tick) }, []) - // Set up polling interval when pending requests exist + // Set up polling: immediate check on mount + interval when there are actionable requests. useEffect(() => { - const hasPending = outgoing.some(r => r.status === 'pending') - if (hasPending) { + const needsPoll = outgoing.some(r => + r.status === 'pending' || (r.status === 'paid' && !r.incomingPayment) + ) + + if (needsPoll) { + // Immediate poll on mount / when status changes + pollResponses() + + // Then continue polling every 15 seconds if (!pollRef.current) { pollRef.current = setInterval(pollResponses, 15_000) } From 74191d596ea5d46a48fd74a630545260bea0eba4 Mon Sep 17 00:00:00 2001 From: Ishaan Lahoti <113637618+Quaakee@users.noreply.github.com> Date: Wed, 8 Apr 2026 09:55:28 -0500 Subject: [PATCH 15/22] fix: prevent infinite polling loop by decoupling pollResponses from outgoing state --- .../Dashboard/Payments/RequestPaymentForm.tsx | 50 +++++++++---------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx b/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx index 28b80a2..822e598 100644 --- a/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx +++ b/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx @@ -115,7 +115,6 @@ export default function RequestPaymentForm({ wallet, onRequestSent }: Props) { }) const [cancellingId, setCancellingId] = useState(null) const [receivingId, setReceivingId] = useState(null) - const pollRef = useRef | null>(null) // Reload outgoing requests when identity changes (profile switch / re-login). useEffect(() => { @@ -147,11 +146,16 @@ export default function RequestPaymentForm({ wallet, onRequestSent }: Props) { setCurrencySymbol(currencyConverter.getCurrencySymbol()) }, []) - // Poll for responses every 15 seconds when there are pending or paid (unreceived) requests + // Keep a ref to outgoing so pollResponses doesn't depend on it (avoids re-render loops). + const outgoingRef = useRef(outgoing) + useEffect(() => { outgoingRef.current = outgoing }, [outgoing]) + + // Poll for responses when there are pending or paid (unreceived) requests. const pollResponses = useCallback(async () => { if (!peerPayClient || !messageBoxUrl) return - const hasPending = outgoing.some(r => r.status === 'pending' && r.expiresAt > Date.now()) - const hasPaidUnreceived = outgoing.some(r => r.status === 'paid' && !r.incomingPayment) + const current = outgoingRef.current + const hasPending = current.some(r => r.status === 'pending' && r.expiresAt > Date.now()) + const hasPaidUnreceived = current.some(r => r.status === 'paid' && !r.incomingPayment) if (!hasPending && !hasPaidUnreceived) return try { const responses: PaymentRequestResponse[] = await peerPayClient.listPaymentRequestResponses(messageBoxUrl) @@ -196,7 +200,7 @@ export default function RequestPaymentForm({ wallet, onRequestSent }: Props) { } catch { // Silently ignore poll errors } - }, [peerPayClient, messageBoxUrl, outgoing]) + }, [peerPayClient, messageBoxUrl]) // Mark expired requests useEffect(() => { @@ -212,33 +216,25 @@ export default function RequestPaymentForm({ wallet, onRequestSent }: Props) { return () => clearInterval(tick) }, []) - // Set up polling: immediate check on mount + interval when there are actionable requests. + // Immediate poll once on mount if there are actionable requests. + const hasMountPolled = useRef(false) useEffect(() => { - const needsPoll = outgoing.some(r => + if (hasMountPolled.current) return + const current = outgoingRef.current + const needsPoll = current.some(r => r.status === 'pending' || (r.status === 'paid' && !r.incomingPayment) ) - - if (needsPoll) { - // Immediate poll on mount / when status changes + if (needsPoll && peerPayClient && messageBoxUrl) { + hasMountPolled.current = true pollResponses() - - // Then continue polling every 15 seconds - if (!pollRef.current) { - pollRef.current = setInterval(pollResponses, 15_000) - } - } else { - if (pollRef.current) { - clearInterval(pollRef.current) - pollRef.current = null - } } - return () => { - if (pollRef.current) { - clearInterval(pollRef.current) - pollRef.current = null - } - } - }, [outgoing, pollResponses]) + }, [peerPayClient, messageBoxUrl, pollResponses]) + + // Set up 15-second polling interval (stable — doesn't re-create on outgoing changes). + useEffect(() => { + const id = setInterval(pollResponses, 15_000) + return () => clearInterval(id) + }, [pollResponses]) const handleAmountChange = useCallback(async (e: React.ChangeEvent) => { const val = e.target.value.replace(/[^0-9.]/g, '') From 5fb3e8e50732a7702657a22dbe33c312518ae111 Mon Sep 17 00:00:00 2001 From: Ishaan Lahoti <113637618+Quaakee@users.noreply.github.com> Date: Wed, 8 Apr 2026 10:00:11 -0500 Subject: [PATCH 16/22] fix: defer localStorage load/save until identity key is available Previously the component loaded from a fallback key when activeProfile was undefined on mount, then overwrote the identity-scoped key with empty data when the profile resolved. Now storageKey is null until the identity is available, and load/save effects are skipped until then. --- .../Dashboard/Payments/RequestPaymentForm.tsx | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx b/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx index 822e598..d83fd87 100644 --- a/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx +++ b/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx @@ -92,9 +92,9 @@ export default function RequestPaymentForm({ wallet, onRequestSent }: Props) { const { managers, adminOriginator, peerPayClient, messageBoxUrl, activeProfile } = useContext(WalletContext) // Storage key scoped to the current user's identity to prevent cross-account overwrites. - const storageKey = activeProfile?.identityKey - ? `payReq_outgoing_${activeProfile.identityKey}` - : 'payReq_outgoing' + // Null until identity is available — prevents saving/loading with the wrong key. + const identityKey = activeProfile?.identityKey + const storageKey = identityKey ? `payReq_outgoing_${identityKey}` : null // Form state const [recipient, setRecipient] = useState('') @@ -106,18 +106,14 @@ export default function RequestPaymentForm({ wallet, onRequestSent }: Props) { const [sending, setSending] = useState(false) const [currencySymbol, setCurrencySymbol] = useState('$') - // Outgoing tracker — persisted to localStorage (keyed by identity) so requests survive tab switches - const [outgoing, setOutgoing] = useState(() => { - try { - const saved = localStorage.getItem(storageKey) - return saved ? JSON.parse(saved) : [] - } catch { return [] } - }) + // Outgoing tracker — starts empty, loaded from localStorage once identity is known. + const [outgoing, setOutgoing] = useState([]) const [cancellingId, setCancellingId] = useState(null) const [receivingId, setReceivingId] = useState(null) - // Reload outgoing requests when identity changes (profile switch / re-login). + // Load outgoing requests from localStorage when identity becomes available or changes. useEffect(() => { + if (!storageKey) return try { const saved = localStorage.getItem(storageKey) setOutgoing(saved ? JSON.parse(saved) : []) @@ -125,8 +121,10 @@ export default function RequestPaymentForm({ wallet, onRequestSent }: Props) { }, [storageKey]) // Persist outgoing requests to localStorage on every change. + // Only persist when we have a valid storage key (identity loaded). // Exclude incomingPayment (contains transaction data too large for localStorage). useEffect(() => { + if (!storageKey) return const serializable = outgoing.map(({ incomingPayment, ...rest }) => rest) localStorage.setItem(storageKey, JSON.stringify(serializable)) }, [outgoing, storageKey]) From 28eb34df00e84a7680e7b3892e6c6c0376307489 Mon Sep 17 00:00:00 2001 From: Ishaan Lahoti <113637618+Quaakee@users.noreply.github.com> Date: Wed, 8 Apr 2026 10:09:29 -0500 Subject: [PATCH 17/22] fix: prevent SAVE effect from overwriting localStorage before LOAD completes Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Dashboard/Payments/RequestPaymentForm.tsx | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx b/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx index d83fd87..4ebc7cf 100644 --- a/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx +++ b/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx @@ -111,20 +111,29 @@ export default function RequestPaymentForm({ wallet, onRequestSent }: Props) { const [cancellingId, setCancellingId] = useState(null) const [receivingId, setReceivingId] = useState(null) + // Guard: the SAVE effect must not run until the LOAD effect has completed at least once. + // This prevents the initial empty state from overwriting persisted data in localStorage. + const hasLoadedRef = useRef(false) + // Load outgoing requests from localStorage when identity becomes available or changes. useEffect(() => { if (!storageKey) return + hasLoadedRef.current = false try { - const saved = localStorage.getItem(storageKey) - setOutgoing(saved ? JSON.parse(saved) : []) - } catch { setOutgoing([]) } + const raw = localStorage.getItem(storageKey) + const parsed = raw ? JSON.parse(raw) : [] + setOutgoing(parsed) + } catch { + setOutgoing([]) + } + hasLoadedRef.current = true }, [storageKey]) // Persist outgoing requests to localStorage on every change. // Only persist when we have a valid storage key (identity loaded). // Exclude incomingPayment (contains transaction data too large for localStorage). useEffect(() => { - if (!storageKey) return + if (!storageKey || !hasLoadedRef.current) return const serializable = outgoing.map(({ incomingPayment, ...rest }) => rest) localStorage.setItem(storageKey, JSON.stringify(serializable)) }, [outgoing, storageKey]) From 3a6eb44fa029988bfefa776471c729f8ae9c55a0 Mon Sep 17 00:00:00 2001 From: Ishaan Lahoti <113637618+Quaakee@users.noreply.github.com> Date: Wed, 8 Apr 2026 10:13:17 -0500 Subject: [PATCH 18/22] fix: eliminate save/load race by writing localStorage synchronously on mutation Replace the useEffect-based save (which raced with the load effect) with an updateOutgoing() helper that writes to localStorage synchronously inside the setState updater. The load effect only reads; all mutations go through updateOutgoing which persists immediately. This guarantees the initial empty state never overwrites persisted data. --- .../Dashboard/Payments/RequestPaymentForm.tsx | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx b/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx index 4ebc7cf..551d5e9 100644 --- a/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx +++ b/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx @@ -106,37 +106,38 @@ export default function RequestPaymentForm({ wallet, onRequestSent }: Props) { const [sending, setSending] = useState(false) const [currencySymbol, setCurrencySymbol] = useState('$') - // Outgoing tracker — starts empty, loaded from localStorage once identity is known. + // Outgoing tracker — loaded from localStorage once identity is known. const [outgoing, setOutgoing] = useState([]) const [cancellingId, setCancellingId] = useState(null) const [receivingId, setReceivingId] = useState(null) - // Guard: the SAVE effect must not run until the LOAD effect has completed at least once. - // This prevents the initial empty state from overwriting persisted data in localStorage. - const hasLoadedRef = useRef(false) - // Load outgoing requests from localStorage when identity becomes available or changes. useEffect(() => { if (!storageKey) return - hasLoadedRef.current = false try { const raw = localStorage.getItem(storageKey) - const parsed = raw ? JSON.parse(raw) : [] - setOutgoing(parsed) + setOutgoing(raw ? JSON.parse(raw) : []) } catch { setOutgoing([]) } - hasLoadedRef.current = true }, [storageKey]) - // Persist outgoing requests to localStorage on every change. - // Only persist when we have a valid storage key (identity loaded). - // Exclude incomingPayment (contains transaction data too large for localStorage). - useEffect(() => { - if (!storageKey || !hasLoadedRef.current) return - const serializable = outgoing.map(({ incomingPayment, ...rest }) => rest) - localStorage.setItem(storageKey, JSON.stringify(serializable)) - }, [outgoing, storageKey]) + /** + * Persist-aware setter for outgoing requests. + * Writes to localStorage immediately when called, avoiding the + * race condition where a useEffect-based save overwrites data + * before the load effect's setOutgoing takes effect. + */ + const updateOutgoing = useCallback((updater: OutgoingRequest[] | ((prev: OutgoingRequest[]) => OutgoingRequest[])) => { + setOutgoing(prev => { + const next = typeof updater === 'function' ? updater(prev) : updater + if (storageKey) { + const serializable = next.map(({ incomingPayment, ...rest }) => rest) + localStorage.setItem(storageKey, JSON.stringify(serializable)) + } + return next + }) + }, [storageKey]) const currencyConverter = new CurrencyConverter(undefined, managers?.settingsManager as any) @@ -173,7 +174,7 @@ export default function RequestPaymentForm({ wallet, onRequestSent }: Props) { incomingPayments = await peerPayClient.listIncomingPayments(messageBoxUrl) } - setOutgoing(prev => prev.map(req => { + updateOutgoing(prev => prev.map(req => { // Don't update already received requests if (req.status === 'received') return req @@ -212,7 +213,7 @@ export default function RequestPaymentForm({ wallet, onRequestSent }: Props) { // Mark expired requests useEffect(() => { const tick = setInterval(() => { - setOutgoing(prev => + updateOutgoing(prev => prev.map(r => r.status === 'pending' && r.expiresAt < Date.now() ? { ...r, status: 'expired' } @@ -270,7 +271,7 @@ export default function RequestPaymentForm({ wallet, onRequestSent }: Props) { ? (identitySearch.selectedIdentity as IdentityOption).name || (identitySearch.selectedIdentity as IdentityOption).identityKey.slice(0, 14) : recipient.slice(0, 14) - setOutgoing(prev => [ + updateOutgoing(prev => [ { requestId, recipient: recipient.trim(), @@ -306,7 +307,7 @@ export default function RequestPaymentForm({ wallet, onRequestSent }: Props) { { recipient: req.recipient, requestId: req.requestId }, messageBoxUrl || undefined ) - setOutgoing(prev => + updateOutgoing(prev => prev.map(r => r.requestId === req.requestId ? { ...r, status: 'cancelled' } : r) ) toast.success('Request cancelled') @@ -323,7 +324,7 @@ export default function RequestPaymentForm({ wallet, onRequestSent }: Props) { try { setReceivingId(req.requestId) await peerPayClient.acceptPayment(req.incomingPayment) - setOutgoing(prev => + updateOutgoing(prev => prev.map(r => r.requestId === req.requestId ? { ...r, status: 'received' as const } : r) ) window.dispatchEvent(new CustomEvent('balance-changed')) From ccb2194a1ebbf777da077b9a3ee5828a93db05da Mon Sep 17 00:00:00 2001 From: Ishaan Lahoti <113637618+Quaakee@users.noreply.github.com> Date: Wed, 8 Apr 2026 10:22:16 -0500 Subject: [PATCH 19/22] fix: preserve payReq_ localStorage keys across logout The logout() function calls localStorage.clear() which wiped all payment request data (outgoing requests, whitelist, amount limits). Now saves and restores all keys prefixed with payReq_ around the clear, so request data persists across logout/login cycles. --- src/lib/WalletContext.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/lib/WalletContext.tsx b/src/lib/WalletContext.tsx index 54bb5f0..777b73d 100644 --- a/src/lib/WalletContext.tsx +++ b/src/lib/WalletContext.tsx @@ -2200,12 +2200,26 @@ export const WalletContextProvider: React.FC = ({ }, [peerPayClient, checkAnointmentStatus]); const logout = useCallback(() => { + // Preserve payment request data across logout (keyed by identity). + const preservedKeys: Record = {} + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i) + if (key && key.startsWith('payReq_')) { + preservedKeys[key] = localStorage.getItem(key) ?? '' + } + } + // Clear localStorage to prevent auto-login localStorage.clear(); if (localStorage.snap) { localStorage.removeItem('snap'); } + // Restore preserved payment request data + for (const [key, value] of Object.entries(preservedKeys)) { + localStorage.setItem(key, value) + } + // Reset manager state setManagers({}); From a284f5a2204022b1e1f8e6cee31da45565960396 Mon Sep 17 00:00:00 2001 From: Ishaan Lahoti <113637618+Quaakee@users.noreply.github.com> Date: Wed, 8 Apr 2026 10:28:22 -0500 Subject: [PATCH 20/22] feat: add Refresh button to Sent Requests section --- src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx b/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx index 551d5e9..ed843c5 100644 --- a/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx +++ b/src/lib/pages/Dashboard/Payments/RequestPaymentForm.tsx @@ -497,9 +497,10 @@ export default function RequestPaymentForm({ wallet, onRequestSent }: Props) { {/* ---- Outgoing tracker ---- */} {outgoing.length > 0 && ( - - Sent Requests - + + Sent Requests + + {outgoing.map(req => ( Date: Wed, 8 Apr 2026 12:03:09 -0500 Subject: [PATCH 21/22] fix: update payment request UI for message-box-client API changes - RequestPaymentForm: capture requestProof from requestPayment() return, store it in OutgoingRequest, pass it to cancelPaymentRequest() - IncomingRequestList: remove amount override from fulfillPaymentRequest() (API no longer supports it), remove "Amount to Pay" text field and unused payAmounts state Matches API changes in bsv-blockchain/message-box-client#38: - fulfillPaymentRequest no longer accepts amount override - cancelPaymentRequest now requires requestProof parameter - requestPayment now returns { requestId, requestProof } Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Dashboard/Payments/IncomingRequestList.tsx | 18 +----------------- .../Dashboard/Payments/RequestPaymentForm.tsx | 6 ++++-- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/src/lib/pages/Dashboard/Payments/IncomingRequestList.tsx b/src/lib/pages/Dashboard/Payments/IncomingRequestList.tsx index bf214e5..17f755a 100644 --- a/src/lib/pages/Dashboard/Payments/IncomingRequestList.tsx +++ b/src/lib/pages/Dashboard/Payments/IncomingRequestList.tsx @@ -78,7 +78,6 @@ export default function IncomingRequestList({ requests, onRefresh, wallet }: Pro const whitelistEnabledKey = `payReq_whitelistEnabled${idSuffix}` // Per-card state - const [payAmounts, setPayAmounts] = useState>({}) const [notes, setNotes] = useState>({}) const [paying, setPaying] = useState({}) const [declining, setDeclining] = useState({}) @@ -190,11 +189,9 @@ export default function IncomingRequestList({ requests, onRefresh, wallet }: Pro const id = req.messageId setPaying(prev => ({ ...prev, [id]: true })) try { - const rawAmt = payAmounts[id] - const overrideAmount = rawAmt ? parseInt(rawAmt, 10) : undefined const note = notes[id] || undefined await peerPayClient.fulfillPaymentRequest( - { request: req, amount: overrideAmount, note }, + { request: req, note }, messageBoxUrl || undefined ) window.dispatchEvent(new CustomEvent('balance-changed')) @@ -515,19 +512,6 @@ export default function IncomingRequestList({ requests, onRefresh, wallet }: Pro {!isExpired && ( <> - {/* Inline amount override */} - setPayAmounts(prev => ({ ...prev, [id]: e.target.value.replace(/[^0-9]/g, '') }))} - type="number" - inputProps={{ min: 1 }} - helperText="Leave blank to pay requested amount" - disabled={isBusy} - /> - {/* Optional note */} [ { requestId, + requestProof, recipient: recipient.trim(), recipientLabel: label, amount, @@ -304,7 +306,7 @@ export default function RequestPaymentForm({ wallet, onRequestSent }: Props) { try { setCancellingId(req.requestId) await peerPayClient.cancelPaymentRequest( - { recipient: req.recipient, requestId: req.requestId }, + { recipient: req.recipient, requestId: req.requestId, requestProof: req.requestProof }, messageBoxUrl || undefined ) updateOutgoing(prev => From 2768fbe08988c1aa503074e29ad3ebdf6e041bf6 Mon Sep 17 00:00:00 2001 From: Deggen Date: Thu, 9 Apr 2026 09:15:15 -0500 Subject: [PATCH 22/22] update deps Signed-off-by: Deggen --- package-lock.json | 675 ++++++++++++++++++++++++---------------------- package.json | 11 +- 2 files changed, 359 insertions(+), 327 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5204a53..e11a031 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "bsv-desktop-electron", - "version": "2.0.9", + "version": "2.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "bsv-desktop-electron", - "version": "2.0.9", + "version": "2.1.0", "license": "Apache-2.0", "dependencies": { "@bsv/amountinator": "^2.0.1", @@ -14,16 +14,17 @@ "@bsv/btms-permission-module": "^1.0.1", "@bsv/btms-permission-module-ui": "^1.0.0", "@bsv/identity-react": "^1.1.14", - "@bsv/message-box-client": "^2.0.6", - "@bsv/sdk": "^2.0.7", + "@bsv/message-box-client": "^2.0.7", + "@bsv/sdk": "^2.0.13", "@bsv/uhrp-react": "^1.0.6", - "@bsv/wallet-toolbox": "^2.1.5", - "@bsv/wallet-toolbox-client": "^2.1.5", + "@bsv/wallet-toolbox": "^2.1.19", + "@bsv/wallet-toolbox-client": "^2.1.19", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@mui/icons-material": "^6.4.8", "@mui/material": "^6.4.8", "@mui/styles": "^6.4.8", + "better-sqlite3": "^12.8.0", "cors": "^2.8.6", "date-fns": "^4.1.0", "dotenv": "^17.2.4", @@ -56,7 +57,7 @@ "@types/react-router-dom": "^5.3.3", "@vitejs/plugin-react": "^4.3.4", "concurrently": "^9.2.1", - "electron": "^38.8.0", + "electron": "^41.2.0", "electron-builder": "^26.0.12", "typescript": "~5.6.2", "vite": "^6.3.6", @@ -750,9 +751,9 @@ } }, "node_modules/@bsv/message-box-client": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@bsv/message-box-client/-/message-box-client-2.0.6.tgz", - "integrity": "sha512-8dkQ7Qo/DkdQmJOg8XdenvdBRmkcm5lDIMpT8ap4V9mQ1kDvWgkUcD6P5SIOcvfv8r0P2fDdOYW8kH8OnASJnA==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@bsv/message-box-client/-/message-box-client-2.0.7.tgz", + "integrity": "sha512-POhAoG0daA/+E0jUJLZLysvNDjDpMtwsD3GBaY/SyqR26euqIQMSJjrnCDnjhPWO7Et34rcaJNa46mllXgxENw==", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@bsv/authsocket-client": "^2.0.2", @@ -1095,16 +1096,17 @@ } }, "node_modules/@bsv/wallet-toolbox": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@bsv/wallet-toolbox/-/wallet-toolbox-2.1.5.tgz", - "integrity": "sha512-0VAgT1O16iqwQzyps5cmYzZf7rmVc7bhn1KF4yf+1crUu+30VHAqAjms7o5IGPsHDWSOfU8WwPrDXgMqFVYQUQ==", + "version": "2.1.19", + "resolved": "https://registry.npmjs.org/@bsv/wallet-toolbox/-/wallet-toolbox-2.1.19.tgz", + "integrity": "sha512-KTKh5GHHIByPJ/HerVnQMkMeX+an5naIH0YYk+0D1NFF+dYzxl9iZzMdArpVVR7/t3Z/4k0Ey1F9Yy3qDCiycA==", "license": "SEE LICENSE IN license.md", "dependencies": { "@bsv/auth-express-middleware": "^2.0.4", "@bsv/payment-express-middleware": "^2.0.1", - "@bsv/sdk": "^2.0.6", + "@bsv/sdk": "^2.0.13", "better-sqlite3": "^12.6.2", "express": "^4.21.2", + "hash-wasm": "^4.12.0", "idb": "^8.0.2", "knex": "^3.1.0", "mysql2": "^3.12.0", @@ -1112,12 +1114,13 @@ } }, "node_modules/@bsv/wallet-toolbox-client": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@bsv/wallet-toolbox-client/-/wallet-toolbox-client-2.1.5.tgz", - "integrity": "sha512-LZQpyQ2tCQF0ZCr8X5HkcxMTMdc/SU+IiEQ7xGBKajuDOXX27VPm6qe/cV9lNCgkV2fhArJqt/65LIMI3B6uZw==", + "version": "2.1.19", + "resolved": "https://registry.npmjs.org/@bsv/wallet-toolbox-client/-/wallet-toolbox-client-2.1.19.tgz", + "integrity": "sha512-uGHRUqRZsB/N3BwW7AFEXPAuap24JzPFG4hOUbtKmsLfUNnpjwMaLmAIYKJqmBPwkp7/f9UmNURxb1YgvJuQKQ==", "license": "SEE LICENSE IN license.md", "dependencies": { - "@bsv/sdk": "^2.0.0", + "@bsv/sdk": "^2.0.13", + "hash-wasm": "^4.12.0", "idb": "^8.0.2" } }, @@ -1158,9 +1161,9 @@ } }, "node_modules/@electron/asar/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -1415,9 +1418,9 @@ } }, "node_modules/@electron/rebuild/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "dependencies": { @@ -1448,16 +1451,6 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/@electron/rebuild/node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/@electron/rebuild/node_modules/fs-minipass": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", @@ -1534,13 +1527,13 @@ } }, "node_modules/@electron/rebuild/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -1590,19 +1583,6 @@ "encoding": "^0.1.13" } }, - "node_modules/@electron/rebuild/node_modules/minizlib": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/@electron/rebuild/node_modules/negotiator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", @@ -1690,23 +1670,6 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/@electron/rebuild/node_modules/tar": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz", - "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@electron/rebuild/node_modules/unique-filename": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", @@ -1749,16 +1712,6 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/@electron/rebuild/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/@electron/universal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.3.tgz", @@ -1779,9 +1732,9 @@ } }, "node_modules/@electron/universal/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "dependencies": { @@ -1817,13 +1770,13 @@ } }, "node_modules/@electron/universal/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -2496,29 +2449,6 @@ "node": ">=18" } }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", - "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -3149,9 +3079,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.3.tgz", - "integrity": "sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", + "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", "cpu": [ "arm" ], @@ -3163,9 +3093,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.3.tgz", - "integrity": "sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", + "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", "cpu": [ "arm64" ], @@ -3177,9 +3107,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.3.tgz", - "integrity": "sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", + "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", "cpu": [ "arm64" ], @@ -3191,9 +3121,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.3.tgz", - "integrity": "sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", + "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", "cpu": [ "x64" ], @@ -3205,9 +3135,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.3.tgz", - "integrity": "sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", + "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", "cpu": [ "arm64" ], @@ -3219,9 +3149,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.3.tgz", - "integrity": "sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", + "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", "cpu": [ "x64" ], @@ -3233,9 +3163,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.3.tgz", - "integrity": "sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", + "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", "cpu": [ "arm" ], @@ -3247,9 +3177,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.3.tgz", - "integrity": "sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", + "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", "cpu": [ "arm" ], @@ -3261,9 +3191,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.3.tgz", - "integrity": "sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", + "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", "cpu": [ "arm64" ], @@ -3275,9 +3205,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.3.tgz", - "integrity": "sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", + "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", "cpu": [ "arm64" ], @@ -3289,9 +3219,23 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.3.tgz", - "integrity": "sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", + "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", + "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", "cpu": [ "loong64" ], @@ -3303,9 +3247,23 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.3.tgz", - "integrity": "sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", + "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", + "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", "cpu": [ "ppc64" ], @@ -3317,9 +3275,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.3.tgz", - "integrity": "sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", + "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", "cpu": [ "riscv64" ], @@ -3331,9 +3289,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.3.tgz", - "integrity": "sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", + "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", "cpu": [ "riscv64" ], @@ -3345,9 +3303,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.3.tgz", - "integrity": "sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", + "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", "cpu": [ "s390x" ], @@ -3359,9 +3317,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.3.tgz", - "integrity": "sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", + "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", "cpu": [ "x64" ], @@ -3373,9 +3331,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.3.tgz", - "integrity": "sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", + "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", "cpu": [ "x64" ], @@ -3386,10 +3344,24 @@ "linux" ] }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", + "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.3.tgz", - "integrity": "sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", + "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", "cpu": [ "arm64" ], @@ -3401,9 +3373,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.3.tgz", - "integrity": "sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", + "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", "cpu": [ "arm64" ], @@ -3415,9 +3387,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.3.tgz", - "integrity": "sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", + "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", "cpu": [ "ia32" ], @@ -3429,9 +3401,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.3.tgz", - "integrity": "sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", + "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", "cpu": [ "x64" ], @@ -3443,9 +3415,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.3.tgz", - "integrity": "sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", + "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", "cpu": [ "x64" ], @@ -4061,9 +4033,9 @@ } }, "node_modules/@xmldom/xmldom": { - "version": "0.8.11", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz", - "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==", + "version": "0.8.12", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.12.tgz", + "integrity": "sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg==", "dev": true, "license": "MIT", "engines": { @@ -4101,9 +4073,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", "dependencies": { @@ -4258,16 +4230,6 @@ "semver": "bin/semver.js" } }, - "node_modules/app-builder-lib/node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/app-builder-lib/node_modules/ci-info": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", @@ -4345,29 +4307,6 @@ "node": ">=16" } }, - "node_modules/app-builder-lib/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/app-builder-lib/node_modules/minizlib": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/app-builder-lib/node_modules/semver": { "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", @@ -4381,23 +4320,6 @@ "node": ">=10" } }, - "node_modules/app-builder-lib/node_modules/tar": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz", - "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/app-builder-lib/node_modules/which": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", @@ -4414,16 +4336,6 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/app-builder-lib/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -4564,9 +4476,9 @@ } }, "node_modules/better-sqlite3": { - "version": "12.6.2", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.6.2.tgz", - "integrity": "sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA==", + "version": "12.8.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.8.0.tgz", + "integrity": "sha512-RxD2Vd96sQDjQr20kdP+F+dK/1OUNiVOl200vKBZY8u0vTwysfolF6Hq+3ZK2+h8My9YvZhHsF+RSGZW2VYrPQ==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -4687,9 +4599,9 @@ "optional": true }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -5323,9 +5235,9 @@ } }, "node_modules/cosmiconfig/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "license": "ISC", "engines": { "node": ">= 6" @@ -5601,9 +5513,9 @@ } }, "node_modules/dir-compare/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -5790,15 +5702,15 @@ } }, "node_modules/electron": { - "version": "38.8.2", - "resolved": "https://registry.npmjs.org/electron/-/electron-38.8.2.tgz", - "integrity": "sha512-EvXHgoy7NJ/O1mg6ZIEq5jgYnm1LHy4dNDMh/jhiJz6rAOH9VKDfXhx7Y65LYaTyntukNQhS90KBY3zk4t4+ow==", + "version": "41.2.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-41.2.0.tgz", + "integrity": "sha512-0OKLiymqfV0WK68RBXqAm3Myad2TpI5wwxLCBEUcH5Nugo3YfSk7p1Js/AL9266qTz5xZioUnxt9hG8FFwax0g==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { "@electron/get": "^2.0.0", - "@types/node": "^22.7.7", + "@types/node": "^24.9.0", "extract-zip": "^2.0.1" }, "bin": { @@ -6073,6 +5985,23 @@ "node": ">=6 <7 || >=8" } }, + "node_modules/electron/node_modules/@types/node": { + "version": "24.12.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", + "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/electron/node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -6532,9 +6461,9 @@ } }, "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "dependencies": { @@ -6542,9 +6471,9 @@ } }, "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "dev": true, "license": "ISC", "dependencies": { @@ -6869,9 +6798,9 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -7027,6 +6956,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hash-wasm": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/hash-wasm/-/hash-wasm-4.12.0.tgz", + "integrity": "sha512-+/2B2rYLb48I/evdOIhP+K/DD2ca2fgBjp6O+GBEnCDk2e4rpeXIK8GvIyRPjTezgmWn9gmKwkQjjx6BtqDHVQ==", + "license": "MIT" + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -7546,9 +7481,9 @@ } }, "node_modules/jest-util/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "license": "MIT", "engines": { "node": ">=8.6" @@ -7859,9 +7794,9 @@ "license": "MIT" }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, "node_modules/lodash.escaperegexp": { @@ -8317,9 +8252,9 @@ } }, "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "license": "MIT", "engines": { "node": ">=8.6" @@ -8383,21 +8318,44 @@ } }, "node_modules/minimatch": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.2.tgz", - "integrity": "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/brace-expansion": "^5.0.1" + "brace-expansion": "^5.0.5" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minimatch/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/minimatch/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -8466,6 +8424,29 @@ "dev": true, "license": "ISC" }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", @@ -8663,9 +8644,9 @@ } }, "node_modules/node-forge": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz", - "integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", + "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", "license": "(BSD-3-Clause OR GPL-2.0)", "engines": { "node": ">= 6.13.0" @@ -8943,9 +8924,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", "license": "MIT" }, "node_modules/path-type": { @@ -9009,9 +8990,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -9260,9 +9241,9 @@ } }, "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -9665,9 +9646,9 @@ } }, "node_modules/rollup": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.3.tgz", - "integrity": "sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", + "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", "dev": true, "license": "MIT", "dependencies": { @@ -9681,28 +9662,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.52.3", - "@rollup/rollup-android-arm64": "4.52.3", - "@rollup/rollup-darwin-arm64": "4.52.3", - "@rollup/rollup-darwin-x64": "4.52.3", - "@rollup/rollup-freebsd-arm64": "4.52.3", - "@rollup/rollup-freebsd-x64": "4.52.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.52.3", - "@rollup/rollup-linux-arm-musleabihf": "4.52.3", - "@rollup/rollup-linux-arm64-gnu": "4.52.3", - "@rollup/rollup-linux-arm64-musl": "4.52.3", - "@rollup/rollup-linux-loong64-gnu": "4.52.3", - "@rollup/rollup-linux-ppc64-gnu": "4.52.3", - "@rollup/rollup-linux-riscv64-gnu": "4.52.3", - "@rollup/rollup-linux-riscv64-musl": "4.52.3", - "@rollup/rollup-linux-s390x-gnu": "4.52.3", - "@rollup/rollup-linux-x64-gnu": "4.52.3", - "@rollup/rollup-linux-x64-musl": "4.52.3", - "@rollup/rollup-openharmony-arm64": "4.52.3", - "@rollup/rollup-win32-arm64-msvc": "4.52.3", - "@rollup/rollup-win32-ia32-msvc": "4.52.3", - "@rollup/rollup-win32-x64-gnu": "4.52.3", - "@rollup/rollup-win32-x64-msvc": "4.52.3", + "@rollup/rollup-android-arm-eabi": "4.60.1", + "@rollup/rollup-android-arm64": "4.60.1", + "@rollup/rollup-darwin-arm64": "4.60.1", + "@rollup/rollup-darwin-x64": "4.60.1", + "@rollup/rollup-freebsd-arm64": "4.60.1", + "@rollup/rollup-freebsd-x64": "4.60.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", + "@rollup/rollup-linux-arm-musleabihf": "4.60.1", + "@rollup/rollup-linux-arm64-gnu": "4.60.1", + "@rollup/rollup-linux-arm64-musl": "4.60.1", + "@rollup/rollup-linux-loong64-gnu": "4.60.1", + "@rollup/rollup-linux-loong64-musl": "4.60.1", + "@rollup/rollup-linux-ppc64-gnu": "4.60.1", + "@rollup/rollup-linux-ppc64-musl": "4.60.1", + "@rollup/rollup-linux-riscv64-gnu": "4.60.1", + "@rollup/rollup-linux-riscv64-musl": "4.60.1", + "@rollup/rollup-linux-s390x-gnu": "4.60.1", + "@rollup/rollup-linux-x64-gnu": "4.60.1", + "@rollup/rollup-linux-x64-musl": "4.60.1", + "@rollup/rollup-openbsd-x64": "4.60.1", + "@rollup/rollup-openharmony-arm64": "4.60.1", + "@rollup/rollup-win32-arm64-msvc": "4.60.1", + "@rollup/rollup-win32-ia32-msvc": "4.60.1", + "@rollup/rollup-win32-x64-gnu": "4.60.1", + "@rollup/rollup-win32-x64-msvc": "4.60.1", "fsevents": "~2.3.2" } }, @@ -9723,9 +9707,9 @@ } }, "node_modules/router/node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", "license": "MIT", "funding": { "type": "opencollective", @@ -10440,6 +10424,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tar": { + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz", + "integrity": "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/tar-fs": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", @@ -10468,6 +10469,36 @@ "node": ">=6" } }, + "node_modules/tar/node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/tarn": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz", @@ -10937,9 +10968,9 @@ } }, "node_modules/vite": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", - "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 78874ba..983dd5f 100644 --- a/package.json +++ b/package.json @@ -28,16 +28,17 @@ "@bsv/btms-permission-module": "^1.0.1", "@bsv/btms-permission-module-ui": "^1.0.0", "@bsv/identity-react": "^1.1.14", - "@bsv/message-box-client": "^2.0.6", - "@bsv/sdk": "^2.0.7", + "@bsv/message-box-client": "^2.0.7", + "@bsv/sdk": "^2.0.13", "@bsv/uhrp-react": "^1.0.6", - "@bsv/wallet-toolbox": "^2.1.5", - "@bsv/wallet-toolbox-client": "^2.1.5", + "@bsv/wallet-toolbox": "^2.1.19", + "@bsv/wallet-toolbox-client": "^2.1.19", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@mui/icons-material": "^6.4.8", "@mui/material": "^6.4.8", "@mui/styles": "^6.4.8", + "better-sqlite3": "^12.8.0", "cors": "^2.8.6", "date-fns": "^4.1.0", "dotenv": "^17.2.4", @@ -70,7 +71,7 @@ "@types/react-router-dom": "^5.3.3", "@vitejs/plugin-react": "^4.3.4", "concurrently": "^9.2.1", - "electron": "^38.8.0", + "electron": "^41.2.0", "electron-builder": "^26.0.12", "typescript": "~5.6.2", "vite": "^6.3.6",