mirror of
https://github.com/minibits-cash/minibits_wallet.git
synced 2026-07-22 07:48:28 +00:00
Upgrade to cashu-ts v4
This commit is contained in:
@@ -22,7 +22,7 @@
|
|||||||
"machine-translate": "npx @inlang/cli machine translate --project minibits.inlang"
|
"machine-translate": "npx @inlang/cli machine translate --project minibits.inlang"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cashu/cashu-ts": "^3.6.1",
|
"@cashu/cashu-ts": "^4.2.1",
|
||||||
"@fortawesome/fontawesome-svg-core": "^7.1.0",
|
"@fortawesome/fontawesome-svg-core": "^7.1.0",
|
||||||
"@fortawesome/free-brands-svg-icons": "^7.1.0",
|
"@fortawesome/free-brands-svg-icons": "^7.1.0",
|
||||||
"@fortawesome/free-regular-svg-icons": "^7.1.0",
|
"@fortawesome/free-regular-svg-icons": "^7.1.0",
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
"@react-navigation/native": "^7.1.18",
|
"@react-navigation/native": "^7.1.18",
|
||||||
"@react-navigation/native-stack": "^7.3.27",
|
"@react-navigation/native-stack": "^7.3.27",
|
||||||
"@react-navigation/stack": "^7.4.9",
|
"@react-navigation/stack": "^7.4.9",
|
||||||
"@scure/bip32": "^2.0.1",
|
"@scure/bip32": "^2.2.0",
|
||||||
"@scure/bip39": "2.0.1",
|
"@scure/bip39": "2.0.1",
|
||||||
"@sentry/react-native": "^7.7.0",
|
"@sentry/react-native": "^7.7.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
diff --git a/node_modules/@scure/bip32/index.js b/node_modules/@scure/bip32/index.js
|
diff --git a/node_modules/@scure/bip32/index.js b/node_modules/@scure/bip32/index.js
|
||||||
index 935277b..361161a 100644
|
index 8e49612..d54da85 100644
|
||||||
--- a/node_modules/@scure/bip32/index.js
|
--- a/node_modules/@scure/bip32/index.js
|
||||||
+++ b/node_modules/@scure/bip32/index.js
|
+++ b/node_modules/@scure/bip32/index.js
|
||||||
@@ -23,6 +23,7 @@ import { ripemd160 } from '@noble/hashes/legacy.js';
|
@@ -28,6 +28,7 @@ import { ripemd160 } from '@noble/hashes/legacy.js';
|
||||||
import { sha256, sha512 } from '@noble/hashes/sha2.js';
|
import { sha256, sha512 } from '@noble/hashes/sha2.js';
|
||||||
import { abytes, concatBytes, createView } from '@noble/hashes/utils.js';
|
import { abytes, concatBytes, createView } from '@noble/hashes/utils.js';
|
||||||
import { createBase58check } from '@scure/base';
|
import { createBase58check } from '@scure/base';
|
||||||
+import quickCrypto from 'react-native-quick-crypto';
|
+import quickCrypto from 'react-native-quick-crypto';
|
||||||
const Point = secp.Point;
|
const Point = /* @__PURE__ */ (() => secp.Point)();
|
||||||
const { Fn } = Point;
|
const Fn = /* @__PURE__ */ (() => Point.Fn)();
|
||||||
const base58check = createBase58check(sha256);
|
const base58check = /* @__PURE__ */ createBase58check(sha256);
|
||||||
@@ -87,7 +88,8 @@ export class HDKey {
|
@@ -102,7 +103,8 @@ export class HDKey {
|
||||||
throw new Error('HDKey: seed length must be between 128 and 512 bits; 256 bits is advised, got ' +
|
throw new RangeError('HDKey: seed length must be between 128 and 512 bits; 256 bits is advised, got ' +
|
||||||
seed.length);
|
seed.length);
|
||||||
}
|
}
|
||||||
- const I = hmac(sha512, MASTER_SECRET, seed);
|
- const I = hmac(sha512, MASTER_SECRET, seed);
|
||||||
@@ -20,13 +20,13 @@ index 935277b..361161a 100644
|
|||||||
const privateKey = I.slice(0, 32);
|
const privateKey = I.slice(0, 32);
|
||||||
const chainCode = I.slice(32);
|
const chainCode = I.slice(32);
|
||||||
return new HDKey({ versions, chainCode, privateKey });
|
return new HDKey({ versions, chainCode, privateKey });
|
||||||
@@ -206,7 +208,8 @@ export class HDKey {
|
@@ -225,7 +227,8 @@ export class HDKey {
|
||||||
// Normal child: serP(point(kpar)) || ser32(index)
|
// Normal child: serP(point(kpar)) || ser32(index)
|
||||||
data = concatBytes(this._publicKey, data);
|
data = concatBytes(this._publicKey, data);
|
||||||
}
|
}
|
||||||
- const I = hmac(sha512, this.chainCode, data);
|
- const out = _I || hmac(sha512, this.chainCode, data);
|
||||||
+ //const I = hmac(sha512, this.chainCode, data);
|
+ //const out = _I || hmac(sha512, this.chainCode, data);
|
||||||
+ const I = new Uint8Array(quickCrypto.createHmac('sha512', this.chainCode).update(data).digest());
|
+ const out = _I || new Uint8Array(quickCrypto.createHmac('sha512', this.chainCode).update(data).digest());
|
||||||
const childTweak = I.slice(0, 32);
|
abytes(out, 64);
|
||||||
const chainCode = I.slice(32);
|
const childTweak = out.slice(0, 32);
|
||||||
if (!secp.utils.isValidSecretKey(childTweak)) {
|
const chainCode = out.slice(32);
|
||||||
@@ -15,13 +15,13 @@ import { MintUnit, MintUnits } from '../services/wallet/currency'
|
|||||||
import { getRootStore } from './helpers/getRootStore'
|
import { getRootStore } from './helpers/getRootStore'
|
||||||
import { generateId } from '../utils/utils'
|
import { generateId } from '../utils/utils'
|
||||||
import { Proof } from './Proof'
|
import { Proof } from './Proof'
|
||||||
import { CashuProof, CashuUtils } from '../services/cashu/cashuUtils'
|
import { CashuProof, CashuUtils, StoredMeltPreview } from '../services/cashu/cashuUtils'
|
||||||
|
|
||||||
// Helper function to serialize MeltPreview and convert BigInt to string
|
function serializeMeltPreview(meltPreview: MeltPreview): StoredMeltPreview {
|
||||||
function serializeMeltPreview(meltPreview: MeltPreview): any {
|
return {
|
||||||
return JSON.parse(JSON.stringify(meltPreview, (_key, value) =>
|
keysetId: meltPreview.keysetId,
|
||||||
typeof value === 'bigint' ? value.toString() : value
|
outputData: CashuUtils.serializeOutputData(meltPreview.outputData),
|
||||||
))
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MintBalance = {
|
export type MintBalance = {
|
||||||
@@ -60,7 +60,7 @@ const InFlightRequestModel = types.model('InFlightRequest', {
|
|||||||
const MeltCounterValueModel = types.model('MeltCounterValue', {
|
const MeltCounterValueModel = types.model('MeltCounterValue', {
|
||||||
transactionId: types.number,
|
transactionId: types.number,
|
||||||
counterAtMelt: types.number, // the counter value when melt started (kept for backward compatibility)
|
counterAtMelt: types.number, // the counter value when melt started (kept for backward compatibility)
|
||||||
meltPreview: types.maybe(types.frozen<MeltPreview>()), // v3.x MeltPreview object for recovery
|
meltPreview: types.maybe(types.frozen<StoredMeltPreview>()),
|
||||||
createdAt: types.optional(types.Date, () => new Date()), // optional: when it was added
|
createdAt: types.optional(types.Date, () => new Date()), // optional: when it was added
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -155,13 +155,10 @@ export const MintProofsCounterModel = types
|
|||||||
return self.meltCounterValues.get(key)!.counterAtMelt
|
return self.meltCounterValues.get(key)!.counterAtMelt
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize MeltPreview to handle BigInt values
|
|
||||||
const serializableMeltPreview = meltPreview ? serializeMeltPreview(meltPreview) : undefined
|
|
||||||
|
|
||||||
self.meltCounterValues.set(key, {
|
self.meltCounterValues.set(key, {
|
||||||
transactionId,
|
transactionId,
|
||||||
counterAtMelt: self.counter,
|
counterAtMelt: self.counter,
|
||||||
meltPreview: serializableMeltPreview as any,
|
meltPreview: meltPreview ? serializeMeltPreview(meltPreview) : undefined,
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -205,6 +205,7 @@ import {
|
|||||||
} else {
|
} else {
|
||||||
proofNode = ProofModel.create({
|
proofNode = ProofModel.create({
|
||||||
...proof,
|
...proof,
|
||||||
|
amount: Number(proof.amount),
|
||||||
mintUrl,
|
mintUrl,
|
||||||
tId,
|
tId,
|
||||||
unit,
|
unit,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {Instance, SnapshotOut, types, flow, getRoot, getSnapshot} from 'mobx-sta
|
|||||||
import {
|
import {
|
||||||
Mint as CashuMint,
|
Mint as CashuMint,
|
||||||
Wallet as CashuWallet,
|
Wallet as CashuWallet,
|
||||||
|
KeyChain as CashuKeyChain,
|
||||||
MeltQuoteBolt11Response,
|
MeltQuoteBolt11Response,
|
||||||
setGlobalRequestOptions,
|
setGlobalRequestOptions,
|
||||||
type MintKeys,
|
type MintKeys,
|
||||||
@@ -337,13 +338,17 @@ export const WalletStoreModel = types
|
|||||||
|
|
||||||
const newSeedWallet = new CashuWallet(cashuMint, {
|
const newSeedWallet = new CashuWallet(cashuMint, {
|
||||||
unit,
|
unit,
|
||||||
keys: mintInstance.keys,
|
|
||||||
keysets: mintInstance.keysets,
|
|
||||||
mintInfo: mintInstance.mintInfo,
|
|
||||||
keysetId: walletKeys.id,
|
keysetId: walletKeys.id,
|
||||||
bip39seed: seed
|
bip39seed: seed
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (mintInstance.mintInfo && mintInstance.keysets?.length && mintInstance.keys?.length) {
|
||||||
|
const keychainCache = CashuKeyChain.mintToCacheDTO(mintUrl, [...mintInstance.keysets], [...mintInstance.keys])
|
||||||
|
newSeedWallet.loadMintFromCache(mintInstance.mintInfo, keychainCache)
|
||||||
|
} else {
|
||||||
|
yield newSeedWallet.loadMint()
|
||||||
|
}
|
||||||
|
|
||||||
self.seedWallets.push(newSeedWallet)
|
self.seedWallets.push(newSeedWallet)
|
||||||
|
|
||||||
log.trace('[WalletStore.getWallet]', 'Returning NEW cashuWallet instance with seed', {mintUrl})
|
log.trace('[WalletStore.getWallet]', 'Returning NEW cashuWallet instance with seed', {mintUrl})
|
||||||
@@ -363,13 +368,16 @@ export const WalletStoreModel = types
|
|||||||
|
|
||||||
const newWallet = new CashuWallet(cashuMint, {
|
const newWallet = new CashuWallet(cashuMint, {
|
||||||
unit,
|
unit,
|
||||||
keys: mintInstance.keys,
|
|
||||||
keysets: mintInstance.keysets,
|
|
||||||
mintInfo: mintInstance.mintInfo,
|
|
||||||
keysetId: walletKeys.id,
|
keysetId: walletKeys.id,
|
||||||
bip39seed: undefined
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (mintInstance.mintInfo && mintInstance.keysets?.length && mintInstance.keys?.length) {
|
||||||
|
const keychainCache = CashuKeyChain.mintToCacheDTO(mintUrl, [...mintInstance.keysets], [...mintInstance.keys])
|
||||||
|
newWallet.loadMintFromCache(mintInstance.mintInfo, keychainCache)
|
||||||
|
} else {
|
||||||
|
yield newWallet.loadMint()
|
||||||
|
}
|
||||||
|
|
||||||
self.wallets.push(newWallet)
|
self.wallets.push(newWallet)
|
||||||
|
|
||||||
log.trace('[WalletStore.getWallet]', 'Returning NEW cashuWallet instance', {mintUrl})
|
log.trace('[WalletStore.getWallet]', 'Returning NEW cashuWallet instance', {mintUrl})
|
||||||
@@ -1032,63 +1040,6 @@ export const WalletStoreModel = types
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
recoverMeltQuoteChange: flow(function* recoverMeltQuoteChange(
|
|
||||||
mintUrl: string,
|
|
||||||
transaction: Transaction
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
const mintInstance = self.getMintModelInstance(mintUrl)
|
|
||||||
const transactionId = transaction.id
|
|
||||||
|
|
||||||
if(!mintInstance || !transaction.keysetId) {
|
|
||||||
throw new AppError(Err.VALIDATION_ERROR, 'Missing mint instance or keysetId', {mintUrl, transactionId})
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentCounter = mintInstance.getProofsCounterByKeysetId!(transaction.keysetId)
|
|
||||||
const meltCounterValue = currentCounter.getMeltCounterValue(transaction.id)
|
|
||||||
|
|
||||||
if(!meltCounterValue || !meltCounterValue.meltPreview) {
|
|
||||||
throw new AppError(Err.VALIDATION_ERROR, 'Change already claimed - melt data not available for this transaction', {mintUrl, transactionId})
|
|
||||||
}
|
|
||||||
|
|
||||||
const meltPreview: MeltPreview = meltCounterValue.meltPreview
|
|
||||||
|
|
||||||
if(!meltPreview) {
|
|
||||||
throw new AppError(Err.VALIDATION_ERROR, 'MeltPreview not found - this transaction may be from an older version', {mintUrl, transactionId})
|
|
||||||
}
|
|
||||||
|
|
||||||
const cashuWallet: CashuWallet = yield self.getWallet(
|
|
||||||
mintUrl,
|
|
||||||
transaction.unit,
|
|
||||||
{
|
|
||||||
withSeed: true,
|
|
||||||
keysetId: transaction.keysetId
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Use completeMelt with the stored MeltPreview to recover change
|
|
||||||
const {change}: MeltProofsResponse = yield cashuWallet.completeMelt(meltPreview)
|
|
||||||
|
|
||||||
currentCounter.removeMeltCounterValue(transactionId)
|
|
||||||
|
|
||||||
log.info('[recoverMeltQuoteChange]', {change})
|
|
||||||
|
|
||||||
return change
|
|
||||||
|
|
||||||
} catch (e: any) {
|
|
||||||
let message = 'The mint could not return change from a melt quote.'
|
|
||||||
if (isOnionMint(mintUrl)) message += TorVPNSetupInstructions;
|
|
||||||
throw new AppError(
|
|
||||||
Err.MINT_ERROR,
|
|
||||||
message,
|
|
||||||
{
|
|
||||||
message: e.message,
|
|
||||||
caller: 'recoverMeltQuoteChange',
|
|
||||||
mintUrl,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
restore: flow(function* restore(
|
restore: flow(function* restore(
|
||||||
mintUrl: string,
|
mintUrl: string,
|
||||||
seed: Uint8Array,
|
seed: Uint8Array,
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import { ContactsStoreSnapshot } from '../models/ContactsStore'
|
|||||||
import { MintsStoreSnapshot } from '../models/MintsStore'
|
import { MintsStoreSnapshot } from '../models/MintsStore'
|
||||||
import { ResultModalInfo } from './Wallet/ResultModalInfo'
|
import { ResultModalInfo } from './Wallet/ResultModalInfo'
|
||||||
import { verticalScale } from '@gocodingnow/rn-size-matters'
|
import { verticalScale } from '@gocodingnow/rn-size-matters'
|
||||||
import { Token, getEncodedToken } from '@cashu/cashu-ts'
|
import { Token, getEncodedToken, normalizeProofAmounts } from '@cashu/cashu-ts'
|
||||||
import { StaticScreenProps, useNavigation } from '@react-navigation/native'
|
import { StaticScreenProps, useNavigation } from '@react-navigation/native'
|
||||||
|
|
||||||
const OPTIMIZE_FROM_PROOFS_COUNT = 10
|
const OPTIMIZE_FROM_PROOFS_COUNT = 10
|
||||||
@@ -262,7 +262,7 @@ export const ExportBackupScreen = function ExportBackup({ route }: Props) {
|
|||||||
|
|
||||||
const tokenByKeysetId: Token = {
|
const tokenByKeysetId: Token = {
|
||||||
mint,
|
mint,
|
||||||
proofs: proofsToExport,
|
proofs: normalizeProofAmounts(proofsToExport),
|
||||||
unit: proofsByKeysetId[0].unit
|
unit: proofsByKeysetId[0].unit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -429,7 +429,7 @@ function NutsCard(props: {info: GetInfoResponse}) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ('disabled' in info && info.disabled === false) { // detailed
|
if ('disabled' in info && info.disabled === false) { // detailed
|
||||||
supportedNutsDetailed.push([nut, info])
|
supportedNutsDetailed.push([nut, info as unknown as DetailedNutInfo])
|
||||||
} else if ('supported' in info && typeof info.supported !== 'undefined') { // simple
|
} else if ('supported' in info && typeof info.supported !== 'undefined') { // simple
|
||||||
nutsSimple.push([nut, !!info.supported])
|
nutsSimple.push([nut, !!info.supported])
|
||||||
} else {
|
} else {
|
||||||
@@ -567,14 +567,20 @@ function getMintLimits(info: GetInfoResponse) {
|
|||||||
console.log('runs')
|
console.log('runs')
|
||||||
for (const method of info.nuts['4'].methods) {
|
for (const method of info.nuts['4'].methods) {
|
||||||
if ((typeof method.min_amount !== 'undefined' || typeof method.max_amount !== 'undefined') && method.unit === 'sat') {
|
if ((typeof method.min_amount !== 'undefined' || typeof method.max_amount !== 'undefined') && method.unit === 'sat') {
|
||||||
mintSats = { min: method.min_amount, max: method.max_amount }
|
mintSats = {
|
||||||
|
min: method.min_amount !== undefined ? Number(method.min_amount) : undefined,
|
||||||
|
max: method.max_amount !== undefined ? Number(method.max_amount) : undefined,
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const method of info.nuts['5'].methods) {
|
for (const method of info.nuts['5'].methods) {
|
||||||
if ((typeof method.min_amount !== 'undefined' || typeof method.max_amount !== 'undefined') && method.unit === 'sat') {
|
if ((typeof method.min_amount !== 'undefined' || typeof method.max_amount !== 'undefined') && method.unit === 'sat') {
|
||||||
meltSats = { min: method.min_amount, max: method.max_amount }
|
meltSats = {
|
||||||
|
min: method.min_amount !== undefined ? Number(method.min_amount) : undefined,
|
||||||
|
max: method.max_amount !== undefined ? Number(method.max_amount) : undefined,
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
View,
|
View,
|
||||||
TextStyle,
|
TextStyle,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import { PaymentRequest as CashuPaymentRequest, MeltQuoteBolt11Response, MeltQuoteResponse, PaymentRequestTransportType, decodePaymentRequest, getDecodedToken, getTokenMetadata } from '@cashu/cashu-ts'
|
import { PaymentRequest as CashuPaymentRequest, MeltQuoteBolt11Response, PaymentRequestTransportType, decodePaymentRequest, getDecodedToken, getTokenMetadata } from '@cashu/cashu-ts'
|
||||||
import NfcManager, { Ndef, NfcEvents } from 'react-native-nfc-manager'
|
import NfcManager, { Ndef, NfcEvents } from 'react-native-nfc-manager'
|
||||||
import { colors, spacing, typography, useThemeColor } from '../theme'
|
import { colors, spacing, typography, useThemeColor } from '../theme'
|
||||||
import EventEmitter from '../utils/eventEmitter'
|
import EventEmitter from '../utils/eventEmitter'
|
||||||
@@ -400,7 +400,7 @@ export const NfcPayScreen = observer(function NfcPayScreen({ route }: Props) {
|
|||||||
log.trace('[handlePaymentRequest] decoded Cashu payment request', { pr })
|
log.trace('[handlePaymentRequest] decoded Cashu payment request', { pr })
|
||||||
|
|
||||||
// Validate basics
|
// Validate basics
|
||||||
if (!pr.amount || pr.amount <= 0) {
|
if (!pr.amount || Number(pr.amount) <= 0) {
|
||||||
throw new AppError(Err.VALIDATION_ERROR, 'Payment request has no valid amount')
|
throw new AppError(Err.VALIDATION_ERROR, 'Payment request has no valid amount')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -420,7 +420,7 @@ export const NfcPayScreen = observer(function NfcPayScreen({ route }: Props) {
|
|||||||
// setEncodedCashuPaymentRequest(encoded)
|
// setEncodedCashuPaymentRequest(encoded)
|
||||||
if (pr.description) setMemo(pr.description)
|
if (pr.description) setMemo(pr.description)
|
||||||
|
|
||||||
const requiredAmount = pr.amount
|
const requiredAmount = Number(pr.amount)
|
||||||
const eligibleBalances = await getEligibleMintBalancesForCashu(pr, requiredAmount, unit)
|
const eligibleBalances = await getEligibleMintBalancesForCashu(pr, requiredAmount, unit)
|
||||||
|
|
||||||
if (eligibleBalances.length === 0) {
|
if (eligibleBalances.length === 0) {
|
||||||
@@ -678,14 +678,14 @@ export const NfcPayScreen = observer(function NfcPayScreen({ route }: Props) {
|
|||||||
setInvoice(decoded)
|
setInvoice(decoded)
|
||||||
setInvoiceExpiry(expiresAt)
|
setInvoiceExpiry(expiresAt)
|
||||||
setMeltQuote(quote)
|
setMeltQuote(quote)
|
||||||
setAmountToPay(formatDisplayAmount(quote.amount, unitRef.current))
|
setAmountToPay(formatDisplayAmount(quote.amount.toNumber(), unitRef.current))
|
||||||
if (description) setMemo(description)
|
if (description) setMemo(description)
|
||||||
|
|
||||||
setNfcInfo('Paying Lightning invoice...')
|
setNfcInfo('Paying Lightning invoice...')
|
||||||
|
|
||||||
const result = await WalletTask.transferQueueAwaitable(
|
const result = await WalletTask.transferQueueAwaitable(
|
||||||
balanceToUse,
|
balanceToUse,
|
||||||
quote.amount,
|
quote.amount.toNumber(),
|
||||||
unitRef.current,
|
unitRef.current,
|
||||||
quote,
|
quote,
|
||||||
description ?? '',
|
description ?? '',
|
||||||
|
|||||||
@@ -136,7 +136,8 @@ export const ReceiveScreen = observer(function ReceiveScreen({ route }: Props) {
|
|||||||
|
|
||||||
// keysetsV2 support
|
// keysetsV2 support
|
||||||
const tokenInfo = getTokenMetadata(encoded)
|
const tokenInfo = getTokenMetadata(encoded)
|
||||||
const {amount, unit, memo, mint: mintUrl} = tokenInfo
|
const {amount: rawAmount, unit, memo, mint: mintUrl} = tokenInfo
|
||||||
|
const amount = Number(rawAmount)
|
||||||
|
|
||||||
if(!unit) {
|
if(!unit) {
|
||||||
throw new AppError(Err.VALIDATION_ERROR, translate("decodedMissingCurrencyUnit", { unit: CurrencyCode.SAT }))
|
throw new AppError(Err.VALIDATION_ERROR, translate("decodedMissingCurrencyUnit", { unit: CurrencyCode.SAT }))
|
||||||
|
|||||||
@@ -618,8 +618,10 @@ export const SendScreen = observer(function SendScreen({ route }: Props) {
|
|||||||
setIsCashuPrWithDesc(true)
|
setIsCashuPrWithDesc(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pr.amount) {
|
const prAmount = pr.amount ? Number(pr.amount) : undefined
|
||||||
setAmountToSend(`${numbro(pr.amount / getCurrency(unitRef.current).precision).format({
|
|
||||||
|
if (prAmount) {
|
||||||
|
setAmountToSend(`${numbro(prAmount / getCurrency(unitRef.current).precision).format({
|
||||||
thousandSeparated: true,
|
thousandSeparated: true,
|
||||||
mantissa: getCurrency(unitRef.current).mantissa,
|
mantissa: getCurrency(unitRef.current).mantissa,
|
||||||
})}`)
|
})}`)
|
||||||
@@ -646,22 +648,22 @@ export const SendScreen = observer(function SendScreen({ route }: Props) {
|
|||||||
|
|
||||||
withEnoughBalance = availableBalances.filter(balance => {
|
withEnoughBalance = availableBalances.filter(balance => {
|
||||||
const unitBalance = balance.balances[unitRef.current]
|
const unitBalance = balance.balances[unitRef.current]
|
||||||
if(!pr.amount) return balance
|
if(!prAmount) return balance
|
||||||
if(pr.amount > 0 && unitBalance && unitBalance >= pr.amount) return balance
|
if(prAmount > 0 && unitBalance && unitBalance >= prAmount) return balance
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
|
||||||
if (pr.amount && withEnoughBalance.length === 0) {
|
if (prAmount && withEnoughBalance.length === 0) {
|
||||||
dispatch({ type: 'SET_INFO', message: `Not enough balance to pay this payment request. Required: ${pr.amount} ${unitRef.current}.`})
|
dispatch({ type: 'SET_INFO', message: `Not enough balance to pay this payment request. Required: ${prAmount} ${unitRef.current}.`})
|
||||||
//infoMessage(`Not enough balance to pay this payment request. Required: ${pr.amount} ${unitRef.current}.`)
|
//infoMessage(`Not enough balance to pay this payment request. Required: ${prAmount} ${unitRef.current}.`)
|
||||||
return
|
return
|
||||||
//throw new AppError(Err.VALIDATION_ERROR, `Not enough balance to pay this payment request. Required: ${pr.amount} ${unitRef.current}.`)
|
//throw new AppError(Err.VALIDATION_ERROR, `Not enough balance to pay this payment request. Required: ${prAmount} ${unitRef.current}.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.trace('[handlePaymentRequest] available mint balances for this payment request', {availableBalances, withEnoughBalance})
|
log.trace('[handlePaymentRequest] available mint balances for this payment request', {availableBalances, withEnoughBalance})
|
||||||
} else {
|
} else {
|
||||||
withEnoughBalance = (pr.amount && pr.amount > 0)
|
withEnoughBalance = (prAmount && prAmount > 0)
|
||||||
? proofsStore.getMintBalancesWithEnoughBalance(pr.amount, unitRef.current)
|
? proofsStore.getMintBalancesWithEnoughBalance(prAmount, unitRef.current)
|
||||||
: proofsStore.getMintBalancesWithUnit(unitRef.current)
|
: proofsStore.getMintBalancesWithUnit(unitRef.current)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -514,14 +514,14 @@ export const TransferScreen = observer(function TransferScreen({ route }: Props)
|
|||||||
meltQuoteRef.current = quote
|
meltQuoteRef.current = quote
|
||||||
|
|
||||||
// Format amount for display
|
// Format amount for display
|
||||||
const displayAmount = numbro(quote.amount / getCurrency(unitRef.current).precision).format({
|
const displayAmount = numbro(quote.amount.toNumber() / getCurrency(unitRef.current).precision).format({
|
||||||
thousandSeparated: true,
|
thousandSeparated: true,
|
||||||
mantissa: getCurrency(unitRef.current).mantissa,
|
mantissa: getCurrency(unitRef.current).mantissa,
|
||||||
})
|
})
|
||||||
setAmountToTransfer(displayAmount)
|
setAmountToTransfer(displayAmount)
|
||||||
|
|
||||||
// Check total required balance (amount + fee reserve)
|
// Check total required balance (amount + fee reserve)
|
||||||
const totalRequired = quote.amount + quote.fee_reserve
|
const totalRequired = quote.amount.toNumber() + quote.fee_reserve.toNumber()
|
||||||
const availableBalances = proofsStore.getMintBalancesWithEnoughBalance(totalRequired, unitRef.current)
|
const availableBalances = proofsStore.getMintBalancesWithEnoughBalance(totalRequired, unitRef.current)
|
||||||
|
|
||||||
if (availableBalances.length === 0) {
|
if (availableBalances.length === 0) {
|
||||||
@@ -934,7 +934,7 @@ export const TransferScreen = observer(function TransferScreen({ route }: Props)
|
|||||||
{encodedInvoice && (meltQuote?.fee_reserve || finalFee) ? (
|
{encodedInvoice && (meltQuote?.fee_reserve || finalFee) ? (
|
||||||
<FeeBadge
|
<FeeBadge
|
||||||
currencyCode={getCurrency(unitRef.current).code}
|
currencyCode={getCurrency(unitRef.current).code}
|
||||||
estimatedFee={meltQuote?.fee_reserve || 0}
|
estimatedFee={meltQuote?.fee_reserve.toNumber() ?? 0}
|
||||||
finalFee={finalFee}
|
finalFee={finalFee}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ export const QRCodeBlock = function (props: {
|
|||||||
setKeysetFormat('hex')
|
setKeysetFormat('hex')
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const encodedV3 = getEncodedToken(decoded, {version: 3})
|
const encodedV3 = getEncodedToken(decoded)
|
||||||
setEncodedV3Token(encodedV3)
|
setEncodedV3Token(encodedV3)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
handleQrError(e)
|
handleQrError(e)
|
||||||
@@ -195,7 +195,7 @@ export const QRCodeBlock = function (props: {
|
|||||||
} else if(type === 'EncodedV4Token') {
|
} else if(type === 'EncodedV4Token') {
|
||||||
if(!decodedToken) return
|
if(!decodedToken) return
|
||||||
|
|
||||||
const encodedV3 = getEncodedToken(decodedToken, {version: 3})
|
const encodedV3 = getEncodedToken(decodedToken)
|
||||||
setEncodedV3Token(encodedV3)
|
setEncodedV3Token(encodedV3)
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
import {Mint} from '../../models/Mint'
|
import {Mint} from '../../models/Mint'
|
||||||
|
import {
|
||||||
|
Amount,
|
||||||
|
OutputData,
|
||||||
|
} from '@cashu/cashu-ts'
|
||||||
import type {
|
import type {
|
||||||
Token,
|
Token,
|
||||||
Proof as CashuProof,
|
ProofLike as CashuProof,
|
||||||
PaymentRequest as CashuPaymentRequest,
|
PaymentRequest as CashuPaymentRequest,
|
||||||
PaymentRequestPayload,
|
PaymentRequestPayload,
|
||||||
TokenMetadata,
|
TokenMetadata,
|
||||||
|
OutputDataLike,
|
||||||
} from '@cashu/cashu-ts'
|
} from '@cashu/cashu-ts'
|
||||||
import { bytesToHex } from '@noble/hashes/utils'
|
import { bytesToHex, hexToBytes } from '@noble/hashes/utils'
|
||||||
import AppError, {Err} from '../../utils/AppError'
|
import AppError, {Err} from '../../utils/AppError'
|
||||||
import { getDecodedToken, getTokenMetadata } from '@cashu/cashu-ts'
|
import { getTokenMetadata } from '@cashu/cashu-ts'
|
||||||
import {Proof} from '../../models/Proof'
|
import {Proof} from '../../models/Proof'
|
||||||
import { log } from '../logService'
|
import { log } from '../logService'
|
||||||
import { decodePaymentRequest } from '@cashu/cashu-ts'
|
import { decodePaymentRequest } from '@cashu/cashu-ts'
|
||||||
@@ -29,7 +34,7 @@ const isObj = function(v: unknown): v is object {
|
|||||||
* Sum the amounts of an array of proofs
|
* Sum the amounts of an array of proofs
|
||||||
*/
|
*/
|
||||||
const sumProofs = function(proofs: CashuProof[]): number {
|
const sumProofs = function(proofs: CashuProof[]): number {
|
||||||
return proofs.reduce((acc: number, proof: CashuProof) => acc + proof.amount, 0)
|
return proofs.reduce((acc: number, proof: CashuProof) => acc + Number(proof.amount), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
const CASHU_URI_PREFIXES = [
|
const CASHU_URI_PREFIXES = [
|
||||||
@@ -407,6 +412,38 @@ const isTokenP2PKLocked = function (token: Token | TokenMetadata): boolean {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export interface StoredMeltPreview {
|
||||||
|
keysetId: string
|
||||||
|
outputData: SerializedOutputData[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerializedOutputData {
|
||||||
|
blindedMessage: { amount: string | number; id: string; B_: string }
|
||||||
|
blindingFactor: string // hex
|
||||||
|
secret: string // hex
|
||||||
|
ephemeralE?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const serializeOutputData = (outputData: OutputDataLike[]): SerializedOutputData[] =>
|
||||||
|
outputData.map(od => ({
|
||||||
|
blindedMessage: {
|
||||||
|
amount: od.blindedMessage.amount.toString(),
|
||||||
|
id: od.blindedMessage.id,
|
||||||
|
B_: od.blindedMessage.B_,
|
||||||
|
},
|
||||||
|
blindingFactor: od.blindingFactor.toString(16),
|
||||||
|
secret: bytesToHex(od.secret),
|
||||||
|
ephemeralE: od.ephemeralE,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const deserializeOutputData = (serialized: SerializedOutputData[]): OutputData[] =>
|
||||||
|
serialized.map(od => new OutputData(
|
||||||
|
{ amount: Amount.from(od.blindedMessage.amount), id: od.blindedMessage.id, B_: od.blindedMessage.B_ },
|
||||||
|
BigInt('0x' + od.blindingFactor),
|
||||||
|
hexToBytes(od.secret),
|
||||||
|
od.ephemeralE,
|
||||||
|
))
|
||||||
|
|
||||||
export const CashuUtils = {
|
export const CashuUtils = {
|
||||||
findEncodedCashuToken,
|
findEncodedCashuToken,
|
||||||
findEncodedCashuPaymentRequest,
|
findEncodedCashuPaymentRequest,
|
||||||
@@ -427,7 +464,9 @@ export const CashuUtils = {
|
|||||||
isTokenP2PKLocked,
|
isTokenP2PKLocked,
|
||||||
isCollidingKeysetId,
|
isCollidingKeysetId,
|
||||||
isObj,
|
isObj,
|
||||||
sumProofs
|
sumProofs,
|
||||||
|
serializeOutputData,
|
||||||
|
deserializeOutputData,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ import AppError, {Err} from '../../utils/AppError'
|
|||||||
import { TransactionTaskResult } from '../walletService'
|
import { TransactionTaskResult } from '../walletService'
|
||||||
import { WalletUtils } from './utils'
|
import { WalletUtils } from './utils'
|
||||||
import { MintUnit, formatCurrency, getCurrency } from './currency'
|
import { MintUnit, formatCurrency, getCurrency } from './currency'
|
||||||
import { PaymentRequestPayload, Token, getDecodedToken } from '@cashu/cashu-ts'
|
import { PaymentRequestPayload, Token, getDecodedToken, getEncodedToken, normalizeProofAmounts } from '@cashu/cashu-ts'
|
||||||
import { getEncodedToken } from '@cashu/cashu-ts'
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
mintsStore,
|
mintsStore,
|
||||||
@@ -605,7 +604,7 @@ export const receiveSync = async function (
|
|||||||
// store swapped proofs as encoded token in tx data
|
// store swapped proofs as encoded token in tx data
|
||||||
const outputToken = getEncodedToken({
|
const outputToken = getEncodedToken({
|
||||||
mint: mintToReceive,
|
mint: mintToReceive,
|
||||||
proofs: receivedProofs,
|
proofs: normalizeProofAmounts(receivedProofs),
|
||||||
unit,
|
unit,
|
||||||
memo,
|
memo,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ import AppError, {Err} from '../../utils/AppError'
|
|||||||
import { TransactionTaskResult } from '../walletService'
|
import { TransactionTaskResult } from '../walletService'
|
||||||
import { WalletUtils } from './utils'
|
import { WalletUtils } from './utils'
|
||||||
import { MintUnit } from './currency'
|
import { MintUnit } from './currency'
|
||||||
import { Token } from '@cashu/cashu-ts'
|
import { Token, getEncodedToken, normalizeProofAmounts } from '@cashu/cashu-ts'
|
||||||
import { getEncodedToken, getKeepAmounts } from '@cashu/cashu-ts'
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
mintsStore,
|
mintsStore,
|
||||||
@@ -52,7 +51,7 @@ try {
|
|||||||
// This will invalidate originally sent proofs effectively reverting the transaction.
|
// This will invalidate originally sent proofs effectively reverting the transaction.
|
||||||
const encodedToken: Token = {
|
const encodedToken: Token = {
|
||||||
mint: mintInstance.mintUrl,
|
mint: mintInstance.mintUrl,
|
||||||
proofs: pendingProofs,
|
proofs: normalizeProofAmounts(pendingProofs),
|
||||||
unit
|
unit
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,8 +68,8 @@ try {
|
|||||||
// store freshed proofs as encoded token in tx data
|
// store freshed proofs as encoded token in tx data
|
||||||
const outputToken = getEncodedToken({
|
const outputToken = getEncodedToken({
|
||||||
mint: mintInstance.mintUrl,
|
mint: mintInstance.mintUrl,
|
||||||
proofs: receivedProofs,
|
proofs: normalizeProofAmounts(receivedProofs),
|
||||||
unit
|
unit
|
||||||
})
|
})
|
||||||
|
|
||||||
// Remove original pending proofs
|
// Remove original pending proofs
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
MintKeyset,
|
MintKeyset,
|
||||||
ProofState,
|
ProofState,
|
||||||
getEncodedToken,
|
getEncodedToken,
|
||||||
|
normalizeProofAmounts,
|
||||||
} from '@cashu/cashu-ts'
|
} from '@cashu/cashu-ts'
|
||||||
import { MAX_SWAP_INPUT_SIZE, TransactionTaskResult, WalletTask } from '../walletService'
|
import { MAX_SWAP_INPUT_SIZE, TransactionTaskResult, WalletTask } from '../walletService'
|
||||||
import { Mint, MintBalance } from '../../models/Mint'
|
import { Mint, MintBalance } from '../../models/Mint'
|
||||||
@@ -44,7 +45,7 @@ const _monitorSentProofs = async (params: {
|
|||||||
try {
|
try {
|
||||||
log.trace('[send] Subscribing to proofStateUpdates for sent proof', {secret: proofsToSend[0]})
|
log.trace('[send] Subscribing to proofStateUpdates for sent proof', {secret: proofsToSend[0]})
|
||||||
const unsub = await wsWallet.on.proofStateUpdates(
|
const unsub = await wsWallet.on.proofStateUpdates(
|
||||||
[proofsToSend[0]],
|
normalizeProofAmounts([proofsToSend[0]]),
|
||||||
async (proofState: ProofState) => {
|
async (proofState: ProofState) => {
|
||||||
log.trace(`Websocket: proof state updated: ${proofState.state} with secret: ${proofsToSend[0].secret}`)
|
log.trace(`Websocket: proof state updated: ${proofState.state} with secret: ${proofsToSend[0].secret}`)
|
||||||
if (proofState.state == CheckStateEnum.SPENT) {
|
if (proofState.state == CheckStateEnum.SPENT) {
|
||||||
@@ -146,7 +147,7 @@ export const sendTask = async function (
|
|||||||
|
|
||||||
const outputToken = getEncodedToken({
|
const outputToken = getEncodedToken({
|
||||||
mint: mintUrl,
|
mint: mintUrl,
|
||||||
proofs: proofsToSend,
|
proofs: normalizeProofAmounts(proofsToSend),
|
||||||
unit,
|
unit,
|
||||||
memo,
|
memo,
|
||||||
})
|
})
|
||||||
@@ -340,7 +341,7 @@ export const sendFromMintSync = async function (
|
|||||||
if(isSwapNeeded) {
|
if(isSwapNeeded) {
|
||||||
// Calculate feeReserve from mint fee rate
|
// Calculate feeReserve from mint fee rate
|
||||||
const walletInstance = await walletStore.getWallet(mintUrl, unit, {withSeed: true}) as CashuWallet
|
const walletInstance = await walletStore.getWallet(mintUrl, unit, {withSeed: true}) as CashuWallet
|
||||||
swapFeeReserve = walletInstance.getFeesForProofs(proofsToSendFrom)
|
swapFeeReserve = walletInstance.getFeesForProofs(proofsToSendFrom).toNumber()
|
||||||
const amountWithFees = amountToSend + swapFeeReserve
|
const amountWithFees = amountToSend + swapFeeReserve
|
||||||
|
|
||||||
if (totalAmountFromMint < amountWithFees) {
|
if (totalAmountFromMint < amountWithFees) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {CashuUtils} from '../cashu/cashuUtils'
|
import {CashuUtils} from '../cashu/cashuUtils'
|
||||||
import AppError, {Err} from '../../utils/AppError'
|
import AppError, {Err} from '../../utils/AppError'
|
||||||
import {Mint as CashuMint, Wallet as CashuWallet, MeltProofsResponse, MeltQuoteBolt11Response, MeltQuoteResponse, MeltQuoteState, getEncodedToken} from '@cashu/cashu-ts'
|
import {Mint as CashuMint, Wallet as CashuWallet, MeltProofsResponse, MeltQuoteBolt11Response, MeltQuoteState, getEncodedToken, normalizeProofAmounts} from '@cashu/cashu-ts'
|
||||||
import {rootStoreInstance} from '../../models'
|
import {rootStoreInstance} from '../../models'
|
||||||
import { TransactionTaskResult, WalletTask } from '../walletService'
|
import { TransactionTaskResult, WalletTask } from '../walletService'
|
||||||
import { MintBalance } from '../../models/Mint'
|
import { MintBalance } from '../../models/Mint'
|
||||||
@@ -116,7 +116,7 @@ export const transferTask = async function (
|
|||||||
const newTransaction = {
|
const newTransaction = {
|
||||||
type: TransactionType.TRANSFER,
|
type: TransactionType.TRANSFER,
|
||||||
amount: amountToTransfer,
|
amount: amountToTransfer,
|
||||||
fee: meltQuote.fee_reserve,
|
fee: meltQuote.fee_reserve.toNumber(),
|
||||||
unit,
|
unit,
|
||||||
data: JSON.stringify(transactionData),
|
data: JSON.stringify(transactionData),
|
||||||
memo,
|
memo,
|
||||||
@@ -137,7 +137,7 @@ export const transferTask = async function (
|
|||||||
// Replace individual setters with a single update
|
// Replace individual setters with a single update
|
||||||
transaction.update({ paymentId: paymentHash, quote: meltQuote.quote })
|
transaction.update({ paymentId: paymentHash, quote: meltQuote.quote })
|
||||||
|
|
||||||
if (amountToTransfer + meltQuote.fee_reserve > mintBalanceToTransferFrom.balances[unit]!) {
|
if (amountToTransfer + meltQuote.fee_reserve.toNumber() > mintBalanceToTransferFrom.balances[unit]!) {
|
||||||
throw new AppError(
|
throw new AppError(
|
||||||
Err.VALIDATION_ERROR,
|
Err.VALIDATION_ERROR,
|
||||||
'Mint balance is insufficient to cover the amount to transfer with the expected Lightning fees.',
|
'Mint balance is insufficient to cover the amount to transfer with the expected Lightning fees.',
|
||||||
@@ -165,15 +165,15 @@ export const transferTask = async function (
|
|||||||
const totalAmountFromMint = CashuUtils.getProofsAmount(proofsFromMint)
|
const totalAmountFromMint = CashuUtils.getProofsAmount(proofsFromMint)
|
||||||
|
|
||||||
proofsToMeltFrom = CashuUtils.getProofsToSend(
|
proofsToMeltFrom = CashuUtils.getProofsToSend(
|
||||||
amountToTransfer + meltQuote.fee_reserve,
|
amountToTransfer + meltQuote.fee_reserve.toNumber(),
|
||||||
proofsFromMint
|
proofsFromMint
|
||||||
)
|
)
|
||||||
|
|
||||||
proofsToMeltFromAmount = CashuUtils.getProofsAmount(proofsToMeltFrom)
|
proofsToMeltFromAmount = CashuUtils.getProofsAmount(proofsToMeltFrom)
|
||||||
|
|
||||||
const walletInstance = await walletStore.getWallet(mintUrl, unit, {withSeed: true})
|
const walletInstance = await walletStore.getWallet(mintUrl, unit, {withSeed: true})
|
||||||
meltFeeReserve = walletInstance.getFeesForProofs(proofsToMeltFrom)
|
meltFeeReserve = walletInstance.getFeesForProofs(proofsToMeltFrom).toNumber()
|
||||||
const amountWithFees = amountToTransfer + meltQuote.fee_reserve + meltFeeReserve
|
const amountWithFees = amountToTransfer + meltQuote.fee_reserve.toNumber() + meltFeeReserve
|
||||||
|
|
||||||
if (totalAmountFromMint < amountWithFees) {
|
if (totalAmountFromMint < amountWithFees) {
|
||||||
throw new AppError(
|
throw new AppError(
|
||||||
@@ -217,15 +217,15 @@ export const transferTask = async function (
|
|||||||
transactionData.push({
|
transactionData.push({
|
||||||
status: TransactionStatus.PREPARED,
|
status: TransactionStatus.PREPARED,
|
||||||
proofsToMeltFromAmount,
|
proofsToMeltFromAmount,
|
||||||
lightningFeeReserve: meltQuote.fee_reserve,
|
lightningFeeReserve: meltQuote.fee_reserve.toNumber(),
|
||||||
meltFeeReserve,
|
meltFeeReserve,
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const inputToken = getEncodedToken({
|
const inputToken = getEncodedToken({
|
||||||
mint: mintUrl,
|
mint: mintUrl,
|
||||||
proofs: proofsToMeltFrom,
|
proofs: normalizeProofAmounts(proofsToMeltFrom),
|
||||||
unit
|
unit
|
||||||
})
|
})
|
||||||
|
|
||||||
transaction.update({
|
transaction.update({
|
||||||
@@ -383,7 +383,7 @@ export const transferTask = async function (
|
|||||||
taskFunction: TRANSFER_TASK,
|
taskFunction: TRANSFER_TASK,
|
||||||
mintUrl,
|
mintUrl,
|
||||||
transaction,
|
transaction,
|
||||||
message: 'Lightning payment is in progress. Check your transaction history for the result.',
|
message: 'Lightning payment is in progress...',
|
||||||
meltQuote: meltResponse.quote,
|
meltQuote: meltResponse.quote,
|
||||||
nwcEvent,
|
nwcEvent,
|
||||||
} as TransactionTaskResult
|
} as TransactionTaskResult
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {CashuProof, CashuUtils} from './cashu/cashuUtils'
|
|||||||
import {LightningUtils} from './lightning/lightningUtils'
|
import {LightningUtils} from './lightning/lightningUtils'
|
||||||
import AppError, {Err} from '../utils/AppError'
|
import AppError, {Err} from '../utils/AppError'
|
||||||
import {MintBalance, MintStatus} from '../models/Mint'
|
import {MintBalance, MintStatus} from '../models/Mint'
|
||||||
import {MeltQuoteBaseResponse, MeltQuoteBolt11Response, MeltQuoteResponse, MeltQuoteState, MintQuoteState, OutputData, PaymentRequestPayload, Token, TokenMetadata, getDecodedToken, getEncodedToken, getTokenMetadata} from '@cashu/cashu-ts'
|
import {MeltQuoteBaseResponse, MeltQuoteBolt11Response, MeltQuoteState, MintQuoteState, PaymentRequestPayload, Token, TokenMetadata, getDecodedToken, getEncodedToken, getTokenMetadata, normalizeProofAmounts} from '@cashu/cashu-ts'
|
||||||
import {Mint} from '../models/Mint'
|
import {Mint} from '../models/Mint'
|
||||||
import {pollerExists, stopPolling} from '../utils/poller'
|
import {pollerExists, stopPolling} from '../utils/poller'
|
||||||
import { NostrClient, NostrEvent, NostrProfile } from './nostrService'
|
import { NostrClient, NostrEvent, NostrProfile } from './nostrService'
|
||||||
@@ -95,7 +95,7 @@ type WalletTaskService = {
|
|||||||
mintBalanceToTransferFrom: MintBalance,
|
mintBalanceToTransferFrom: MintBalance,
|
||||||
amountToTransfer: number,
|
amountToTransfer: number,
|
||||||
unit: MintUnit,
|
unit: MintUnit,
|
||||||
meltQuote: MeltQuoteResponse,
|
meltQuote: MeltQuoteBolt11Response,
|
||||||
memo: string,
|
memo: string,
|
||||||
invoiceExpiry: Date,
|
invoiceExpiry: Date,
|
||||||
encodedInvoice: string,
|
encodedInvoice: string,
|
||||||
@@ -149,7 +149,7 @@ type WalletTaskService = {
|
|||||||
}) => Promise<{recoveredAmount: number}>
|
}) => Promise<{recoveredAmount: number}>
|
||||||
recoverMeltQuoteChange: (params: {
|
recoverMeltQuoteChange: (params: {
|
||||||
mintUrl: string,
|
mintUrl: string,
|
||||||
meltQuote: string | MeltQuoteResponse
|
meltQuote: string | MeltQuoteBolt11Response
|
||||||
}) => Promise<{recoveredAmount: number}>
|
}) => Promise<{recoveredAmount: number}>
|
||||||
handlePendingMeltTask: (params: {
|
handlePendingMeltTask: (params: {
|
||||||
mintUrl: string
|
mintUrl: string
|
||||||
@@ -301,7 +301,8 @@ const receiveQueueAwaitable = function (
|
|||||||
): Promise<TransactionTaskResult> {
|
): Promise<TransactionTaskResult> {
|
||||||
|
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
const {amount, memo, unit} = tokenMetadata
|
const {amount: rawAmount, memo, unit} = tokenMetadata
|
||||||
|
const amount = Number(rawAmount)
|
||||||
const proofsCount = tokenMetadata.incompleteProofs.length
|
const proofsCount = tokenMetadata.incompleteProofs.length
|
||||||
|
|
||||||
const taskId = proofsCount > MAX_SWAP_INPUT_SIZE
|
const taskId = proofsCount > MAX_SWAP_INPUT_SIZE
|
||||||
@@ -338,7 +339,7 @@ const receiveQueueAwaitable = function (
|
|||||||
taskId,
|
taskId,
|
||||||
async () => {
|
async () => {
|
||||||
try {
|
try {
|
||||||
const token = getDecodedToken(encodedToken, mint.keysetIds)
|
const token = getDecodedToken(encodedToken, mint.keysetIds ?? [])
|
||||||
return proofsCount > MAX_SWAP_INPUT_SIZE
|
return proofsCount > MAX_SWAP_INPUT_SIZE
|
||||||
? await receiveBatchTask(token, amount, memo || '', encodedToken)
|
? await receiveBatchTask(token, amount, memo || '', encodedToken)
|
||||||
: await receiveTask(token, amount, memo || '', encodedToken)
|
: await receiveTask(token, amount, memo || '', encodedToken)
|
||||||
@@ -443,7 +444,8 @@ const receiveOfflinePrepareQueueAwaitable = function (
|
|||||||
): Promise<TransactionTaskResult> {
|
): Promise<TransactionTaskResult> {
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
const taskId = `receiveOfflinePrepareTask-${now}`
|
const taskId = `receiveOfflinePrepareTask-${now}`
|
||||||
const { mint: mintUrl, amount, memo, unit } = tokenMetadata
|
const { mint: mintUrl, amount: rawAmount, memo, unit } = tokenMetadata
|
||||||
|
const amount = Number(rawAmount)
|
||||||
|
|
||||||
return new Promise<TransactionTaskResult>((resolve, reject) => {
|
return new Promise<TransactionTaskResult>((resolve, reject) => {
|
||||||
let resolved = false
|
let resolved = false
|
||||||
@@ -1677,7 +1679,7 @@ const handleInFlightByMintTask = async (mint: Mint): Promise<WalletTaskResult> =
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
const outputToken = getEncodedToken({ mint: mintUrl, proofs, unit })
|
const outputToken = getEncodedToken({ mint: mintUrl, proofs: normalizeProofAmounts(proofs), unit })
|
||||||
const balanceAfter = proofsStore.getUnitBalance(unit)?.unitBalance
|
const balanceAfter = proofsStore.getUnitBalance(unit)?.unitBalance
|
||||||
|
|
||||||
txData.push({ status: TransactionStatus.COMPLETED, receivedAmount, swapFeePaid, createdAt: new Date() })
|
txData.push({ status: TransactionStatus.COMPLETED, receivedAmount, swapFeePaid, createdAt: new Date() })
|
||||||
@@ -1718,7 +1720,7 @@ const handleInFlightByMintTask = async (mint: Mint): Promise<WalletTaskResult> =
|
|||||||
proofsStore.addOrUpdate(returnedProofs, { mintUrl, tId: tx.id, unit, isPending: false, isSpent: false })
|
proofsStore.addOrUpdate(returnedProofs, { mintUrl, tId: tx.id, unit, isPending: false, isSpent: false })
|
||||||
proofsStore.addOrUpdate(proofsToSend, { mintUrl, tId: tx.id, unit, isPending: true, isSpent: false })
|
proofsStore.addOrUpdate(proofsToSend, { mintUrl, tId: tx.id, unit, isPending: true, isSpent: false })
|
||||||
|
|
||||||
const outputToken = getEncodedToken({ mint: mintUrl, proofs: proofsToSend, unit })
|
const outputToken = getEncodedToken({ mint: mintUrl, proofs: normalizeProofAmounts(proofsToSend), unit })
|
||||||
const balanceAfter = proofsStore.getUnitBalance(unit)?.unitBalance
|
const balanceAfter = proofsStore.getUnitBalance(unit)?.unitBalance
|
||||||
|
|
||||||
txData.push({ status: TransactionStatus.PENDING, createdAt: new Date() })
|
txData.push({ status: TransactionStatus.PENDING, createdAt: new Date() })
|
||||||
@@ -2269,15 +2271,7 @@ const recoverMintQuote = async (
|
|||||||
const cashuWallet = await walletStore.getWallet(mintUrl, unit, { withSeed: true, keysetId: meltPreview.keysetId })
|
const cashuWallet = await walletStore.getWallet(mintUrl, unit, { withSeed: true, keysetId: meltPreview.keysetId })
|
||||||
const keyset = cashuWallet.getKeyset(meltPreview.keysetId)
|
const keyset = cashuWallet.getKeyset(meltPreview.keysetId)
|
||||||
|
|
||||||
const reconstructedOutputData = meltPreview.outputData.map((od: any) => {
|
const reconstructedOutputData = CashuUtils.deserializeOutputData(meltPreview.outputData)
|
||||||
const blindingFactor = BigInt(od.blindingFactor)
|
|
||||||
const secretValues: number[] = typeof od.secret === 'object' && !ArrayBuffer.isView(od.secret)
|
|
||||||
? Object.values(od.secret as Record<string, number>)
|
|
||||||
: Array.from(od.secret as Uint8Array)
|
|
||||||
const secret = new Uint8Array(secretValues)
|
|
||||||
return new OutputData(od.blindedMessage, blindingFactor, secret)
|
|
||||||
})
|
|
||||||
|
|
||||||
const recoveredChange = change.map((sig, i) => reconstructedOutputData[i].toProof(sig, keyset))
|
const recoveredChange = change.map((sig, i) => reconstructedOutputData[i].toProof(sig, keyset))
|
||||||
currentCounter.removeMeltCounterValue(tx.id)
|
currentCounter.removeMeltCounterValue(tx.id)
|
||||||
|
|
||||||
@@ -2383,19 +2377,11 @@ const handlePendingMeltTask = async (params: {
|
|||||||
{ withSeed: true, keysetId: meltPreview.keysetId })
|
{ withSeed: true, keysetId: meltPreview.keysetId })
|
||||||
const keyset = cashuWallet.getKeyset(meltPreview.keysetId)
|
const keyset = cashuWallet.getKeyset(meltPreview.keysetId)
|
||||||
|
|
||||||
// Reconstruct OutputData instances: serializeMeltPreview converts bigint→string
|
const reconstructedOutputData = CashuUtils.deserializeOutputData(meltPreview.outputData)
|
||||||
// and Uint8Array→plain-object, so we must restore the types before calling toProof.
|
|
||||||
const reconstructedOutputData = meltPreview.outputData.map((od: any) => {
|
|
||||||
const blindingFactor = BigInt(od.blindingFactor)
|
|
||||||
const secretValues: number[] = typeof od.secret === 'object' && !ArrayBuffer.isView(od.secret)
|
|
||||||
? Object.values(od.secret as Record<string, number>)
|
|
||||||
: Array.from(od.secret as Uint8Array)
|
|
||||||
const secret = new Uint8Array(secretValues)
|
|
||||||
return new OutputData(od.blindedMessage, blindingFactor, secret)
|
|
||||||
})
|
|
||||||
|
|
||||||
const change = quote.change.map((sig, i) => reconstructedOutputData[i].toProof(sig, keyset))
|
const change = quote.change.map((sig, i) => reconstructedOutputData[i].toProof(sig, keyset))
|
||||||
|
|
||||||
|
log.trace('[handlePendingMelt] Change unblinded', { transactionId, quoteId, change })
|
||||||
|
|
||||||
currentCounter.removeMeltCounterValue(transactionId)
|
currentCounter.removeMeltCounterValue(transactionId)
|
||||||
|
|
||||||
if (change.length > 0) {
|
if (change.length > 0) {
|
||||||
@@ -2410,7 +2396,7 @@ const handlePendingMeltTask = async (params: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
log.error('[handlePendingMelt] change recovery failed, completing without change', e.message)
|
log.error('[handlePendingMelt] change recovery failed, completing without change', {message: e.message})
|
||||||
}
|
}
|
||||||
|
|
||||||
const txData: TransactionData[] = transaction.data ? JSON.parse(transaction.data) : []
|
const txData: TransactionData[] = transaction.data ? JSON.parse(transaction.data) : []
|
||||||
@@ -2540,7 +2526,7 @@ const handleClaimTask = async function (params: {
|
|||||||
|
|
||||||
const result: TransactionTaskResult = await receiveTask(
|
const result: TransactionTaskResult = await receiveTask(
|
||||||
decoded,
|
decoded,
|
||||||
tokenInfo.amount,
|
Number(tokenInfo.amount),
|
||||||
tokenInfo.memo || 'Received to Lightning address',
|
tokenInfo.memo || 'Received to Lightning address',
|
||||||
encodedToken,
|
encodedToken,
|
||||||
)
|
)
|
||||||
@@ -2838,7 +2824,7 @@ const handleReceivedEventTask = async function (encryptedEvent: NostrEvent): Pro
|
|||||||
|
|
||||||
// keysetsV2 support
|
// keysetsV2 support
|
||||||
const tokenInfo = getTokenMetadata(incoming.encoded)
|
const tokenInfo = getTokenMetadata(incoming.encoded)
|
||||||
const amountToReceive = tokenInfo.amount
|
const amountToReceive = Number(tokenInfo.amount)
|
||||||
const memo = tokenInfo.memo || 'Received over Nostr'
|
const memo = tokenInfo.memo || 'Received over Nostr'
|
||||||
const {unit, mint: mintUrl} = tokenInfo
|
const {unit, mint: mintUrl} = tokenInfo
|
||||||
|
|
||||||
@@ -3041,7 +3027,8 @@ const handleReceivedEventTask = async function (encryptedEvent: NostrEvent): Pro
|
|||||||
}
|
}
|
||||||
|
|
||||||
// create draft transaction
|
// create draft transaction
|
||||||
const {amount, unit, description, id, mints} = decoded
|
const {amount: rawAmount, unit, description, id, mints} = decoded
|
||||||
|
const amount = Number(rawAmount)
|
||||||
|
|
||||||
const availableBalances: MintBalance[] = []
|
const availableBalances: MintBalance[] = []
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"exclude": ["**/node_modules", "**/Pods"],
|
"exclude": ["**/node_modules", "**/Pods"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
"typeRoots": ["./src/utils/envtypes"],
|
"typeRoots": ["./src/utils/envtypes", "./node_modules/@types"],
|
||||||
"lib": ["ES2022"],
|
"lib": ["ES2022"],
|
||||||
"paths": {
|
"paths": {
|
||||||
"nostr-tools/*": ["./node_modules/nostr-tools/lib/types/*"]
|
"nostr-tools/*": ["./node_modules/nostr-tools/lib/types/*"]
|
||||||
|
|||||||
62
yarn.lock
62
yarn.lock
@@ -2869,15 +2869,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@cashu/cashu-ts@npm:^3.6.1":
|
"@cashu/cashu-ts@npm:^4.2.1":
|
||||||
version: 3.6.1
|
version: 4.2.1
|
||||||
resolution: "@cashu/cashu-ts@npm:3.6.1"
|
resolution: "@cashu/cashu-ts@npm:4.2.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@noble/curves": ^2.0.1
|
"@noble/curves": ^2.2.0
|
||||||
"@noble/hashes": ^2.0.1
|
"@noble/hashes": ^2.2.0
|
||||||
"@scure/base": ^2.0.0
|
"@scure/base": ^2.2.0
|
||||||
"@scure/bip32": ^2.0.1
|
"@scure/bip32": ^2.2.0
|
||||||
checksum: 07018c68fa9b5bd8affde081a6f5c6fb0ba67e1e6e1a3872527547461aea57a3aeb6bb6dace5dad430464b17f53503ad11d6a56b985a43cea3a8a18e8e86838b
|
checksum: a950f38686fcab220a61ea7b83c08f14f8af0ed5b54a18b38f8a370fc3e63897a6c1f266d8e7e9e269f05413d8b5f9d58d319bd1b194b2e2830cb7983169c931
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -5433,12 +5433,12 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@noble/curves@npm:2.0.1, @noble/curves@npm:^2.0.1":
|
"@noble/curves@npm:2.2.0, @noble/curves@npm:^2.2.0":
|
||||||
version: 2.0.1
|
version: 2.2.0
|
||||||
resolution: "@noble/curves@npm:2.0.1"
|
resolution: "@noble/curves@npm:2.2.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@noble/hashes": 2.0.1
|
"@noble/hashes": 2.2.0
|
||||||
checksum: b6844f350d629bb6de55d3a123bc148403901c8b8eed77d2132a389d080d553242bcf1e06913bf51f51e395849ec70b9a8e8902c19e562ea3c6538bb0240d92f
|
checksum: 2b6f02c18918578f528791644886f54333c67323f5ccefe10bf250f08760f63786b807390d8d5f8562c661e70dd3559ce8e51e1fe360118c8fd47071c2bd6991
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -5465,13 +5465,20 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@noble/hashes@npm:2.0.1, @noble/hashes@npm:^2.0.1":
|
"@noble/hashes@npm:2.0.1":
|
||||||
version: 2.0.1
|
version: 2.0.1
|
||||||
resolution: "@noble/hashes@npm:2.0.1"
|
resolution: "@noble/hashes@npm:2.0.1"
|
||||||
checksum: aea67a671ca464027cd512d07666a804bd99abc343ba98c93ee8463a1c870c5594f8de468279ef3c60f3b23b6330db9f1a92bd23723b8a911ec73bddc9420248
|
checksum: aea67a671ca464027cd512d07666a804bd99abc343ba98c93ee8463a1c870c5594f8de468279ef3c60f3b23b6330db9f1a92bd23723b8a911ec73bddc9420248
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@noble/hashes@npm:^1.1.5":
|
"@noble/hashes@npm:^1.1.5":
|
||||||
version: 1.8.0
|
version: 1.8.0
|
||||||
resolution: "@noble/hashes@npm:1.8.0"
|
resolution: "@noble/hashes@npm:1.8.0"
|
||||||
@@ -6405,13 +6412,20 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@scure/base@npm:2.0.0, @scure/base@npm:^2.0.0":
|
"@scure/base@npm:2.0.0":
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
resolution: "@scure/base@npm:2.0.0"
|
resolution: "@scure/base@npm:2.0.0"
|
||||||
checksum: 15d10f154256f52bb3e02b63cdf93b6df6db3814fa4c61a8bb2d339831aec7859609fb715b93c7fc852755f0fe67fb6411664c06159745711bf38af3b10ab93e
|
checksum: 15d10f154256f52bb3e02b63cdf93b6df6db3814fa4c61a8bb2d339831aec7859609fb715b93c7fc852755f0fe67fb6411664c06159745711bf38af3b10ab93e
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@scure/base@npm:~1.1.0":
|
"@scure/base@npm:~1.1.0":
|
||||||
version: 1.1.9
|
version: 1.1.9
|
||||||
resolution: "@scure/base@npm:1.1.9"
|
resolution: "@scure/base@npm:1.1.9"
|
||||||
@@ -6430,14 +6444,14 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@scure/bip32@npm:^2.0.1":
|
"@scure/bip32@npm:^2.2.0":
|
||||||
version: 2.0.1
|
version: 2.2.0
|
||||||
resolution: "@scure/bip32@npm:2.0.1"
|
resolution: "@scure/bip32@npm:2.2.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@noble/curves": 2.0.1
|
"@noble/curves": 2.2.0
|
||||||
"@noble/hashes": 2.0.1
|
"@noble/hashes": 2.2.0
|
||||||
"@scure/base": 2.0.0
|
"@scure/base": 2.2.0
|
||||||
checksum: 5e6c7b455c4a5599673d5fa1b7edc1b3655fe2f7b8388dc19ebe4a89adb9c80c511215d4af624eb774c63c216fd8b7bb8fecf1eb8c3d5e979e279c7542657a42
|
checksum: 6cd0ddf603b739d3c24a13618fc246d409bbf0dffec22bc2c3c8e3e64271bb58c7078bea0b6ab9601fcfe881a969ee31e3074d478affe13a439d7527a0b68ecf
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -14606,7 +14620,7 @@ __metadata:
|
|||||||
"@babel/plugin-proposal-export-namespace-from": ^7.18.9
|
"@babel/plugin-proposal-export-namespace-from": ^7.18.9
|
||||||
"@babel/preset-env": ^7.25.3
|
"@babel/preset-env": ^7.25.3
|
||||||
"@babel/runtime": ^7.25.0
|
"@babel/runtime": ^7.25.0
|
||||||
"@cashu/cashu-ts": ^3.6.1
|
"@cashu/cashu-ts": ^4.2.1
|
||||||
"@fortawesome/fontawesome-svg-core": ^7.1.0
|
"@fortawesome/fontawesome-svg-core": ^7.1.0
|
||||||
"@fortawesome/free-brands-svg-icons": ^7.1.0
|
"@fortawesome/free-brands-svg-icons": ^7.1.0
|
||||||
"@fortawesome/free-regular-svg-icons": ^7.1.0
|
"@fortawesome/free-regular-svg-icons": ^7.1.0
|
||||||
@@ -14635,7 +14649,7 @@ __metadata:
|
|||||||
"@react-navigation/native": ^7.1.18
|
"@react-navigation/native": ^7.1.18
|
||||||
"@react-navigation/native-stack": ^7.3.27
|
"@react-navigation/native-stack": ^7.3.27
|
||||||
"@react-navigation/stack": ^7.4.9
|
"@react-navigation/stack": ^7.4.9
|
||||||
"@scure/bip32": ^2.0.1
|
"@scure/bip32": ^2.2.0
|
||||||
"@scure/bip39": 2.0.1
|
"@scure/bip39": 2.0.1
|
||||||
"@sentry/cli": ^2.56.0
|
"@sentry/cli": ^2.56.0
|
||||||
"@sentry/react-native": ^7.7.0
|
"@sentry/react-native": ^7.7.0
|
||||||
|
|||||||
Reference in New Issue
Block a user