mirror of
https://github.com/minibits-cash/minibits_wallet.git
synced 2026-10-05 11:18:24 +00:00
Uograde cashu-ts to 4.11
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* The cashu-ts contract that the websocket->poller fallbacks rest on.
|
||||
*
|
||||
* `_monitorSentProofs` (sendOperationApi), `_monitorAsyncMeltQuote`
|
||||
* (transferOperationApi) and the mint-quote watcher (topupOperationApi) all
|
||||
* subscribe over NUT-17 and fall back to a poller when the websocket cannot
|
||||
* carry the update. The subscribe call failing is the easy half, and a plain
|
||||
* try/catch covers it. The other half is an ESTABLISHED subscription dying
|
||||
* later: mints and reverse proxies idle out long-lived sockets, and a sent
|
||||
* token or a pending melt quote routinely outlives one.
|
||||
*
|
||||
* Up to cashu-ts 4.10 that case was UNREPORTABLE. `createSubscription` passed
|
||||
* the error callback only to `addRpcListener`, which covers the subscribe RPC
|
||||
* itself; `addSubListener(subId, callback)` took no error callback at all, so
|
||||
* once the subscription was live nothing held a way to signal it. A socket
|
||||
* close ran `stopMessageHandling()`, which drained the queue and told nobody.
|
||||
* The subscription simply went quiet and the transaction hung.
|
||||
*
|
||||
* 4.11 (#1252) stores the error callback per subscription and fans it out from
|
||||
* `stopMessageHandling(err)`. That is what lets the fallback start, and it is
|
||||
* the entire reason the pollers in those three functions are hoisted out of
|
||||
* the setup `catch` into a shared `startPoller()` reachable from both paths.
|
||||
*
|
||||
* If a future cashu-ts stops invoking the error callback on close, those three
|
||||
* fallbacks go silent again with no other test failing. This pins it at the
|
||||
* WSConnection level — the layer the `wallet.on.*` helpers are built on — so
|
||||
* the assertion is about the library contract rather than our wiring.
|
||||
*
|
||||
* Deterministic and offline: the WebSocket implementation is injected.
|
||||
*
|
||||
* @jest-environment node
|
||||
*/
|
||||
import {WSConnection, injectWebSocketImpl} from '@cashu/cashu-ts'
|
||||
|
||||
type CloseInit = {code: number; reason: string; wasClean: boolean}
|
||||
|
||||
/** Minimal WebSocket double: opens next tick, acks `subscribe`, closes on demand. */
|
||||
class FakeWebSocket {
|
||||
static instances: FakeWebSocket[] = []
|
||||
static OPEN = 1
|
||||
|
||||
onopen?: () => void
|
||||
onclose?: (e: CloseInit) => void
|
||||
onmessage?: (e: {data: string}) => void
|
||||
onerror?: (e: unknown) => void
|
||||
readyState = 0
|
||||
sent: string[] = []
|
||||
|
||||
constructor(public url: string) {
|
||||
FakeWebSocket.instances.push(this)
|
||||
setTimeout(() => {
|
||||
this.readyState = FakeWebSocket.OPEN
|
||||
this.onopen?.()
|
||||
}, 0)
|
||||
}
|
||||
|
||||
send(raw: string) {
|
||||
this.sent.push(raw)
|
||||
const msg = JSON.parse(raw)
|
||||
if (msg.method === 'subscribe') {
|
||||
// The sub listener is only registered once this ack lands.
|
||||
setTimeout(
|
||||
() =>
|
||||
this.onmessage?.({
|
||||
data: JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
result: {status: 'OK', subId: msg.params.subId},
|
||||
id: msg.id,
|
||||
}),
|
||||
}),
|
||||
0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
this.readyState = 3
|
||||
}
|
||||
|
||||
/** What a mint or proxy does to an idle connection. */
|
||||
dropRemote(init: Partial<CloseInit> = {}) {
|
||||
this.readyState = 3
|
||||
this.onclose?.({code: 1006, reason: 'idle timeout', wasClean: false, ...init})
|
||||
}
|
||||
}
|
||||
|
||||
const tick = () => new Promise(resolve => setTimeout(resolve, 5))
|
||||
|
||||
async function establishedSubscription() {
|
||||
const ws = new WSConnection('wss://mint.test/v1/ws')
|
||||
await ws.connect()
|
||||
|
||||
const updates: unknown[] = []
|
||||
const errors: Error[] = []
|
||||
ws.createSubscription(
|
||||
{kind: 'proof_state', filters: ['deadbeef']} as never,
|
||||
payload => updates.push(payload),
|
||||
error => errors.push(error),
|
||||
)
|
||||
await tick()
|
||||
|
||||
const socket = FakeWebSocket.instances[FakeWebSocket.instances.length - 1]
|
||||
return {ws, socket, updates, errors}
|
||||
}
|
||||
|
||||
describe('an established NUT-17 subscription reports a socket close', () => {
|
||||
beforeEach(() => {
|
||||
FakeWebSocket.instances = []
|
||||
injectWebSocketImpl(FakeWebSocket as never)
|
||||
})
|
||||
|
||||
it('PRECONDITION: the subscription is live before the socket drops', async () => {
|
||||
const {ws, errors} = await establishedSubscription()
|
||||
|
||||
expect(ws.activeSubscriptions).toHaveLength(1)
|
||||
expect(errors).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('invokes the error callback when the mint drops the connection', async () => {
|
||||
const {socket, errors} = await establishedSubscription()
|
||||
|
||||
socket.dropRemote()
|
||||
await tick()
|
||||
|
||||
// This is the signal the three operation-api fallbacks start their
|
||||
// poller from. Before 4.11 it never arrived and they hung instead.
|
||||
expect(errors).toHaveLength(1)
|
||||
expect(errors[0]).toBeInstanceOf(Error)
|
||||
expect(errors[0].message).toMatch(/closed/i)
|
||||
})
|
||||
|
||||
it('drops the subscription so a later close cannot fire it twice', async () => {
|
||||
const {ws, socket, errors} = await establishedSubscription()
|
||||
|
||||
socket.dropRemote()
|
||||
await tick()
|
||||
socket.dropRemote()
|
||||
await tick()
|
||||
|
||||
expect(errors).toHaveLength(1)
|
||||
expect(ws.activeSubscriptions).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('reports a CLEAN close too — idle teardown is not a success signal', async () => {
|
||||
const {socket, errors} = await establishedSubscription()
|
||||
|
||||
socket.dropRemote({code: 1000, reason: '', wasClean: true})
|
||||
await tick()
|
||||
|
||||
expect(errors).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
+2
-2
@@ -22,7 +22,7 @@
|
||||
"machine-translate": "npx @inlang/cli machine translate --project minibits.inlang"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cashu/cashu-ts": "^4.10.0",
|
||||
"@cashu/cashu-ts": "^4.11.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^7.1.0",
|
||||
"@fortawesome/free-brands-svg-icons": "^7.1.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^7.1.0",
|
||||
@@ -43,7 +43,7 @@
|
||||
"@react-navigation/native": "^7.1.18",
|
||||
"@react-navigation/native-stack": "^7.3.27",
|
||||
"@react-navigation/stack": "^7.4.9",
|
||||
"@scure/bip32": "^2.2.0",
|
||||
"@scure/bip32": "^2.4.0",
|
||||
"@scure/bip39": "2.0.1",
|
||||
"@sentry/react-native": "^7.7.0",
|
||||
"date-fns": "^4.1.0",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/node_modules/@scure/bip32/index.js b/node_modules/@scure/bip32/index.js
|
||||
index 8e49612..d54da85 100644
|
||||
index 49b54b8..a66540e 100644
|
||||
--- a/node_modules/@scure/bip32/index.js
|
||||
+++ b/node_modules/@scure/bip32/index.js
|
||||
@@ -28,6 +28,7 @@ import { ripemd160 } from '@noble/hashes/legacy.js';
|
||||
@@ -10,7 +10,7 @@ index 8e49612..d54da85 100644
|
||||
const Point = /* @__PURE__ */ (() => secp.Point)();
|
||||
const Fn = /* @__PURE__ */ (() => Point.Fn)();
|
||||
const base58check = /* @__PURE__ */ createBase58check(sha256);
|
||||
@@ -102,7 +103,8 @@ export class HDKey {
|
||||
@@ -112,7 +113,8 @@ export class HDKey {
|
||||
throw new RangeError('HDKey: seed length must be between 128 and 512 bits; 256 bits is advised, got ' +
|
||||
seed.length);
|
||||
}
|
||||
@@ -20,13 +20,13 @@ index 8e49612..d54da85 100644
|
||||
const privateKey = I.slice(0, 32);
|
||||
const chainCode = I.slice(32);
|
||||
return new HDKey({ versions, chainCode, privateKey });
|
||||
@@ -225,7 +227,8 @@ export class HDKey {
|
||||
@@ -248,7 +250,8 @@ export class HDKey {
|
||||
// Normal child: serP(point(kpar)) || ser32(index)
|
||||
data = concatBytes(this._publicKey, data);
|
||||
}
|
||||
- const out = _I || hmac(sha512, this.chainCode, data);
|
||||
+ //const out = _I || hmac(sha512, this.chainCode, data);
|
||||
+ const out = _I || new Uint8Array(quickCrypto.createHmac('sha512', this.chainCode).update(data).digest());
|
||||
- const out = _I || hmac(sha512, this._chainCode, data);
|
||||
+ //const out = _I || hmac(sha512, this._chainCode, data);
|
||||
+ const out = _I || new Uint8Array(quickCrypto.createHmac('sha512', this._chainCode).update(data).digest());
|
||||
abytes(out, 64);
|
||||
const childTweak = out.slice(0, 32);
|
||||
const chainCode = out.slice(32);
|
||||
@@ -739,6 +739,25 @@ async function _monitorSentProofs(params: {mintUrl: string; proofsToSend: CashuP
|
||||
const wsMint = new CashuMint(mintUrl)
|
||||
const wsWallet = new CashuWallet(wsMint)
|
||||
|
||||
// The websocket is the fast path; the poller is the fallback that has to run
|
||||
// whenever it is not available. Two things must reach it: the subscribe failing
|
||||
// outright, and an ESTABLISHED socket closing later — mints and proxies idle out
|
||||
// long-lived subscriptions. cashu-ts reports the second case through the error
|
||||
// callback (>= 4.11; before that an established subscription just went silent),
|
||||
// so the fallback cannot live in the catch below — that catch has long since
|
||||
// been left by then.
|
||||
let settled = false
|
||||
const startPoller = () => {
|
||||
if (settled) return
|
||||
settled = true
|
||||
poller(
|
||||
`syncStateWithMintPoller-${mintUrl}`,
|
||||
WalletTask.syncStateWithMintQueueAwaitable,
|
||||
{interval: 10 * 1000, maxPolls: 3, maxErrors: 1},
|
||||
{proofsToSync, mintUrl, proofState: 'PENDING' as const},
|
||||
).then(() => log.trace('[SendOperationApi]', 'polling completed', {mintUrl}))
|
||||
}
|
||||
|
||||
try {
|
||||
log.trace('[SendOperationApi]', 'Subscribing to proofStateUpdates', {secret: proofsToSend[0]?.secret})
|
||||
const unsub = await wsWallet.on.proofStateUpdates(
|
||||
@@ -747,21 +766,21 @@ async function _monitorSentProofs(params: {mintUrl: string; proofsToSend: CashuP
|
||||
log.trace(`[SendOperationApi] Websocket: proof state updated: ${proofState.state} with secret: ${proofsToSend[0].secret}`)
|
||||
if (proofState.state === CheckStateEnum.SPENT) {
|
||||
WalletTask.syncStateWithMintQueueAwaitable({proofsToSync, mintUrl, proofState: 'PENDING'})
|
||||
settled = true
|
||||
unsub()
|
||||
}
|
||||
},
|
||||
async (error: any) => {
|
||||
throw error
|
||||
log.warn(
|
||||
'[SendOperationApi] Proof state subscription closed, falling back to poller',
|
||||
{mintUrl, error: error?.message},
|
||||
)
|
||||
startPoller()
|
||||
},
|
||||
)
|
||||
} catch (error: any) {
|
||||
log.error(Err.NETWORK_ERROR, 'WebSocket subscription failed. Starting poller.', error.message)
|
||||
poller(
|
||||
`syncStateWithMintPoller-${mintUrl}`,
|
||||
WalletTask.syncStateWithMintQueueAwaitable,
|
||||
{interval: 10 * 1000, maxPolls: 3, maxErrors: 1},
|
||||
{proofsToSync, mintUrl, proofState: 'PENDING' as const},
|
||||
).then(() => log.trace('[SendOperationApi]', 'polling completed', {mintUrl}))
|
||||
startPoller()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -585,6 +585,24 @@ async function _monitorMintQuote(params: {
|
||||
MintOperationService.enqueuePendingTopupCheck(tx)
|
||||
}
|
||||
|
||||
// The websocket is the fast path; the poller is the fallback that has to run
|
||||
// whenever it is not available. Two things must reach it: the subscribe failing
|
||||
// outright, and an ESTABLISHED socket closing later — mints and proxies idle out
|
||||
// long-lived subscriptions. cashu-ts reports the second case through the error
|
||||
// callback (>= 4.11; before that an established subscription just went silent),
|
||||
// so the fallback cannot live in the catch below — that catch has long since
|
||||
// been left by then.
|
||||
let settled = false
|
||||
const startPoller = () => {
|
||||
if (settled) return
|
||||
settled = true
|
||||
poller(
|
||||
`handlePendingTopupPoller-${paymentHash}`,
|
||||
enqueueRefresh,
|
||||
{interval: 10 * 1000, maxPolls: 6, maxErrors: 2},
|
||||
).then(() => log.trace('[handlePendingTopupPoller] polling completed', {quote}))
|
||||
}
|
||||
|
||||
try {
|
||||
log.trace('[TopupOperationApi]', 'Subscribing to mintQuotePaid', {quote})
|
||||
const unsub = await wsWallet.on.mintQuotePaid(
|
||||
@@ -598,10 +616,15 @@ async function _monitorMintQuote(params: {
|
||||
{transactionId, error: e.message},
|
||||
)
|
||||
}
|
||||
settled = true
|
||||
unsub()
|
||||
},
|
||||
async (error: any) => {
|
||||
throw error
|
||||
log.warn(
|
||||
'[TopupOperationApi] Mint quote subscription closed, falling back to poller',
|
||||
{quote, transactionId, error: error?.message},
|
||||
)
|
||||
startPoller()
|
||||
},
|
||||
)
|
||||
} catch (error: any) {
|
||||
@@ -610,11 +633,7 @@ async function _monitorMintQuote(params: {
|
||||
'[TopupOperationApi] WebSocket error for mint quote, starting poller.',
|
||||
error.message,
|
||||
)
|
||||
poller(
|
||||
`handlePendingTopupPoller-${paymentHash}`,
|
||||
enqueueRefresh,
|
||||
{interval: 10 * 1000, maxPolls: 6, maxErrors: 2},
|
||||
).then(() => log.trace('[handlePendingTopupPoller] polling completed', {quote}))
|
||||
startPoller()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1340,6 +1340,24 @@ async function _monitorAsyncMeltQuote(params: {
|
||||
const wsMint = new CashuMint(mintUrl)
|
||||
const wsWallet = new CashuWallet(wsMint)
|
||||
|
||||
// The websocket is the fast path; the poller is what actually resolves the tx
|
||||
// when it is not available. Two things must reach it: the subscribe failing
|
||||
// outright, and an ESTABLISHED socket closing later — mints and proxies idle out
|
||||
// long-lived subscriptions, and a melt quote can stay pending for minutes.
|
||||
// cashu-ts reports the second case through the error callback (>= 4.11; before
|
||||
// that an established subscription just went silent), so the fallback cannot
|
||||
// live in the catch below — that catch has long since been left by then.
|
||||
let settled = false
|
||||
const startPoller = () => {
|
||||
if (settled) return
|
||||
settled = true
|
||||
poller(
|
||||
`meltQuotePoller-${quoteId}`,
|
||||
() => refresh(transactionId),
|
||||
{interval: 15 * 1000, maxPolls: 8, maxErrors: 2},
|
||||
).then(() => log.trace('[meltQuotePoller] polling completed', {quoteId}))
|
||||
}
|
||||
|
||||
try {
|
||||
log.trace('[TransferOperationApi]', 'Subscribing to meltQuoteUpdates', {quoteId})
|
||||
const unsub = await wsWallet.on.meltQuoteUpdates(
|
||||
@@ -1357,11 +1375,16 @@ async function _monitorAsyncMeltQuote(params: {
|
||||
{transactionId, error: refreshError.message},
|
||||
)
|
||||
}
|
||||
settled = true
|
||||
unsub()
|
||||
}
|
||||
},
|
||||
async (error: any) => {
|
||||
throw error
|
||||
log.warn(
|
||||
'[TransferOperationApi] Melt quote subscription closed, falling back to poller',
|
||||
{quoteId, transactionId, error: error?.message},
|
||||
)
|
||||
startPoller()
|
||||
},
|
||||
)
|
||||
} catch (error: any) {
|
||||
@@ -1370,11 +1393,7 @@ async function _monitorAsyncMeltQuote(params: {
|
||||
'[TransferOperationApi] WebSocket error for async melt, starting poller.',
|
||||
error.message,
|
||||
)
|
||||
poller(
|
||||
`meltQuotePoller-${quoteId}`,
|
||||
() => refresh(transactionId),
|
||||
{interval: 15 * 1000, maxPolls: 8, maxErrors: 2},
|
||||
).then(() => log.trace('[meltQuotePoller] polling completed', {quoteId}))
|
||||
startPoller()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2869,15 +2869,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@cashu/cashu-ts@npm:^4.10.0":
|
||||
version: 4.10.0
|
||||
resolution: "@cashu/cashu-ts@npm:4.10.0"
|
||||
"@cashu/cashu-ts@npm:^4.11.0":
|
||||
version: 4.11.0
|
||||
resolution: "@cashu/cashu-ts@npm:4.11.0"
|
||||
dependencies:
|
||||
"@noble/curves": ^2.2.0
|
||||
"@noble/hashes": ^2.2.0
|
||||
"@scure/base": ^2.2.0
|
||||
"@scure/bip32": ^2.2.0
|
||||
checksum: 55994116585f64acf6078e000ca747e5d082d307779165c9a1aece0803a6d2f1d1c3d9c0c5616cb1b3df8ac12ca2175d03c42f59d08ef3df21ab13c79ecc6b79
|
||||
"@noble/curves": ^2.4.0
|
||||
"@noble/hashes": ^2.4.0
|
||||
"@scure/base": ^2.4.0
|
||||
"@scure/bip32": ^2.4.0
|
||||
checksum: 1adfa648fdb36bd6308e600535f6d1a1d258fbaeada371709e42a7aade425dd5ff0eb844bec09a29da79aad226ad7c4377fe3abcab6f8614e0371144ae4a2709
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -5449,12 +5449,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@noble/curves@npm:2.2.0, @noble/curves@npm:^2.2.0":
|
||||
version: 2.2.0
|
||||
resolution: "@noble/curves@npm:2.2.0"
|
||||
"@noble/curves@npm:2.4.0, @noble/curves@npm:^2.4.0":
|
||||
version: 2.4.0
|
||||
resolution: "@noble/curves@npm:2.4.0"
|
||||
dependencies:
|
||||
"@noble/hashes": 2.2.0
|
||||
checksum: 2b6f02c18918578f528791644886f54333c67323f5ccefe10bf250f08760f63786b807390d8d5f8562c661e70dd3559ce8e51e1fe360118c8fd47071c2bd6991
|
||||
"@noble/hashes": 2.4.0
|
||||
checksum: 3e1d2a57efea01bd76cd2e0d711446b67ac4124988f4abe8b4b849f04a9d52b7fa1e7fa7d8ed94151ca31cb09c0bc7e00b77a057a7d99f114a0d45f8ead6d71e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -5465,10 +5465,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@noble/hashes@npm:2.2.0, @noble/hashes@npm:^2.2.0":
|
||||
version: 2.2.0
|
||||
resolution: "@noble/hashes@npm:2.2.0"
|
||||
checksum: a8745fb5da57a73ddd3dd6d5ad45d156ea664be51ead5344ca1528f8e56ad0ba0720ec8adff3bda37c50c72a86a33a57332ec02c3fb8d2a2612f9c3e5e7079ab
|
||||
"@noble/hashes@npm:2.4.0, @noble/hashes@npm:^2.4.0":
|
||||
version: 2.4.0
|
||||
resolution: "@noble/hashes@npm:2.4.0"
|
||||
checksum: 0eaf273e341c53ebf7de3e6dcc27755c2124da04152549a61580a525929844e05a7799aeeb9a6a46943cde59ff297ebe6d61968669b162577db188c62b948efe
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -6463,10 +6463,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@scure/base@npm:2.2.0, @scure/base@npm:^2.2.0":
|
||||
version: 2.2.0
|
||||
resolution: "@scure/base@npm:2.2.0"
|
||||
checksum: 2dd91f310765366e2dcb5e37709eb35ccd57d3f2f112d36232ca03fb8819c8764708f333ac40935fa719ff2e943bd50e1912ab43d212e44e807100d21f835f13
|
||||
"@scure/base@npm:2.4.0, @scure/base@npm:^2.4.0":
|
||||
version: 2.4.0
|
||||
resolution: "@scure/base@npm:2.4.0"
|
||||
checksum: 95014a31c1c0fc8d70913031535060deac181aef10cd84cacbc0bd394db58df22fd35ce7b14a73d34e158b472094fd92e614613598aa03378ffeb8967b5b007c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -6481,14 +6481,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@scure/bip32@npm:^2.2.0":
|
||||
version: 2.2.0
|
||||
resolution: "@scure/bip32@npm:2.2.0"
|
||||
"@scure/bip32@npm:^2.4.0":
|
||||
version: 2.4.0
|
||||
resolution: "@scure/bip32@npm:2.4.0"
|
||||
dependencies:
|
||||
"@noble/curves": 2.2.0
|
||||
"@noble/hashes": 2.2.0
|
||||
"@scure/base": 2.2.0
|
||||
checksum: 6cd0ddf603b739d3c24a13618fc246d409bbf0dffec22bc2c3c8e3e64271bb58c7078bea0b6ab9601fcfe881a969ee31e3074d478affe13a439d7527a0b68ecf
|
||||
"@noble/curves": 2.4.0
|
||||
"@noble/hashes": 2.4.0
|
||||
"@scure/base": 2.4.0
|
||||
checksum: fabadcab4673e17c3702bca8037f847f0c6e90b7bc751424de740c26f5de432bac957716dbd16fb775005db1701fd6e220cf8adc17a46d5e355b7086969faf39
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -14762,7 +14762,7 @@ __metadata:
|
||||
"@babel/plugin-proposal-export-namespace-from": ^7.18.9
|
||||
"@babel/preset-env": ^7.25.3
|
||||
"@babel/runtime": ^7.25.0
|
||||
"@cashu/cashu-ts": ^4.10.0
|
||||
"@cashu/cashu-ts": ^4.11.0
|
||||
"@expo/fingerprint": ^0.13.4
|
||||
"@fortawesome/fontawesome-svg-core": ^7.1.0
|
||||
"@fortawesome/free-brands-svg-icons": ^7.1.0
|
||||
@@ -14794,7 +14794,7 @@ __metadata:
|
||||
"@react-navigation/native": ^7.1.18
|
||||
"@react-navigation/native-stack": ^7.3.27
|
||||
"@react-navigation/stack": ^7.4.9
|
||||
"@scure/bip32": ^2.2.0
|
||||
"@scure/bip32": ^2.4.0
|
||||
"@scure/bip39": 2.0.1
|
||||
"@sentry/cli": ^2.56.0
|
||||
"@sentry/react-native": ^7.7.0
|
||||
|
||||
Reference in New Issue
Block a user