v3 broken

This commit is contained in:
minibits-cash
2026-01-10 00:39:30 +01:00
parent d80232f700
commit 658e62cd75
28 changed files with 365 additions and 242 deletions
-1
View File
@@ -1 +0,0 @@
019b98cc-e66d-7947-b6a6-e253c1830c03
+39
View File
@@ -8,7 +8,46 @@ const { withSentryConfig } = require("@sentry/react-native/metro");
* @type {import('@react-native/metro-config').MetroConfig} * @type {import('@react-native/metro-config').MetroConfig}
*/ */
const config = { const config = {
resolver: {
// Ensure Metro can resolve .cjs files (CommonJS modules)
sourceExts: ['jsx', 'js', 'ts', 'tsx', 'json', 'cjs'],
// Workaround for Unicode character issues in @noble/curves
// Files with Greek letters (β, α) cause "Property β does not exist" errors in Hermes
/*resolveRequest: (context, moduleName, platform) => {
// Force cashu-ts to use pre-built CJS bundle
if (moduleName === '@cashu/cashu-ts') {
return {
filePath: context.resolveRequest(context, '@cashu/cashu-ts/lib/cashu-ts.cjs', platform).filePath,
type: 'sourceFile',
};
}
// Block problematic @noble/curves files that contain Unicode characters
// Allow only essential modules needed for secp256k1
if (moduleName.startsWith('@noble/curves/')) {
const allowed = [
'secp256k1',
'abstract/modular',
'abstract/weierstrass',
'_shortw_utils',
'utils'
];
const isAllowed = allowed.some(path => moduleName.includes(path));
if (!isAllowed) {
// Return empty stub for blocked imports (contains Unicode chars incompatible with Hermes)
const stubPath = require('path').resolve(__dirname, 'node_modules/.metro-stub.js');
return {
filePath: stubPath,
type: 'sourceFile',
};
}
}
// Default resolution
return context.resolveRequest(context, moduleName, platform);
},*/
},
}; };
module.exports = withSentryConfig(mergeConfig(getDefaultConfig(__dirname), config)); module.exports = withSentryConfig(mergeConfig(getDefaultConfig(__dirname), config));
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "minibits_wallet", "name": "minibits_wallet",
"version": "0.3.4-beta.13", "version": "0.3.4-beta.14",
"private": true, "private": true,
"scripts": { "scripts": {
"android:clean": "cd android && ./gradlew clean", "android:clean": "cd android && ./gradlew clean",
@@ -21,7 +21,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": "minibits-cash/cashu-ts#e97b1a4ee9a7ddbb1468302c6996a9d6d3efcb6f", "@cashu/cashu-ts": "3.2.2",
"@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",
+31 -48
View File
@@ -1,53 +1,36 @@
diff --git a/node_modules/@scure/bip32/index.ts b/node_modules/@scure/bip32/index.ts diff --git a/node_modules/@scure/bip32/lib/esm/index.js b/node_modules/@scure/bip32/lib/esm/index.js
index 0c9c91c..14c9ae2 100644 index ea306d0..c0ed23e 100644
--- a/node_modules/@scure/bip32/index.ts --- a/node_modules/@scure/bip32/lib/esm/index.js
+++ b/node_modules/@scure/bip32/index.ts +++ b/node_modules/@scure/bip32/lib/esm/index.js
@@ -25,6 +25,7 @@ import { bytesToHex, concatBytes, createView, hexToBytes, utf8ToBytes } from '@n @@ -25,6 +25,8 @@ import { bytesToHex, concatBytes, createView, hexToBytes, utf8ToBytes } from '@n
import { secp256k1 as secp } from '@noble/curves/secp256k1'; import { secp256k1 as secp } from '@noble/curves/secp256k1';
import { mod } from '@noble/curves/abstract/modular'; import { mod } from '@noble/curves/abstract/modular';
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.ProjectivePoint; const Point = secp.ProjectivePoint;
const base58check = createBase58check(sha256); const base58check = createBase58check(sha256);
@@ -114,7 +115,8 @@ export class HDKey { function bytesToNumber(bytes) {
seed.length @@ -89,7 +91,9 @@ export class HDKey {
); throw new Error('HDKey: seed length must be between 128 and 512 bits; 256 bits is advised, got ' +
} seed.length);
- const I = hmac(sha512, MASTER_SECRET, seed); }
+ // const I = hmac(sha512, MASTER_SECRET, seed); - const I = hmac(sha512, MASTER_SECRET, seed);
+ const I = new Uint8Array(QuickCrypto.createHmac('sha512', MASTER_SECRET).update(seed).digest()); + // const I = hmac(sha512, MASTER_SECRET, seed);
return new HDKey({ + console.log('[bip32.fromMasterSeed] Using ESM quickCrypto HMAC');
versions, + const I = new Uint8Array(quickCrypto.createHmac('sha512', MASTER_SECRET).update(seed).digest());
chainCode: I.slice(32), return new HDKey({
@@ -237,7 +239,8 @@ export class HDKey { versions,
// Normal child: serP(point(kpar)) || ser32(index) chainCode: I.slice(32),
data = concatBytes(this.pubKey, data); @@ -206,7 +210,10 @@ export class HDKey {
} // Normal child: serP(point(kpar)) || ser32(index)
- const I = hmac(sha512, this.chainCode, data); data = concatBytes(this.pubKey, data);
+ //const I = hmac(sha512, this.chainCode, data); }
+ const I = new Uint8Array(QuickCrypto.createHmac('sha512', this.chainCode).update(data).digest()) - const I = hmac(sha512, this.chainCode, data);
const childTweak = bytesToNumber(I.slice(0, 32)); + // const I = hmac(sha512, this.chainCode, data);
const chainCode = I.slice(32); + console.log('[bip32.deriveChild] Using ESM quickCrypto HMAC');
if (!secp.utils.isValidPrivateKey(childTweak)) { + const I = new Uint8Array(quickCrypto.createHmac('sha512', this.chainCode).update(data).digest())
diff --git a/node_modules/@scure/bip32/package.json b/node_modules/@scure/bip32/package.json +
index e866fba..6389575 100644 const childTweak = bytesToNumber(I.slice(0, 32));
--- a/node_modules/@scure/bip32/package.json const chainCode = I.slice(32);
+++ b/node_modules/@scure/bip32/package.json if (!secp.utils.isValidPrivateKey(childTweak)) {
@@ -6,15 +6,8 @@
"index.ts",
"./lib"
],
- "main": "./lib/index.js",
- "module": "./lib/esm/index.js",
+ "main": "./index.ts",
"types": "./lib/index.d.ts",
- "exports": {
- ".": {
- "import": "./lib/esm/index.js",
- "require": "./lib/index.js"
- }
- },
"dependencies": {
"@noble/curves": "~1.8.1",
"@noble/hashes": "~1.7.1",
+23 -2
View File
@@ -1,5 +1,26 @@
diff --git a/node_modules/@scure/bip39/esm/index.js b/node_modules/@scure/bip39/esm/index.js
index d5734b0..deb1491 100644
--- a/node_modules/@scure/bip39/esm/index.js
+++ b/node_modules/@scure/bip39/esm/index.js
@@ -32,6 +32,7 @@ import { pbkdf2, pbkdf2Async } from '@noble/hashes/pbkdf2';
import { sha256, sha512 } from '@noble/hashes/sha2';
import { abytes, anumber, randomBytes } from '@noble/hashes/utils';
import { utils as baseUtils } from '@scure/base';
+import quickCrypto from 'react-native-quick-crypto'
// Japanese wordlist
const isJapanese = (wordlist) => wordlist[0] === '\u3042\u3044\u3053\u304f\u3057\u3093';
// Normalization replaces equivalent sequences of characters
@@ -157,5 +158,7 @@ export function mnemonicToSeed(mnemonic, passphrase = '') {
* // new Uint8Array([...64 bytes])
*/
export function mnemonicToSeedSync(mnemonic, passphrase = '') {
- return pbkdf2(sha512, normalize(mnemonic).nfkd, psalt(passphrase), { c: 2048, dkLen: 64 });
+ console.log('[bip39.mnemonicToSeedSync] Using ESM quickCrypto PBKDF2 Sync');
+ return new Uint8Array(quickCrypto.pbkdf2Sync(normalize(mnemonic).nfkd, psalt(passphrase), 2048, 64, 'sha512'));
+ //return pbkdf2(sha512, normalize(mnemonic).nfkd, psalt(passphrase), { c: 2048, dkLen: 64 });
}
diff --git a/node_modules/@scure/bip39/src/index.ts b/node_modules/@scure/bip39/src/index.ts diff --git a/node_modules/@scure/bip39/src/index.ts b/node_modules/@scure/bip39/src/index.ts
index 4284c3e..6ff83b2 100644 index 4284c3e..71e39e9 100644
--- a/node_modules/@scure/bip39/src/index.ts --- a/node_modules/@scure/bip39/src/index.ts
+++ b/node_modules/@scure/bip39/src/index.ts +++ b/node_modules/@scure/bip39/src/index.ts
@@ -33,6 +33,7 @@ import { pbkdf2, pbkdf2Async } from '@noble/hashes/pbkdf2'; @@ -33,6 +33,7 @@ import { pbkdf2, pbkdf2Async } from '@noble/hashes/pbkdf2';
@@ -16,7 +37,7 @@ index 4284c3e..6ff83b2 100644
export function mnemonicToSeedSync(mnemonic: string, passphrase = ''): Uint8Array { export function mnemonicToSeedSync(mnemonic: string, passphrase = ''): Uint8Array {
- return pbkdf2(sha512, normalize(mnemonic).nfkd, psalt(passphrase), { c: 2048, dkLen: 64 }); - return pbkdf2(sha512, normalize(mnemonic).nfkd, psalt(passphrase), { c: 2048, dkLen: 64 });
+ // return pbkdf2(sha512, normalize(mnemonic).nfkd, psalt(passphrase), { c: 2048, dkLen: 64 }); + // return pbkdf2(sha512, normalize(mnemonic).nfkd, psalt(passphrase), { c: 2048, dkLen: 64 });
+ + console.log('[mnemonicToSeedSync] Using TS quickCrypto PBKDF2 Sync');
+ const updated = new Uint8Array(QuickCrypto.pbkdf2Sync(normalize(mnemonic).nfkd, psalt(passphrase), 2048, 64, 'sha512')); + const updated = new Uint8Array(QuickCrypto.pbkdf2Sync(normalize(mnemonic).nfkd, psalt(passphrase), 2048, 64, 'sha512'));
+ // console.log('[@scure.bip39.mnemonicToSeedSync] Patched to use QuickCrypto.pbkdf2Sync') + // console.log('[@scure.bip39.mnemonicToSeedSync] Patched to use QuickCrypto.pbkdf2Sync')
+ return updated + return updated
+7 -6
View File
@@ -97,6 +97,12 @@ function App() {
if (isInternetReachable === true) { if (isInternetReachable === true) {
if(userSettingsStore.isOnboarded === false) {
log.trace('[App] User not onboarded → new install, skipping device re-enrollment')
setIsDeviceAuthenticated(true)
return
}
if(authStore.isRefreshTokenExpired) { if(authStore.isRefreshTokenExpired) {
log.trace('[App] Network is online and refresh token expired → attempting device re-enrollment') log.trace('[App] Network is online and refresh token expired → attempting device re-enrollment')
@@ -110,12 +116,7 @@ function App() {
log.trace('[App] Device re-enrollment successful') log.trace('[App] Device re-enrollment successful')
} catch (e: any) { } catch (e: any) {
if (e.name !== Err.NOTFOUND_ERROR) { log.error('[App] Device re-enrollment failed', { message: e.message })
log.error('[App] Device re-enrollment failed', { message: e.message })
} else {
// Expected on fresh install
userSettingsStore.setIsOnboarded(false)
}
} finally { } finally {
// Always unblock the app // Always unblock the app
setIsDeviceAuthenticated(true) setIsDeviceAuthenticated(true)
+2 -2
View File
@@ -3,7 +3,7 @@ import { View, ScrollView, LayoutAnimation, Platform, UIManager, ViewStyle } fro
import { BottomModal, Button, Icon, ListItem, Text } from '../components' import { BottomModal, Button, Icon, ListItem, Text } from '../components'
import AppError, { Err } from '../utils/AppError' import AppError, { Err } from '../utils/AppError'
import { spacing, useThemeColor, colors } from '../theme' import { spacing, useThemeColor, colors } from '../theme'
import { isObj } from '@cashu/cashu-ts/src/utils' import { CashuUtils } from '../services/cashu/cashuUtils'
import JSONTree from 'react-native-json-tree' import JSONTree from 'react-native-json-tree'
import Clipboard from '@react-native-clipboard/clipboard' import Clipboard from '@react-native-clipboard/clipboard'
import { useStores } from '../models' import { useStores } from '../models'
@@ -85,7 +85,7 @@ export const ErrorModal: FC<ErrorModalProps> = function ({ error }) {
/> />
) : ( ) : (
<> <>
{error.params && isObj(error.params) && ( {error.params && CashuUtils.isObj(error.params) && (
<> <>
{isParamsVisible ? ( {isParamsVisible ? (
<> <>
+2 -2
View File
@@ -4,7 +4,7 @@ import {
type GetInfoResponse, type GetInfoResponse,
type MintKeys as CashuMintKeys, type MintKeys as CashuMintKeys,
type MintKeyset as CashuMintKeyset, type MintKeyset as CashuMintKeyset,
CashuMint, Mint as CashuMint,
} from '@cashu/cashu-ts' } from '@cashu/cashu-ts'
import {colors, getRandomIconColor} from '../theme' import {colors, getRandomIconColor} from '../theme'
import { log } from '../services' import { log } from '../services'
@@ -16,7 +16,7 @@ import { generateId } from '../utils/utils'
import { Proof } from './Proof' import { Proof } from './Proof'
import { CashuProof, CashuUtils } from '../services/cashu/cashuUtils' import { CashuProof, CashuUtils } from '../services/cashu/cashuUtils'
ß
export type MintBalance = { export type MintBalance = {
mintUrl: string mintUrl: string
balances: { balances: {
+5 -3
View File
@@ -13,7 +13,9 @@ import {
import {log} from '../services/logService' import {log} from '../services/logService'
import AppError, { Err } from '../utils/AppError' import AppError, { Err } from '../utils/AppError'
import { import {
CashuMint, Mint as CashuMint,
GetKeysetsResponse,
GetKeysResponse,
MintActiveKeys, MintActiveKeys,
MintAllKeysets, MintAllKeysets,
type MintKeys as CashuMintKeys, type MintKeys as CashuMintKeys,
@@ -124,8 +126,8 @@ export const MintsStoreModel = types
const newMint = new CashuMint(mintUrl) const newMint = new CashuMint(mintUrl)
// get fresh keysets // get fresh keysets
const keySetResult: MintAllKeysets = yield newMint.getKeySets() const keySetResult: GetKeysetsResponse = yield newMint.getKeySets()
const keysResult: MintActiveKeys = yield newMint.getKeys() const keysResult: GetKeysResponse = yield newMint.getKeys()
const {keysets} = keySetResult const {keysets} = keySetResult
const {keysets: keys} = keysResult const {keysets: keys} = keysResult
+8
View File
@@ -321,6 +321,14 @@ import {
Database.updateProofsMintUrl(currentMintUrl, updatedMintUrl) Database.updateProofsMintUrl(currentMintUrl, updatedMintUrl)
log.trace('[updateMintUrl] Updated mint URL in proofs') log.trace('[updateMintUrl] Updated mint URL in proofs')
}, },
// Action for importing proofs from backup without validation or side effects
importProofs(proofs: Proof[]) {
for (const proof of proofs) {
self.proofs.put(ProofModel.create(proof))
}
log.trace('[importProofs]', `Imported ${proofs.length} proofs from backup`)
},
})) }))
// ───────────────────── DERIVED VIEWS ───────────────────── // ───────────────────── DERIVED VIEWS ─────────────────────
+80 -43
View File
@@ -1,20 +1,20 @@
import {Instance, SnapshotOut, types, flow, getRoot, getSnapshot} from 'mobx-state-tree' import {Instance, SnapshotOut, types, flow, getRoot, getSnapshot} from 'mobx-state-tree'
import { import {
CashuMint, Mint as CashuMint,
CashuWallet, Wallet as CashuWallet,
MeltQuoteResponse, MeltQuoteBolt11Response,
setGlobalRequestOptions, setGlobalRequestOptions,
type MintKeys, type MintKeys,
type MintKeyset, type MintKeyset,
MintAllKeysets,
MintActiveKeys,
Token, Token,
MeltProofsResponse, MeltProofsResponse,
CheckStateEnum, CheckStateEnum,
OutputAmounts, GetKeysetsResponse,
MintQuoteResponse, GetKeysResponse,
MintQuoteBolt11Response,
type ProofState,
type OperationCounters,
} from '@cashu/cashu-ts' } from '@cashu/cashu-ts'
import { isObj } from '@cashu/cashu-ts/src/utils'
import { JS_BUNDLE_VERSION } from '@env' import { JS_BUNDLE_VERSION } from '@env'
import {KeyChain, MinibitsClient, WalletKeys} from '../services' import {KeyChain, MinibitsClient, WalletKeys} from '../services'
import {log} from '../services/logService' import {log} from '../services/logService'
@@ -25,7 +25,6 @@ import { Proof } from './Proof'
import { InFlightRequest, Mint } from './Mint' import { InFlightRequest, Mint } from './Mint'
import { getRootStore } from './helpers/getRootStore' import { getRootStore } from './helpers/getRootStore'
import { getUnixTime } from 'date-fns/getUnixTime'
//refresh //refresh
/* /*
@@ -43,7 +42,6 @@ export type ReceiveParams = {
token: string | Token, token: string | Token,
options?: { options?: {
keysetId?: string; keysetId?: string;
outputAmounts?: OutputAmounts;
proofsWeHave?: Array<CashuProof>; proofsWeHave?: Array<CashuProof>;
counter?: number; counter?: number;
pubkey?: string; pubkey?: string;
@@ -57,7 +55,6 @@ export type SendParams = {
amount: number, amount: number,
proofs: Array<CashuProof>, proofs: Array<CashuProof>,
options?: { options?: {
outputAmounts?: OutputAmounts;
proofsWeHave?: Array<CashuProof>; proofsWeHave?: Array<CashuProof>;
counter?: number; counter?: number;
pubkey?: string; pubkey?: string;
@@ -76,7 +73,6 @@ export type MintParams = {
quote: string, quote: string,
options?: { options?: {
keysetId?: string; keysetId?: string;
outputAmounts?: OutputAmounts;
proofsWeHave?: Array<CashuProof>; proofsWeHave?: Array<CashuProof>;
counter?: number; counter?: number;
pubkey?: string; pubkey?: string;
@@ -85,7 +81,7 @@ export type MintParams = {
export type MeltParams = { export type MeltParams = {
meltQuote: MeltQuoteResponse, meltQuote: MeltQuoteBolt11Response,
proofsToSend: Array<CashuProof>, proofsToSend: Array<CashuProof>,
options?: { options?: {
keysetId?: string; keysetId?: string;
@@ -208,7 +204,7 @@ export const WalletStoreModel = types
const mint = self.mints.find(m => m.mintUrl === mintUrl) const mint = self.mints.find(m => m.mintUrl === mintUrl)
if (mint) { if (mint) {
return mint return mint as CashuMint
} }
setGlobalRequestOptions({ setGlobalRequestOptions({
@@ -255,7 +251,7 @@ export const WalletStoreModel = types
} }
) { ) {
// syncs mint model in wallet state and returns cashu-ts mint class instance // syncs mint model in wallet state and returns cashu-ts mint class instance
const cashuMint = yield self.getMint(mintUrl) const cashuMint: CashuMint = yield self.getMint(mintUrl)
// get uptodate mint model from wallet state // get uptodate mint model from wallet state
const mintInstance = self.getMintModelInstance(mintUrl) const mintInstance = self.getMintModelInstance(mintUrl)
@@ -315,7 +311,7 @@ export const WalletStoreModel = types
if (seedWallet) { if (seedWallet) {
log.trace('[WalletStore.getWallet]', 'Returning CACHED cashuWallet instance with seed', {mintUrl}) log.trace('[WalletStore.getWallet]', 'Returning CACHED cashuWallet instance with seed', {mintUrl})
return seedWallet return seedWallet as CashuWallet
} }
const seed = yield self.getCachedSeed() const seed = yield self.getCachedSeed()
@@ -324,13 +320,13 @@ export const WalletStoreModel = types
unit, unit,
keys: mintInstance.keys, keys: mintInstance.keys,
keysets: mintInstance.keysets, keysets: mintInstance.keysets,
keysetId: walletKeys.id,
bip39seed: seed bip39seed: seed
}) })
// Load uptodate mint info to wallet.mintInfo (NUTS support etc) // Load uptodate mint info to wallet.mintInfo (NUTS support etc)
yield newSeedWallet.getMintInfo() yield newSeedWallet.loadMint()
newSeedWallet.keysetId = walletKeys.id
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})
@@ -352,10 +348,10 @@ export const WalletStoreModel = types
unit, unit,
keys: mintInstance.keys, keys: mintInstance.keys,
keysets: mintInstance.keysets, keysets: mintInstance.keysets,
keysetId: walletKeys.id,
bip39seed: undefined bip39seed: undefined
}) })
newWallet.keysetId = walletKeys.id
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})
@@ -365,7 +361,7 @@ export const WalletStoreModel = types
const cashuMint: CashuMint = yield self.getMint(mintUrl) const cashuMint: CashuMint = yield self.getMint(mintUrl)
try { try {
const {keysets} = yield cashuMint.getKeySets() as Promise<MintAllKeysets> // all keysets const {keysets} = yield cashuMint.getKeySets() as Promise<GetKeysetsResponse> // all keysets
return keysets as MintKeyset[] return keysets as MintKeyset[]
} catch (e: any) { } catch (e: any) {
let message = 'Could not connect to the selected mint.' let message = 'Could not connect to the selected mint.'
@@ -377,7 +373,7 @@ export const WalletStoreModel = types
const cashuMint: CashuMint = yield self.getMint(mintUrl) const cashuMint: CashuMint = yield self.getMint(mintUrl)
try { try {
const {keysets} = yield cashuMint.getKeys() as Promise<MintActiveKeys> // all active keys const {keysets} = yield cashuMint.getKeys() as Promise<GetKeysResponse> // all active keys
return keysets as MintKeys[] return keysets as MintKeys[]
} catch (e: any) { } catch (e: any) {
let message = 'Could not connect to the selected mint.' let message = 'Could not connect to the selected mint.'
@@ -449,7 +445,7 @@ export const WalletStoreModel = types
} }
} }
if(cashuWallet.mintInfo.nuts['19'] && !options?.inFlightRequest) { if(cashuWallet.getMintInfo().nuts['19'] && !options?.inFlightRequest) {
currentCounter.addInFlightRequest(transactionId, receiveParams) currentCounter.addInFlightRequest(transactionId, receiveParams)
} }
@@ -540,7 +536,7 @@ export const WalletStoreModel = types
} }
} }
if(cashuWallet.mintInfo.nuts['19'] && !options?.inFlightRequest) { if(cashuWallet.getMintInfo().nuts['19'] && !options?.inFlightRequest) {
currentCounter.addInFlightRequest(transactionId, sendParams) currentCounter.addInFlightRequest(transactionId, sendParams)
} }
@@ -597,7 +593,27 @@ export const WalletStoreModel = types
log.trace('[WalletStore.getProofsStatesFromMint] start', {mintUrl, unit}) log.trace('[WalletStore.getProofsStatesFromMint] start', {mintUrl, unit})
const cashuWallet: CashuWallet = yield self.getWallet(mintUrl, unit, {withSeed: false}) const cashuWallet: CashuWallet = yield self.getWallet(mintUrl, unit, {withSeed: false})
const proofsByState: {[key in CheckStateEnum]: CashuProof[]} = yield cashuWallet.checkProofsStates(proofs)
// v3.x returns ProofState[] array, not grouped by state
const proofStatesArray: ProofState[] = yield cashuWallet.checkProofsStates(proofs)
// Transform array into grouped structure expected by the rest of the code
// Map proof states back to original proofs using secret matching
const proofsByState: {[key in CheckStateEnum]: CashuProof[]} = {
SPENT: [],
PENDING: [],
UNSPENT: []
}
// proofStatesArray is returned in the same order as input proofs
for (let i = 0; i < proofStatesArray.length; i++) {
const proofState = proofStatesArray[i]
const originalProof = proofs[i]
if (originalProof) {
proofsByState[proofState.state].push(originalProof as CashuProof)
}
}
log.trace('[WalletStore.getProofsStatesFromMint]', {mintUrl, proofsByState}) log.trace('[WalletStore.getProofsStatesFromMint]', {mintUrl, proofsByState})
@@ -625,7 +641,7 @@ export const WalletStoreModel = types
) { ) {
try { try {
const cashuMint = yield self.getMint(mintUrl) const cashuMint = yield self.getMint(mintUrl)
const mintQuoteResponse: MintQuoteResponse = yield cashuMint.createMintQuote({ const mintQuoteResponse: MintQuoteBolt11Response = yield cashuMint.createMintQuoteBolt11({
unit, unit,
amount, amount,
description description
@@ -657,7 +673,7 @@ export const WalletStoreModel = types
) { ) {
try { try {
const cashuMint: CashuMint = yield self.getMint(mintUrl) const cashuMint: CashuMint = yield self.getMint(mintUrl)
const quoteResponse: MintQuoteResponse = yield cashuMint.checkMintQuote( const quoteResponse: MintQuoteBolt11Response = yield cashuMint.checkMintQuoteBolt11(
quote quote
) )
@@ -700,7 +716,7 @@ export const WalletStoreModel = types
throw new AppError(Err.VALIDATION_ERROR, 'Missing mint instance', {mintUrl}) throw new AppError(Err.VALIDATION_ERROR, 'Missing mint instance', {mintUrl})
} }
const cashuWallet = yield self.getWallet( const cashuWallet: CashuWallet = yield self.getWallet(
mintUrl, mintUrl,
unit, unit,
{ {
@@ -717,29 +733,51 @@ export const WalletStoreModel = types
log.debug('[WalletStore.mintProofs] counter', currentCounter.counter) log.debug('[WalletStore.mintProofs] counter', currentCounter.counter)
// Sync wallet's internal counter with our stored counter (v3.x)
yield cashuWallet.counters.advanceToAtLeast(cashuWallet.keysetId, currentCounter.counter)
const mintParams: MintParams = options?.inFlightRequest?.request || { const mintParams: MintParams = options?.inFlightRequest?.request || {
amount, amount,
quote: mintQuote, quote: mintQuote,
options: { options: {
keysetId: cashuWallet.keysetId, keysetId: cashuWallet.keysetId
counter: currentCounter.counter // Note: counter is no longer passed in v3.x, wallet manages it internally
} }
} }
if(cashuWallet.mintInfo.nuts['19'] && !options?.inFlightRequest) { if(cashuWallet.getMintInfo().nuts['19'] && !options?.inFlightRequest) {
currentCounter.addInFlightRequest(transactionId, mintParams) currentCounter.addInFlightRequest(transactionId, mintParams)
} }
let reservedCounters: OperationCounters | undefined = undefined
try { try {
const proofs = yield cashuWallet.mintProofs( const proofs = yield cashuWallet.mintProofsBolt11(
mintParams.amount, mintParams.amount,
mintParams.quote, mintParams.quote,
mintParams.options {
keysetId: mintParams.options?.keysetId,
onCountersReserved: (info) => {
reservedCounters = info as OperationCounters
log.debug('[mintProofsBolt11] Counters reserved', info)
}
}
) )
currentCounter.removeInFlightRequest(transactionId) currentCounter.removeInFlightRequest(transactionId)
// Update our counter to match what the wallet used (v3.x)
if (reservedCounters) {
currentCounter.counter = reservedCounters.next
log.debug('[mintProofs] Updated counter', {
keysetId: reservedCounters.keysetId,
start: reservedCounters.start,
count: reservedCounters.count,
next: reservedCounters.next
})
}
log.debug('[mintProofs]', {amount: mintParams.amount, quote: mintParams.quote, proofs}) log.debug('[mintProofs]', {amount: mintParams.amount, quote: mintParams.quote, proofs})
return proofs return proofs
@@ -770,8 +808,8 @@ export const WalletStoreModel = types
encodedInvoice: string, encodedInvoice: string,
) { ) {
try { try {
const cashuMint = yield self.getMint(mintUrl) const cashuMint: CashuMint = yield self.getMint(mintUrl)
const lightningQuote: MeltQuoteResponse = yield cashuMint.createMeltQuote({ const lightningQuote: MeltQuoteBolt11Response = yield cashuMint.createMeltQuoteBolt11({
unit, unit,
request: encodedInvoice request: encodedInvoice
}) })
@@ -797,7 +835,7 @@ export const WalletStoreModel = types
payLightningMelt: flow(function* payLightningMelt( payLightningMelt: flow(function* payLightningMelt(
mintUrl: string, mintUrl: string,
unit: MintUnit, unit: MintUnit,
meltQuote: MeltQuoteResponse, // invoice is stored by mint by quote meltQuote: MeltQuoteBolt11Response, // invoice is stored by mint by quote
proofsToMeltFrom: Proof[], proofsToMeltFrom: Proof[],
transactionId: number, transactionId: number,
options?: { options?: {
@@ -849,13 +887,13 @@ export const WalletStoreModel = types
} }
} }
if(cashuWallet.mintInfo.nuts['19'] && !options?.inFlightRequest) { if(cashuWallet.getMintInfo().nuts['19'] && !options?.inFlightRequest) {
currentCounter.addInFlightRequest(transactionId, meltParams) currentCounter.addInFlightRequest(transactionId, meltParams)
} }
try { try {
const meltResponse: MeltProofsResponse = yield cashuWallet.meltProofs( const meltResponse = yield cashuWallet.meltProofsBolt11(
meltParams.meltQuote, meltParams.meltQuote,
meltParams.proofsToSend, meltParams.proofsToSend,
meltParams.options meltParams.options
@@ -865,7 +903,7 @@ export const WalletStoreModel = types
log.trace('[payLightningMelt]', {meltResponse}) log.trace('[payLightningMelt]', {meltResponse})
// we normalize naming of returned parameters // we normalize naming of returned parameters
return meltResponse return meltResponse as MeltProofsResponse
} catch (e: any) { } catch (e: any) {
if(!e.message.toLowerCase().includes('timeout') && if(!e.message.toLowerCase().includes('timeout') &&
@@ -896,7 +934,7 @@ export const WalletStoreModel = types
) { ) {
try { try {
const cashuMint: CashuMint = yield self.getMint(mintUrl) const cashuMint: CashuMint = yield self.getMint(mintUrl)
const quoteResponse: MeltQuoteResponse = yield cashuMint.checkMeltQuote( const quoteResponse: MeltQuoteBolt11Response = yield cashuMint.checkMeltQuoteBolt11(
quote quote
) )
@@ -920,7 +958,7 @@ export const WalletStoreModel = types
}), }),
recoverMeltQuoteChange: flow(function* recoverMeltQuoteChange( recoverMeltQuoteChange: flow(function* recoverMeltQuoteChange(
mintUrl: string, mintUrl: string,
meltQuote: MeltQuoteResponse, meltQuote: MeltQuoteBolt11Response,
transactionId: number transactionId: number
) { ) {
try { try {
@@ -991,11 +1029,10 @@ export const WalletStoreModel = types
unit: 'sat', // just use default unit as we restore by keyset unit: 'sat', // just use default unit as we restore by keyset
keys: cashuMint.keys, keys: cashuMint.keys,
keysets: cashuMint.keysets, keysets: cashuMint.keysets,
keysetId,
bip39seed: seed bip39seed: seed
}) })
seedWallet.keysetId = keysetId
const count = Math.abs(indexTo - indexFrom) const count = Math.abs(indexTo - indexFrom)
const {proofs} = yield seedWallet.restore( const {proofs} = yield seedWallet.restore(
@@ -1011,7 +1048,7 @@ export const WalletStoreModel = types
proofs: proofs || [] as Proof[] proofs: proofs || [] as Proof[]
} }
} catch (e: any) { } catch (e: any) {
throw new AppError(Err.MINT_ERROR, isObj(e.message) ? JSON.stringify(e.message) : e.message, {mintUrl}) throw new AppError(Err.MINT_ERROR, CashuUtils.isObj(e.message) ? JSON.stringify(e.message) : e.message, {mintUrl})
} }
}), }),
getMintInfo: flow(function* getMintInfo(mintUrl: string) { getMintInfo: flow(function* getMintInfo(mintUrl: string) {
+2 -6
View File
@@ -35,7 +35,7 @@ import { MintsStoreSnapshot } from '../models/MintsStore'
import { ContactsStoreSnapshot } from '../models/ContactsStore' import { ContactsStoreSnapshot } from '../models/ContactsStore'
import { CashuMint, MintActiveKeys } from '@cashu/cashu-ts' import { CashuMint, MintActiveKeys } from '@cashu/cashu-ts'
import { StaticScreenProps, useNavigation } from '@react-navigation/native' import { StaticScreenProps, useNavigation } from '@react-navigation/native'
import { Proof, ProofModel } from '../models/Proof' import { Proof } from '../models/Proof'
type Props = StaticScreenProps<undefined> type Props = StaticScreenProps<undefined>
@@ -199,11 +199,7 @@ export const ImportBackupScreen = observer(function ImportBackupScreen({ route }
} }
// applySnapshot(proofsStore, walletSnapshot.proofsStore) // applySnapshot(proofsStore, walletSnapshot.proofsStore)
for (const proof of walletSnapshot.proofsStore.proofs) { proofsStore.importProofs(walletSnapshot.proofsStore.proofs)
proofsStore.proofs.put(
ProofModel.create(proof)
)
}
for(const secret of walletSnapshot.proofsStore.pendingByMintSecrets) { for(const secret of walletSnapshot.proofsStore.pendingByMintSecrets) {
proofsStore.pendingByMintSecrets.push(secret) proofsStore.pendingByMintSecrets.push(secret)
} }
+2 -2
View File
@@ -1,6 +1,6 @@
import React, { useEffect, useMemo, useState } from 'react' import React, { useEffect, useMemo, useState } from 'react'
import { GetInfoResponse as CashuGetInfoResponse, SwapMethod } from '@cashu/cashu-ts' import { GetInfoResponse as CashuGetInfoResponse, SwapMethod } from '@cashu/cashu-ts'
import { isObj } from '@cashu/cashu-ts/src/utils' import { CashuUtils } from '../services/cashu/cashuUtils'
import Clipboard from '@react-native-clipboard/clipboard' import Clipboard from '@react-native-clipboard/clipboard'
import { observer } from 'mobx-react-lite' import { observer } from 'mobx-react-lite'
import { getSnapshot } from 'mobx-state-tree' import { getSnapshot } from 'mobx-state-tree'
@@ -529,7 +529,7 @@ function MintInfoDetails(props: { info: GetInfoResponse, popupMessage: (msg: str
text={translate('mintInfo_emptyValueParam', { param: key })} text={translate('mintInfo_emptyValueParam', { param: key })}
/> />
let stringValue = isObj(value) ? JSON.stringify(value) : value.toString() ?? '' let stringValue = CashuUtils.isObj(value) ? JSON.stringify(value) : value.toString() ?? ''
let valueComponent = stringValue.trim() !== '' let valueComponent = stringValue.trim() !== ''
? <Text size='xs' text={stringValue} /> ? <Text size='xs' text={stringValue} />
: missingComponent; : missingComponent;
+1 -2
View File
@@ -36,7 +36,6 @@ import { getSnapshot } from 'mobx-state-tree'
import { scale } from '@gocodingnow/rn-size-matters' import { scale } from '@gocodingnow/rn-size-matters'
import { WalletUtils } from '../services/wallet/utils' import { WalletUtils } from '../services/wallet/utils'
import { MintUnit, formatCurrency, getCurrency } from '../services/wallet/currency' import { MintUnit, formatCurrency, getCurrency } from '../services/wallet/currency'
import { isObj } from '@cashu/cashu-ts/src/utils'
import { translate } from '../i18n' import { translate } from '../i18n'
import { WalletProfileRecord } from '../models/WalletProfileStore' import { WalletProfileRecord } from '../models/WalletProfileStore'
import { MnemonicInput } from './Recovery/MnemonicInput' import { MnemonicInput } from './Recovery/MnemonicInput'
@@ -357,7 +356,7 @@ export const SeedRecoveryScreen = observer(function SeedRecoveryScreen({ route }
e.params = {mintUrl: selectedMintUrl} e.params = {mintUrl: selectedMintUrl}
} }
log.error('[doRecovery]', {name: e.name, message: isObj(e.message) ? JSON.stringify(e.message) : e.message, params: e.params}) log.error('[doRecovery]', {name: e.name, message: CashuUtils.isObj(e.message) ? JSON.stringify(e.message) : e.message, params: e.params})
errors.push({name: e.name, message: e.message}) // TODO this could now be single error as we do not loop anymore errors.push({name: e.name, message: e.message}) // TODO this could now be single error as we do not loop anymore
if (transaction) { if (transaction) {
-4
View File
@@ -55,7 +55,6 @@ import { MemoInputCard } from '../components/MemoInputCard'
import { PaymentRequest as CashuPaymentRequest, PaymentRequestTransport, PaymentRequestTransportType, decodePaymentRequest, getDecodedToken } from '@cashu/cashu-ts' import { PaymentRequest as CashuPaymentRequest, PaymentRequestTransport, PaymentRequestTransportType, decodePaymentRequest, getDecodedToken } from '@cashu/cashu-ts'
import { ProfilePointer } from 'nostr-tools/nip19' import { ProfilePointer } from 'nostr-tools/nip19'
import { MINIBITS_NIP05_DOMAIN } from '@env' import { MINIBITS_NIP05_DOMAIN } from '@env'
import { SEND_TASK } from '../services/wallet/sendTask'
import FastImage from 'react-native-fast-image' import FastImage from 'react-native-fast-image'
import { CurrencyAmount } from './Wallet/CurrencyAmount' import { CurrencyAmount } from './Wallet/CurrencyAmount'
import { CashuUtils } from '../services/cashu/cashuUtils' import { CashuUtils } from '../services/cashu/cashuUtils'
@@ -128,7 +127,6 @@ export const SendScreen = observer(function SendScreen({ route }: Props) {
const [isCashuPrWithDesc, setIsCashuPrWithDesc] = useState(false) const [isCashuPrWithDesc, setIsCashuPrWithDesc] = useState(false)
const [isNostrDMModalVisible, setIsNostrDMModalVisible] = useState(false) const [isNostrDMModalVisible, setIsNostrDMModalVisible] = useState(false)
const [isProofSelectorModalVisible, setIsProofSelectorModalVisible] = useState(false) // offline mode const [isProofSelectorModalVisible, setIsProofSelectorModalVisible] = useState(false) // offline mode
const [isSendTaskSentToQueue, setIsSendTaskSentToQueue] = useState(false)
const [isResultModalVisible, setIsResultModalVisible] = useState(false) const [isResultModalVisible, setIsResultModalVisible] = useState(false)
const [isNostrDMSending, setIsNostrDMSending] = useState(false) const [isNostrDMSending, setIsNostrDMSending] = useState(false)
const [isNostrDMSuccess, setIsNostrDMSuccess] = useState(false) const [isNostrDMSuccess, setIsNostrDMSuccess] = useState(false)
@@ -770,8 +768,6 @@ export const SendScreen = observer(function SendScreen({ route }: Props) {
} }
} }
setIsSendTaskSentToQueue(true)
const result = await WalletTask.sendQueueAwaitable( const result = await WalletTask.sendQueueAwaitable(
mintBalanceToSendFrom as MintBalance, mintBalanceToSendFrom as MintBalance,
amountToSendInt, amountToSendInt,
@@ -255,7 +255,7 @@ export const TransactionListItem = observer(function (props: TransactionListProp
amountStyle={{color: txReceiveColor}} amountStyle={{color: txReceiveColor}}
/> />
)} )}
{[TransactionStatus.ERROR, TransactionStatus.BLOCKED, TransactionStatus.PREPARED_OFFLINE, TransactionStatus.EXPIRED].includes(tx.status) && ( {[TransactionStatus.ERROR, TransactionStatus.BLOCKED, TransactionStatus.PREPARED_OFFLINE, TransactionStatus.PENDING, TransactionStatus.EXPIRED].includes(tx.status) && (
<CurrencyAmount <CurrencyAmount
amount={tx.amount} amount={tx.amount}
mintUnit={tx.unit} mintUnit={tx.unit}
+2 -2
View File
@@ -498,7 +498,7 @@ const onRequestLnurlInvoice = async function () {
amount: roundUp(lnurlPayParams.minSendable / 1000, 0), amount: roundUp(lnurlPayParams.minSendable / 1000, 0),
currency: CurrencyCode.SAT currency: CurrencyCode.SAT
})) }))
return; return
} }
if (lnurlPayParams.maxSendable && amountSats > lnurlPayParams.maxSendable / 1000) { if (lnurlPayParams.maxSendable && amountSats > lnurlPayParams.maxSendable / 1000) {
@@ -506,7 +506,7 @@ const onRequestLnurlInvoice = async function () {
amount: roundDown(lnurlPayParams.maxSendable / 1000, 0), amount: roundDown(lnurlPayParams.maxSendable / 1000, 0),
currency: CurrencyCode.SAT currency: CurrencyCode.SAT
})) }))
return; return
} }
if (lnurlPayParams.payerData) { if (lnurlPayParams.payerData) {
+18 -2
View File
@@ -10,13 +10,27 @@ import AppError, {Err} from '../../utils/AppError'
import { getDecodedToken } from '@cashu/cashu-ts' import { getDecodedToken } from '@cashu/cashu-ts'
import {Proof} from '../../models/Proof' import {Proof} from '../../models/Proof'
import { log } from '../logService' import { log } from '../logService'
import { decodePaymentRequest, sumProofs } from '@cashu/cashu-ts/src/utils' import { decodePaymentRequest } from '@cashu/cashu-ts'
import { NostrClient } from '../nostrService' import { NostrClient } from '../nostrService'
import { getUnixTime } from 'date-fns/getUnixTime' import { getUnixTime } from 'date-fns/getUnixTime'
import { getSnapshot, isStateTreeNode } from 'mobx-state-tree' import { getSnapshot, isStateTreeNode } from 'mobx-state-tree'
export {CashuProof} export {CashuProof}
/**
* Type guard to check if a value is an object
*/
const isObj = function(v: unknown): v is object {
return typeof v === 'object'
}
/**
* Sum the amounts of an array of proofs
*/
const sumProofs = function(proofs: CashuProof[]): number {
return proofs.reduce((acc: number, proof: CashuProof) => acc + proof.amount, 0)
}
const CASHU_URI_PREFIXES = [ const CASHU_URI_PREFIXES = [
'https://wallet.nutstash.app/#', 'https://wallet.nutstash.app/#',
'https://wallet.cashu.me/?token=', 'https://wallet.cashu.me/?token=',
@@ -407,7 +421,9 @@ export const CashuUtils = {
getP2PKPubkeySecret, getP2PKPubkeySecret,
getP2PKLocktime, getP2PKLocktime,
isTokenP2PKLocked, isTokenP2PKLocked,
isCollidingKeysetId isCollidingKeysetId,
isObj,
sumProofs
} }
+1
View File
@@ -90,6 +90,7 @@ const generateWalletKeys = function () {
const mnemonic = generateMnemonic() const mnemonic = generateMnemonic()
const seedBytesArray = mnemonicToSeedSync(mnemonic) const seedBytesArray = mnemonicToSeedSync(mnemonic)
const seed = Buffer.from(seedBytesArray).toString('base64') const seed = Buffer.from(seedBytesArray).toString('base64')
log.trace('[generateWalletKeys] Seed generated from mnemonic (base64)', {seed})
const seedHash = QuickCrypto const seedHash = QuickCrypto
.createHash('sha256') .createHash('sha256')
.update(seedBytesArray) .update(seedBytesArray)
+1 -2
View File
@@ -12,8 +12,7 @@ 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 } from '@cashu/cashu-ts'
import { getEncodedToken, getKeepAmounts } from '@cashu/cashu-ts/src/utils' import { getEncodedToken } from '@cashu/cashu-ts'
import { Proof } from '../../models/Proof'
const { const {
mintsStore, mintsStore,
+1 -1
View File
@@ -10,7 +10,7 @@ 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 } from '@cashu/cashu-ts'
import { getEncodedToken, getKeepAmounts } from '@cashu/cashu-ts/src/utils' import { getEncodedToken, getKeepAmounts } from '@cashu/cashu-ts'
const { const {
mintsStore, mintsStore,
+4 -4
View File
@@ -9,8 +9,8 @@ import {rootStoreInstance} from '../../models'
import {CashuUtils, CashuProof} from '../cashu/cashuUtils' import {CashuUtils, CashuProof} from '../cashu/cashuUtils'
import AppError, {Err} from '../../utils/AppError' import AppError, {Err} from '../../utils/AppError'
import { import {
CashuMint, Mint as CashuMint,
CashuWallet, Wallet as CashuWallet,
CheckStateEnum, CheckStateEnum,
MintKeyset, MintKeyset,
ProofState, ProofState,
@@ -144,7 +144,7 @@ export const sendTask = async function (
try { try {
log.trace('[send] Subscribing to onProofStateUpdates for proof', {secret: proofsToSend[0]}) log.trace('[send] Subscribing to onProofStateUpdates for proof', {secret: proofsToSend[0]})
const unsub = await wsWallet.onProofStateUpdates( const unsub = await wsWallet.on.proofStateUpdates(
[proofsToSend[0]], [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}`)
@@ -338,7 +338,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}) const walletInstance = await walletStore.getWallet(mintUrl, unit, {withSeed: true}) as CashuWallet
swapFeeReserve = walletInstance.getFeesForProofs(proofsToSendFrom) swapFeeReserve = walletInstance.getFeesForProofs(proofsToSendFrom)
const amountWithFees = amountToSend + swapFeeReserve const amountWithFees = amountToSend + swapFeeReserve
+7 -3
View File
@@ -11,7 +11,11 @@ import { WalletUtils } from './utils'
import { MintUnit } from './currency' import { MintUnit } from './currency'
import { NostrEvent } from '../nostrService' import { NostrEvent } from '../nostrService'
import AppError, { Err } from '../../utils/AppError' import AppError, { Err } from '../../utils/AppError'
import { CashuMint, CashuWallet, MintQuoteResponse } from '@cashu/cashu-ts' import {
MintQuoteBolt11Response,
Mint as CashuMint,
Wallet as CashuWallet,
} from '@cashu/cashu-ts'
import { addSeconds } from 'date-fns/addSeconds' import { addSeconds } from 'date-fns/addSeconds'
const { const {
@@ -125,9 +129,9 @@ export const topupTask = async function (
const wsWallet = new CashuWallet(wsMint) const wsWallet = new CashuWallet(wsMint)
try { try {
const unsub = await wsWallet.onMintQuotePaid( const unsub = await wsWallet.on.mintQuotePaid(
mintQuote, mintQuote,
async (m: MintQuoteResponse) => { async (m: MintQuoteBolt11Response) => {
log.trace(`Websocket: mint quote PAID: ${m.quote}`) log.trace(`Websocket: mint quote PAID: ${m.quote}`)
WalletTask.handlePendingQueue() WalletTask.handlePendingQueue()
unsub() unsub()
+17 -14
View File
@@ -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 {MeltProofsResponse, MeltQuoteResponse, MeltQuoteState, getEncodedToken} from '@cashu/cashu-ts' import {MeltProofsResponse, MeltQuoteBolt11Response, MeltQuoteResponse, MeltQuoteState, getEncodedToken} 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'
@@ -51,6 +51,7 @@ export const transferTask = async function (
let proofsToMeltFromAmount: number = 0 let proofsToMeltFromAmount: number = 0
let meltFeeReserve: number = 0 let meltFeeReserve: number = 0
let meltResponse: MeltProofsResponse let meltResponse: MeltProofsResponse
let meltQuoteCheck: MeltQuoteBolt11Response
try { try {
@@ -249,6 +250,8 @@ export const transferTask = async function (
lightningFeePaid = totalFeePaid - meltFeeReserve lightningFeePaid = totalFeePaid - meltFeeReserve
} }
meltQuoteCheck = await walletStore.checkLightningMeltQuote(mintUrl, meltQuote.quote)
const balanceAfter = proofsStore.getUnitBalance(unit)?.unitBalance const balanceAfter = proofsStore.getUnitBalance(unit)?.unitBalance
// build consolidated update payload // build consolidated update payload
@@ -257,7 +260,7 @@ export const transferTask = async function (
lightningFeePaid, lightningFeePaid,
meltFeePaid, meltFeePaid,
returnedAmount, returnedAmount,
preimage: meltResponse.quote.payment_preimage, preimage: meltQuoteCheck.payment_preimage,
createdAt: new Date(), createdAt: new Date(),
} }
transactionData.push(completedDataItem) transactionData.push(completedDataItem)
@@ -273,8 +276,8 @@ export const transferTask = async function (
updatePayload.outputToken = outputToken updatePayload.outputToken = outputToken
} }
if (meltResponse.quote.payment_preimage) { if (meltQuoteCheck.payment_preimage) {
updatePayload.proof = meltResponse.quote.payment_preimage updatePayload.proof = meltQuoteCheck.payment_preimage
} }
transaction.update(updatePayload) transaction.update(updatePayload)
@@ -287,8 +290,8 @@ export const transferTask = async function (
lightningFeePaid, lightningFeePaid,
meltFeePaid, meltFeePaid,
totalFeePaid, totalFeePaid,
meltQuote: meltResponse.quote, meltQuote: meltQuoteCheck,
preimage: meltResponse.quote.payment_preimage, preimage: meltQuoteCheck.payment_preimage,
nwcEvent nwcEvent
} as TransactionTaskResult } as TransactionTaskResult
@@ -330,31 +333,31 @@ export const transferTask = async function (
if (proofsToMeltFrom.length > 0) { if (proofsToMeltFrom.length > 0) {
const refreshedMeltQuote = await walletStore.checkLightningMeltQuote(mintUrl, meltQuote.quote) meltQuoteCheck = await walletStore.checkLightningMeltQuote(mintUrl, meltQuote.quote)
taskResult.meltQuote = refreshedMeltQuote taskResult.meltQuote = meltQuoteCheck
if(refreshedMeltQuote.state === MeltQuoteState.PAID) { if(meltQuoteCheck.state === MeltQuoteState.PAID) {
message = `Lightning invoice has been successfully paid, however some error occured: ${e.message}` message = `Lightning invoice has been successfully paid, however some error occured: ${e.message}`
taskResult.preimage = refreshedMeltQuote.payment_preimage taskResult.preimage = meltQuoteCheck.payment_preimage
taskResult.message = message taskResult.message = message
const {recoveredAmount} = await WalletTask.recoverMeltQuoteChange({ const {recoveredAmount} = await WalletTask.recoverMeltQuoteChange({
mintUrl, mintUrl,
meltQuote: refreshedMeltQuote, meltQuote: meltQuoteCheck,
}) })
log.error('[transfer]', message, { log.error('[transfer]', message, {
recoveredAmount, recoveredAmount,
error: e.message, error: e.message,
refreshedMeltQuote, meltQuoteCheck,
unit, unit,
transactionId: transaction.id transactionId: transaction.id
}) })
} else if(refreshedMeltQuote.state === MeltQuoteState.PENDING) { } else if(meltQuoteCheck.state === MeltQuoteState.PENDING) {
message = 'Lightning payment did not complete in time. Your ecash will remain pending until the payment completes or fails.' message = 'Lightning payment did not complete in time. Your ecash will remain pending until the payment completes or fails.'
taskResult.message = message taskResult.message = message
@@ -362,7 +365,7 @@ export const transferTask = async function (
log.error('[transfer]', message, { log.error('[transfer]', message, {
error: `${e.message}: ${e.params.message}`, error: `${e.message}: ${e.params.message}`,
unit, unit,
refreshedMeltQuote, meltQuoteCheck,
transactionId: transaction.id transactionId: transaction.id
}) })
+2 -2
View File
@@ -1,10 +1,10 @@
import AppError from "../../utils/AppError" import AppError from "../../utils/AppError"
import { isObj } from '@cashu/cashu-ts/src/utils' import { CashuUtils } from '../cashu/cashuUtils'
const formatError = function (e: AppError) { const formatError = function (e: AppError) {
return { return {
name: e.name, name: e.name,
message: isObj(e.message) ? JSON.stringify(e.message) : e.message.slice(0, 200), message: CashuUtils.isObj(e.message) ? JSON.stringify(e.message) : e.message.slice(0, 200),
params: e.params || {}, params: e.params || {},
} as AppError } as AppError
} }
+18 -9
View File
@@ -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 {MeltQuoteResponse, MeltQuoteState, MintQuoteState, PaymentRequestPayload, Token, getDecodedToken, getEncodedToken} from '@cashu/cashu-ts' import {MeltQuoteBolt11Response, MeltQuoteResponse, MeltQuoteState, MintQuoteState, PaymentRequestPayload, Token, getDecodedToken, getEncodedToken} 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'
@@ -40,7 +40,7 @@ import { MinibitsClient } from './minibitsService'
import { UnsignedEvent } from 'nostr-tools' import { UnsignedEvent } from 'nostr-tools'
import { Platform } from 'react-native' import { Platform } from 'react-native'
import { cashuPaymentRequestTask } from './wallet/cashuPaymentRequestTask' import { cashuPaymentRequestTask } from './wallet/cashuPaymentRequestTask'
import { decodePaymentRequest } from '@cashu/cashu-ts/src/utils' import { decodePaymentRequest } from '@cashu/cashu-ts'
@@ -168,7 +168,7 @@ export interface TransactionTaskResult extends WalletTaskResult {
swapFeePaid?: number swapFeePaid?: number
lightningFeePaid?: number lightningFeePaid?: number
meltFeePaid?: number meltFeePaid?: number
meltQuote?: MeltQuoteResponse meltQuote?: MeltQuoteBolt11Response
nwcEvent?: NostrEvent nwcEvent?: NostrEvent
} }
@@ -1636,7 +1636,16 @@ const handleInFlightByMintTask = async (mint: Mint): Promise<WalletTaskResult> =
// ─── TRANSFER (melt / lightning out retry) ───────────── // ─── TRANSFER (melt / lightning out retry) ─────────────
case TransactionType.TRANSFER: { case TransactionType.TRANSFER: {
const { quote, change } = await walletStore.payLightningMelt( const meltQuoteCheck = await walletStore.checkLightningMeltQuote(mintUrl, inFlight.request.meltQuote)
if(meltQuoteCheck.state === MeltQuoteState.PAID || meltQuoteCheck.state === MeltQuoteState.PENDING) {
log.debug('[handleInFlightByMintTask] Melt quote already PAID or PENDING', { tId: tx.id })
// TODO recover change if any
counter.removeInFlightRequest(inFlight.transactionId)
break
}
const { change } = await walletStore.payLightningMelt(
mintUrl, mintUrl,
unit, unit,
inFlight.request.meltQuote, inFlight.request.meltQuote,
@@ -1667,9 +1676,9 @@ const handleInFlightByMintTask = async (mint: Mint): Promise<WalletTaskResult> =
tx.update({ outputToken }) tx.update({ outputToken })
} }
if (quote.payment_preimage) { /*if (quote.payment_preimage) {
tx.update({ proof: quote.payment_preimage }) tx.update({ proof: quote.payment_preimage })
} }*/
const inputAmount = CashuUtils.getProofsAmount(inFlight.request.proofsToSend) const inputAmount = CashuUtils.getProofsAmount(inFlight.request.proofsToSend)
const changeAmount = CashuUtils.getProofsAmount(change) const changeAmount = CashuUtils.getProofsAmount(change)
@@ -1680,7 +1689,7 @@ const handleInFlightByMintTask = async (mint: Mint): Promise<WalletTaskResult> =
txData.push({ txData.push({
status: TransactionStatus.COMPLETED, status: TransactionStatus.COMPLETED,
changeAmount, changeAmount,
preimage: quote.payment_preimage, //preimage: quote.payment_preimage,
createdAt: new Date(), createdAt: new Date(),
}) })
@@ -2060,7 +2069,7 @@ const recoverMintQuote = async (
const recoverMeltQuoteChange = async ( const recoverMeltQuoteChange = async (
params: { params: {
mintUrl: string mintUrl: string
meltQuote: string | MeltQuoteResponse meltQuote: string | MeltQuoteBolt11Response
} }
): Promise<{ recoveredAmount: number }> => { ): Promise<{ recoveredAmount: number }> => {
const { mintUrl, meltQuote } = params const { mintUrl, meltQuote } = params
@@ -2073,7 +2082,7 @@ const recoverMintQuote = async (
log.trace('[recoverMeltQuoteChange] start', { mintUrl, meltQuote }) log.trace('[recoverMeltQuoteChange] start', { mintUrl, meltQuote })
const meltQuoteResponse: MeltQuoteResponse = const meltQuoteResponse: MeltQuoteBolt11Response =
typeof meltQuote === 'string' typeof meltQuote === 'string'
? await walletStore.checkLightningMeltQuote(mintUrl, meltQuote) ? await walletStore.checkLightningMeltQuote(mintUrl, meltQuote)
: meltQuote : meltQuote
+1 -1
View File
@@ -25,7 +25,7 @@ export enum Err {
} }
export class AppError extends Error { export class AppError extends Error {
public readonly code: Err public readonly code: number | undefined
public readonly params?: Record<string, any> public readonly params?: Record<string, any>
public readonly caller?: string public readonly caller?: string
+50 -40
View File
@@ -2722,28 +2722,14 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@cashu/cashu-ts@minibits-cash/cashu-ts#e97b1a4ee9a7ddbb1468302c6996a9d6d3efcb6f": "@cashu/cashu-ts@npm:3.2.2":
version: 2.4.0 version: 3.2.2
resolution: "@cashu/cashu-ts@https://github.com/minibits-cash/cashu-ts.git#commit=e97b1a4ee9a7ddbb1468302c6996a9d6d3efcb6f" resolution: "@cashu/cashu-ts@npm:3.2.2"
dependencies: dependencies:
"@cashu/crypto": ^0.3.4 "@noble/curves": ^2.0.1
"@noble/curves": ^1.3.0 "@noble/hashes": ^2.0.1
"@noble/hashes": ^1.3.3 "@scure/bip32": ^2.0.1
buffer: ^6.0.3 checksum: 49516a22f3afbf249c52d0f36a9e6513948b763af94c703a05375b513b0d3f90bb1feaade201214d315f5a26305e182ee00493634519ad9b69c7bd44615de11c
checksum: 68d46ceb9c60e9cef305aad86534c08b23de434de1efcd217d00c73bdf84704801c3cd700901903256de5a408caf029cea7886e9a303dd423027f08184bc1cb5
languageName: node
linkType: hard
"@cashu/crypto@npm:^0.3.4":
version: 0.3.4
resolution: "@cashu/crypto@npm:0.3.4"
dependencies:
"@noble/curves": ^1.6.0
"@noble/hashes": ^1.5.0
"@scure/bip32": ^1.5.0
"@scure/bip39": ^1.4.0
buffer: ^6.0.3
checksum: 634afdd9d8fb262cd3db415794661ba5810f4ba1e64d1af0972c9a9b0a8c45106710a00ee25949014a361458ef544bdfcafe926c548c95b05fff9a41600d34a3
languageName: node languageName: node
linkType: hard linkType: hard
@@ -5299,12 +5285,12 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@noble/curves@npm:^1.3.0, @noble/curves@npm:^1.6.0, @noble/curves@npm:~1.8.1": "@noble/curves@npm:2.0.1, @noble/curves@npm:^2.0.1":
version: 1.8.1 version: 2.0.1
resolution: "@noble/curves@npm:1.8.1" resolution: "@noble/curves@npm:2.0.1"
dependencies: dependencies:
"@noble/hashes": 1.7.1 "@noble/hashes": 2.0.1
checksum: 4143f1248ed57c1ae46dfef5c692a91383e5830420b9c72d3ff1061aa9ebbf8999297da6d2aed8a9716fef8e6b1f5a45737feeab02abf55ca2a4f514bf9339ec checksum: b6844f350d629bb6de55d3a123bc148403901c8b8eed77d2132a389d080d553242bcf1e06913bf51f51e395849ec70b9a8e8902c19e562ea3c6538bb0240d92f
languageName: node languageName: node
linkType: hard linkType: hard
@@ -5317,6 +5303,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@noble/curves@npm:~1.8.1":
version: 1.8.1
resolution: "@noble/curves@npm:1.8.1"
dependencies:
"@noble/hashes": 1.7.1
checksum: 4143f1248ed57c1ae46dfef5c692a91383e5830420b9c72d3ff1061aa9ebbf8999297da6d2aed8a9716fef8e6b1f5a45737feeab02abf55ca2a4f514bf9339ec
languageName: node
linkType: hard
"@noble/hashes@npm:1.3.1": "@noble/hashes@npm:1.3.1":
version: 1.3.1 version: 1.3.1
resolution: "@noble/hashes@npm:1.3.1" resolution: "@noble/hashes@npm:1.3.1"
@@ -5331,13 +5326,20 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@noble/hashes@npm:1.7.1, @noble/hashes@npm:^1.3.3, @noble/hashes@npm:^1.5.0, @noble/hashes@npm:~1.7.1": "@noble/hashes@npm:1.7.1, @noble/hashes@npm:^1.3.3, @noble/hashes@npm:~1.7.1":
version: 1.7.1 version: 1.7.1
resolution: "@noble/hashes@npm:1.7.1" resolution: "@noble/hashes@npm:1.7.1"
checksum: 4f1b56428a10323feef17e4f437c9093556cb18db06f94d254043fadb69c3da8475f96eb3f8322d41e8670117d7486475a8875e68265c2839f60fd03edd6a616 checksum: 4f1b56428a10323feef17e4f437c9093556cb18db06f94d254043fadb69c3da8475f96eb3f8322d41e8670117d7486475a8875e68265c2839f60fd03edd6a616
languageName: node languageName: node
linkType: hard linkType: hard
"@noble/hashes@npm:2.0.1, @noble/hashes@npm:^2.0.1":
version: 2.0.1
resolution: "@noble/hashes@npm:2.0.1"
checksum: aea67a671ca464027cd512d07666a804bd99abc343ba98c93ee8463a1c870c5594f8de468279ef3c60f3b23b6330db9f1a92bd23723b8a911ec73bddc9420248
languageName: node
linkType: hard
"@noble/hashes@npm:^1.1.5, @noble/hashes@npm:~1.8.0": "@noble/hashes@npm:^1.1.5, @noble/hashes@npm:~1.8.0":
version: 1.8.0 version: 1.8.0
resolution: "@noble/hashes@npm:1.8.0" resolution: "@noble/hashes@npm:1.8.0"
@@ -6275,6 +6277,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@scure/base@npm:2.0.0":
version: 2.0.0
resolution: "@scure/base@npm:2.0.0"
checksum: 15d10f154256f52bb3e02b63cdf93b6df6db3814fa4c61a8bb2d339831aec7859609fb715b93c7fc852755f0fe67fb6411664c06159745711bf38af3b10ab93e
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"
@@ -6282,7 +6291,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@scure/base@npm:~1.2.2, @scure/base@npm:~1.2.4": "@scure/base@npm:~1.2.2":
version: 1.2.4 version: 1.2.4
resolution: "@scure/base@npm:1.2.4" resolution: "@scure/base@npm:1.2.4"
checksum: db554eb550a1bd17684af9282e1ad751050a13d4add0e83ad61cc496680d7d1c1c1120ca780e72935a293bb59721c20a006a53a5eec6f6b5bdcd702cf27c8cae checksum: db554eb550a1bd17684af9282e1ad751050a13d4add0e83ad61cc496680d7d1c1c1120ca780e72935a293bb59721c20a006a53a5eec6f6b5bdcd702cf27c8cae
@@ -6307,7 +6316,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@scure/bip32@npm:1.6.2, @scure/bip32@npm:^1.5.0": "@scure/bip32@npm:1.6.2":
version: 1.6.2 version: 1.6.2
resolution: "@scure/bip32@npm:1.6.2" resolution: "@scure/bip32@npm:1.6.2"
dependencies: dependencies:
@@ -6318,6 +6327,17 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@scure/bip32@npm:^2.0.1":
version: 2.0.1
resolution: "@scure/bip32@npm:2.0.1"
dependencies:
"@noble/curves": 2.0.1
"@noble/hashes": 2.0.1
"@scure/base": 2.0.0
checksum: 5e6c7b455c4a5599673d5fa1b7edc1b3655fe2f7b8388dc19ebe4a89adb9c80c511215d4af624eb774c63c216fd8b7bb8fecf1eb8c3d5e979e279c7542657a42
languageName: node
linkType: hard
"@scure/bip39@npm:1.2.1": "@scure/bip39@npm:1.2.1":
version: 1.2.1 version: 1.2.1
resolution: "@scure/bip39@npm:1.2.1" resolution: "@scure/bip39@npm:1.2.1"
@@ -6338,16 +6358,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@scure/bip39@npm:^1.4.0":
version: 1.5.4
resolution: "@scure/bip39@npm:1.5.4"
dependencies:
"@noble/hashes": ~1.7.1
"@scure/base": ~1.2.4
checksum: 744f302559ad05ee6ea4928572ac8f0b5443e8068fd53234c9c2e158814e910a043c54f0688d05546decadd2ff66e0d0c76355d10e103a28cb8f44efe140857a
languageName: node
linkType: hard
"@sentry-internal/browser-utils@npm:10.26.0": "@sentry-internal/browser-utils@npm:10.26.0":
version: 10.26.0 version: 10.26.0
resolution: "@sentry-internal/browser-utils@npm:10.26.0" resolution: "@sentry-internal/browser-utils@npm:10.26.0"
@@ -14456,7 +14466,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": "minibits-cash/cashu-ts#e97b1a4ee9a7ddbb1468302c6996a9d6d3efcb6f" "@cashu/cashu-ts": 3.2.2
"@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