From ad90bb3bb2ec7cf88da219eabb901792adc87530 Mon Sep 17 00:00:00 2001 From: minibits-cash Date: Wed, 31 Jan 2024 00:42:04 +0100 Subject: [PATCH] Upgrade cashu-ts lib, improved zaps --- package.json | 2 +- patches/@cashu+cashu-ts+0.9.0.patch | 25 ++++++ src/models/ProofsStore.ts | 3 +- src/models/TransactionsStore.ts | 12 ++- src/screens/PictureScreen.tsx | 8 ++ src/services/minibitsService.ts | 1 + src/services/nostrService.ts | 25 ++++-- src/services/walletService.ts | 128 +++++++++++----------------- yarn.lock | 43 +++------- 9 files changed, 126 insertions(+), 121 deletions(-) diff --git a/package.json b/package.json index 08da1aa3..5fddeab9 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "mobx": "^6.9.0", "mobx-react-lite": "^3.4.3", "mobx-state-tree": "^5.1.8", - "nostr-tools": "^2.1.4", + "nostr-tools": "1.17.0", "numbro": "^2.3.6", "patch-package": "^7.0.0", "postinstall-postinstall": "^2.1.0", diff --git a/patches/@cashu+cashu-ts+0.9.0.patch b/patches/@cashu+cashu-ts+0.9.0.patch index 0ff1a518..09dc2c86 100644 --- a/patches/@cashu+cashu-ts+0.9.0.patch +++ b/patches/@cashu+cashu-ts+0.9.0.patch @@ -1,3 +1,28 @@ +diff --git a/node_modules/@cashu/cashu-ts/package.json b/node_modules/@cashu/cashu-ts/package.json +index 78f8fc1..a392065 100644 +--- a/node_modules/@cashu/cashu-ts/package.json ++++ b/node_modules/@cashu/cashu-ts/package.json +@@ -2,8 +2,7 @@ + "name": "@cashu/cashu-ts", + "version": "0.9.0", + "description": "cashu library for communicating with a cashu mint", +- "main": "dist/lib/es5/index.js", +- "module": "dist/lib/es6/index.js", ++ "main": "src/index.ts", + "repository": { + "type": "git", + "url": "git+https://github.com/cashubtc/cashu-ts" +@@ -47,8 +46,8 @@ + "dependencies": { + "@gandlaf21/bolt11-decode": "^3.0.6", + "@noble/curves": "^1.0.0", +- "@scure/bip32": "^1.3.2", +- "@scure/bip39": "^1.2.1", ++ "@scure/bip32": "^1.3.3", ++ "@scure/bip39": "^1.2.2", + "buffer": "^6.0.3" + } + } diff --git a/node_modules/@cashu/cashu-ts/src/CashuMint.ts b/node_modules/@cashu/cashu-ts/src/CashuMint.ts index f6bc3b1..3b1ca62 100644 --- a/node_modules/@cashu/cashu-ts/src/CashuMint.ts diff --git a/src/models/ProofsStore.ts b/src/models/ProofsStore.ts index c78cf6ce..9bb49a04 100644 --- a/src/models/ProofsStore.ts +++ b/src/models/ProofsStore.ts @@ -93,8 +93,7 @@ export const ProofsStoreModel = types log.debug('[addProofs]', `Added new ${addedProofs.length}${isPending ? ' pending' : ''} proofs to the ProofsStore`,) - const rootStore = getRootStore(self) - const {userSettingsStore} = rootStore + const userSettingsStore = getRootStore(self).userSettingsStore if (userSettingsStore.isLocalBackupOn === true && addedProofs.length > 0) { Database.addOrUpdateProofs(addedProofs, isPending) // isSpent = false diff --git a/src/models/TransactionsStore.ts b/src/models/TransactionsStore.ts index 61f9ce7e..8a702441 100644 --- a/src/models/TransactionsStore.ts +++ b/src/models/TransactionsStore.ts @@ -50,8 +50,8 @@ export const TransactionsStoreModel = types const tx = self.transactions.find(tx => tx.id === id) return tx || undefined }, - recentByHostname(mintHostname: string) { - return this.all.filter(t => t.mint?.includes(mintHostname)).slice(0, maxTransactionsByHostname) + recentByHostname(mintHostname: string) { + return this.all.filter(t => getHostname(t.mint as string) === mintHostname).slice(0, maxTransactionsByHostname) }, getByMint(mintUrl: string) { return this.all.filter(t => t.mint === mintUrl) @@ -254,6 +254,14 @@ export const TransactionsStoreModel = types }, })) + const getHostname = function (mintUrl: string) { + try { + return new URL(mintUrl).hostname + } catch (e) { + return false + } + } + // refresh export interface TransactionsStore extends Instance {} diff --git a/src/screens/PictureScreen.tsx b/src/screens/PictureScreen.tsx index 1bf56f76..9cbc004e 100644 --- a/src/screens/PictureScreen.tsx +++ b/src/screens/PictureScreen.tsx @@ -89,6 +89,14 @@ export const PictureScreen: FC = observer(function PictureSc ) })} + {pictures.length === 0 && ( + } + /> + )} {selectedPicture && ( diff --git a/src/services/minibitsService.ts b/src/services/minibitsService.ts index 02fc7b72..24015f4a 100644 --- a/src/services/minibitsService.ts +++ b/src/services/minibitsService.ts @@ -271,6 +271,7 @@ const checkDonationPaid = async function (paymentHash: string, pubkey: string) { const fetchApi = async (url: string, options: any, timeout = 15000) => { //ms + log.trace('fetchApi', {url}) const controller = new AbortController() diff --git a/src/services/nostrService.ts b/src/services/nostrService.ts index 55828574..68c5ebbf 100644 --- a/src/services/nostrService.ts +++ b/src/services/nostrService.ts @@ -1,4 +1,3 @@ -import { finalizeEvent, verifyEvent } from 'nostr-tools/pure' import { nip19, getEventHash, @@ -9,7 +8,7 @@ import { validateEvent, UnsignedEvent as NostrUnsignedEvent, utils -} from 'nostr-tools/core' +} from 'nostr-tools' import QuickCrypto from 'react-native-quick-crypto' import {secp256k1} from '@noble/curves/secp256k1' import { @@ -21,6 +20,13 @@ import AppError, { Err } from '../utils/AppError' import { MinibitsClient } from './minibitsService' import { rootStoreInstance } from '../models' +export { + Event as NostrEvent, + Filter as NostrFilter, + Kind as NostrKind, + UnsignedEvent as NostrUnsignedEvent, +} from 'nostr-tools' + // refresh export type NostrProfile = { pubkey: string @@ -167,24 +173,25 @@ const publish = async function ( relays: string[], ): Promise { - const keys: KeyPair = await getOrCreateKeyPair() + const keys: KeyPair = await getOrCreateKeyPair() - let signedEvent = finalizeEvent(event, Uint8Array.from(Buffer.from(keys.privateKey, 'hex'))) + event.created_at = Math.floor(Date.now() / 1000) + event.id = getEventHash(event) + event.sig = getSignature(event, keys.privateKey) - if(!validateEvent(signedEvent)) { + if(!validateEvent(event)) { throw new AppError(Err.VALIDATION_ERROR, 'Event is invalid and could not be published', event) } - log.trace('Event to be published', signedEvent, 'publish') + log.trace('Event to be published', event, 'publish') const pool = getRelayPool() - let pubs = pool.publish(relays, signedEvent) - + let pubs = pool.publish(relays, event) await delay(1000) // await Promise.all(pubs) const published: NostrEvent = await pool.get(relays, { - ids: [signedEvent.id] + ids: [event.id] }) diff --git a/src/services/walletService.ts b/src/services/walletService.ts index 91686cb3..2c34e1c2 100644 --- a/src/services/walletService.ts +++ b/src/services/walletService.ts @@ -1,8 +1,8 @@ import {deriveKeysetId, getEncodedToken} from '@cashu/cashu-ts' -import {addSeconds, isBefore} from 'date-fns' +import {isBefore} from 'date-fns' import {getSnapshot, isStateTreeNode} from 'mobx-state-tree' import {log} from './logService' -import {MintClient, MintKeys, MintKeySets} from './cashuMintClient' +import {MintClient, MintKeys} from './cashuMintClient' import {Proof} from '../models/Proof' import { Transaction, @@ -28,11 +28,9 @@ import { NostrClient, NostrEvent, NostrFilter } from './nostrService' import { MINIBITS_NIP05_DOMAIN, MINIBITS_SERVER_API_HOST } from '@env' import { PaymentRequest, PaymentRequestStatus, PaymentRequestType } from '../models/PaymentRequest' import { IncomingDataType, IncomingParser } from './incomingParser' -import { Contact, ContactType } from '../models/Contact' -import { MinibitsClient } from './minibitsService' +import { Contact } from '../models/Contact' import { getDefaultAmountPreference, isObj } from '@cashu/cashu-ts/src/utils' -import { KeyChain } from './keyChain' -import { delay } from '../utils/utils' + type WalletService = { checkPendingSpent: () => Promise @@ -210,7 +208,9 @@ const checkPendingReceived = async function () { if(contactsStore.eventAlreadyReceived(event.id)) { log.error(Err.ALREADY_EXISTS_ERROR, 'Event has been processed in the past, skipping...', {id: event.id}) return - } + } + + contactsStore.addReceivedEventId(event.id) // decrypt message content const decrypted = await NostrClient.decryptNip04(event.pubkey, event.content) @@ -253,19 +253,35 @@ const checkPendingReceived = async function () { log.trace('Incoming data', {incoming}) if(incoming.type === IncomingDataType.CASHU) { - const { - error, - receivedAmount, + + const decoded: Token = CashuUtils.decodeToken(incoming.encoded) + const amountToReceive = CashuUtils.getTokenAmounts(decoded).totalAmount + const memo = decoded.memo || 'Received over Nostr' + + const {transaction, receivedAmount} = await receive( + decoded as Token, + amountToReceive, memo, - } = await receiveFromNostrEvent(incoming.encoded, event) - - let picture: string | undefined = undefined - - if(sentFrom && sentFrom.includes(MINIBITS_NIP05_DOMAIN)) { - picture = MINIBITS_SERVER_API_HOST + '/profile/avatar/' + sentFromPubkey + incoming.encoded as string, + ) + + if(transaction) { + await transactionsStore.updateSentFrom( + transaction.id as number, + sentFrom as string + ) } - + + // + // Send notification event + // if(receivedAmount > 0) { + let picture: string | undefined = undefined + + if(sentFrom && sentFrom.includes(MINIBITS_NIP05_DOMAIN)) { + picture = MINIBITS_SERVER_API_HOST + '/profile/avatar/' + sentFromPubkey + } + result = { status: TransactionStatus.COMPLETED, title: `⚡${receivedAmount} sats received!`, @@ -277,10 +293,6 @@ const checkPendingReceived = async function () { EventEmitter.emit('receiveTokenCompleted', result) } - - if(error) { - throw new AppError(Err.MINT_ERROR, `Error while receiving transaction: ${error.message}`) - } return } @@ -393,55 +405,6 @@ const findMemo = function (message: string): string | undefined { } -const receiveFromNostrEvent = async function (encoded: string, event: NostrEvent) { - try { - const decoded: Token = CashuUtils.decodeToken(encoded) - const sentFrom = getTagValue(event.tags, 'from') - const sentFromPubkey = event.pubkey - const tokenAmounts = CashuUtils.getTokenAmounts(decoded) - const amountToReceive = tokenAmounts.totalAmount - const memo = decoded.memo || '' - - const {transaction, message, error, receivedAmount} = - await receive( - decoded as Token, - amountToReceive, - memo, - encoded as string, - ) - - if(transaction && transaction.status === TransactionStatus.COMPLETED) { - - const updated = JSON.parse(transaction.data) - updated[2].receivedEvent = event - - await transactionsStore.updateStatus( - transaction.id as number, - TransactionStatus.COMPLETED, - JSON.stringify(updated), - ) - - await transactionsStore.updateSentFrom( - transaction.id as number, - sentFrom as string - ) - } - - contactsStore.addReceivedEventId(event.id) - - return { - error: null, - receivedAmount: receivedAmount, - memo, - sentFrom: sentFrom || '', - sentFromPubkey, - } - - } catch (e: any) { - return {error: e, receivedAmount: 0} - } -} - /* * Recover stuck wallet if tx error caused spent proof to remain in wallet. @@ -733,7 +696,7 @@ const _checkInFlightByMint = async function (mint: Mint, seed: Uint8Array) { log.error('[_checkInFlightByMint]', e.name, {message: e.message, mintUrl}) return { mintUrl, - error: e, + error: {name: e.name, message: e.message} } } } @@ -855,6 +818,8 @@ const receive = async function ( transactionData.push({ status: TransactionStatus.PREPARED, errorToken, + updatedToken, + errors, createdAt: new Date(), }) @@ -875,11 +840,12 @@ const receive = async function ( throw new AppError( Err.VALIDATION_ERROR, 'Ecash could not be redeemed.', - {caller: 'receive', message: errors?.length ? errors[0]?.message : undefined, errorToken} + {caller: 'receive', errors} ) } let receivedAmount = 0 + let addedProofsCount = 0 for (const entry of updatedToken.token) { // create ProofModel instances and store them into the proofsStore @@ -889,11 +855,21 @@ const receive = async function ( transactionId as number ) - receivedAmount += addedAmount + receivedAmount += addedAmount + addedProofsCount += addedProofs.length } - // const receivedAmount = CashuUtils.getTokenAmounts(updatedToken as Token).totalAmount - log.debug('[receive]', `Received amount: ${receivedAmount}`) + // temporary dirty fix of zero value tx until I figure out how it happens + const receivedAmountCheck = CashuUtils.getTokenAmounts(updatedToken as Token).totalAmount + + if (receivedAmount !== receivedAmountCheck) { + log.error('[receive]', `Received per proofStore: ${receivedAmount} Received check using tokenAmounts: ${receivedAmountCheck}`, updatedToken) + + if(receivedAmount === 0 && addedProofsCount > 0) { + receivedAmount = receivedAmountCheck + } + } + // temp fix end // Update tx amount if full amount was not received if (receivedAmount !== amountToReceive) { @@ -2179,7 +2155,7 @@ const _updateMintKeys = function (mintUrl: string, newKeys: MintKeys) { const _formatError = function (e: AppError) { return { name: e.name, - message: e.message.slice(0, 300), + message: e.message.slice(0, 800), params: e.params || {}, } as AppError } diff --git a/yarn.lock b/yarn.lock index 7ef8c864..462987c6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1611,12 +1611,12 @@ resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-0.2.0.tgz#a12cda60f3cf1ab5d7c77068c3711d2366649ed7" integrity sha512-6YBxJDAapHSdd3bLDv6x2wRPwq4QFMUaB3HvljNBUTThDd12eSm7/3F+2lnfzx2jvM+S6Nsy0jEt9QbPqSwqRw== -"@noble/curves@1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" - integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== +"@noble/curves@1.1.0", "@noble/curves@~1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d" + integrity sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA== dependencies: - "@noble/hashes" "1.3.2" + "@noble/hashes" "1.3.1" "@noble/curves@^1.0.0", "@noble/curves@~1.3.0": version "1.3.0" @@ -1625,24 +1625,12 @@ dependencies: "@noble/hashes" "1.3.3" -"@noble/curves@~1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d" - integrity sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA== - dependencies: - "@noble/hashes" "1.3.1" - -"@noble/hashes@1.3.1", "@noble/hashes@~1.3.1": +"@noble/hashes@1.3.1": version "1.3.1" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== -"@noble/hashes@1.3.2": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" - integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== - -"@noble/hashes@1.3.3", "@noble/hashes@~1.3.0", "@noble/hashes@~1.3.2": +"@noble/hashes@1.3.3", "@noble/hashes@~1.3.0", "@noble/hashes@~1.3.1", "@noble/hashes@~1.3.2": version "1.3.3" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699" integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA== @@ -6282,24 +6270,17 @@ normalize-path@^3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -nostr-tools@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-2.1.4.tgz#7df43a5279eb86ce0530420183620dc2618adaa7" - integrity sha512-MX4gU1gJYP2PKdlLwveHhZIJi/k0qp+ZKwGB7gMFkUodJs4V1OX18Cteb+vxFngtD0B5LlCzLn+ml6tzwlKXFA== +nostr-tools@1.17.0: + version "1.17.0" + resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-1.17.0.tgz#b6f62e32fedfd9e68ec0a7ce57f74c44fc768e8c" + integrity sha512-LZmR8GEWKZeElbFV5Xte75dOeE9EFUW/QLI1Ncn3JKn0kFddDKEfBbFN8Mu4TMs+L4HR/WTPha2l+PPuRnJcMw== dependencies: "@noble/ciphers" "0.2.0" - "@noble/curves" "1.2.0" + "@noble/curves" "1.1.0" "@noble/hashes" "1.3.1" "@scure/base" "1.1.1" "@scure/bip32" "1.3.1" "@scure/bip39" "1.2.1" - optionalDependencies: - nostr-wasm v0.1.0 - -nostr-wasm@v0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/nostr-wasm/-/nostr-wasm-0.1.0.tgz#17af486745feb2b7dd29503fdd81613a24058d94" - integrity sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA== npm-run-path@^4.0.1: version "4.0.1"