diff --git a/src/models/Transaction.ts b/src/models/Transaction.ts index bf14d84b..625415b9 100644 --- a/src/models/Transaction.ts +++ b/src/models/Transaction.ts @@ -1,9 +1,13 @@ -import {Instance, SnapshotIn, SnapshotOut, flow, types} from 'mobx-state-tree' +import { Instance, SnapshotIn, SnapshotOut, types } from 'mobx-state-tree' import { log } from '../services/logService' import { MintUnit } from '../services/wallet/currency' import { Database } from '../services' - +/** + * Transaction model with MST actions for database updates. + * Note: Individual setter actions (setStatus, setFee, etc.) are deprecated. + * Use the generic update() action instead for all mutations. + */ export interface TransactionRecord { id: number type: TransactionType @@ -27,8 +31,8 @@ export interface TransactionRecord { noteToSelf?: string | null tags?: Array | null status: TransactionStatus - expiresAt?: Date | null - createdAt: Date + expiresAt?: Date | null + createdAt: Date } export type TransactionData = { @@ -49,7 +53,7 @@ export enum TransactionType { export enum TransactionStatus { DRAFT = 'DRAFT', PREPARED = 'PREPARED', - PREPARED_OFFLINE = 'PREPARED_OFFLINE', // offline receive, safer to have if sepearate from prepared + PREPARED_OFFLINE = 'PREPARED_OFFLINE', // offline receive, safer to have if separate from prepared PENDING = 'PENDING', REVERTED = 'REVERTED', RECOVERED = 'RECOVERED', @@ -60,7 +64,7 @@ export enum TransactionStatus { } export const TransactionModel = types - .model('Transaction', { + .model('Transaction', { id: types.identifierNumber, type: types.frozen(), amount: types.integer, @@ -86,123 +90,29 @@ export const TransactionModel = types expiresAt: types.maybe(types.maybeNull(types.Date)), createdAt: types.optional(types.Date, new Date()), }) - .views(self => ({ - })) + .views(self => ({})) .actions(self => ({ - setUnit(unit: MintUnit) { // migration - self.unit = unit - }, - setIsExpired() { - self.status = TransactionStatus.EXPIRED - }, - setStatus ( - status: TransactionStatus, - data: string, - ) { - // Update status and set related tx data in database - Database.updateStatus(self.id!, status, data) - // Update in the model - self.status = status - self.data = data - log.trace('[setStatus]', 'Transaction status and data updated', {id: self.id, status}) - }, - setBalanceAfter(balanceAfter: number) { - Database.updateBalanceAfter(self.id!, balanceAfter) - self.balanceAfter = balanceAfter - log.trace('[setBalanceAfter]', 'Transaction balanceAfter updated', {id: self.id, balanceAfter}) - }, - setFee(fee: number) { - Database.updateFee(self.id!, fee) - self.fee = fee - log.trace('[setFee]', 'Transaction fee updated', {id: self.id, fee}) - }, - setPaymentId(paymentId: string) { - Database.updatePaymentId(self.id!, paymentId) - self.paymentId = paymentId - log.trace('[setPaymentId]', 'Transaction paymentId updated', {id: self.id, paymentId}) - }, - setQuote(quote: string) { - Database.updateQuote(self.id!, quote) - self.quote = quote - log.trace('[setQuote]', 'Transaction quote updated', {id: self.id, quote}) - }, - setReceivedAmount(amount: number) { - Database.updateReceivedAmount(self.id!, amount) - self.amount = amount - log.trace('[setReceivedAmount]', 'Transaction amount updated', {id: self.id, amount}) - }, - setNote(note: string) { - Database.updateNote(self.id!, note) - self.noteToSelf = note - log.trace('[saveNote]', 'Transaction note updated', {id: self.id, note}) - }, - setZapRequest(zapRequest: string) { - Database.updateZapRequest(self.id!, zapRequest) - self.zapRequest = zapRequest - log.trace('[setZapRequest]', 'Transaction zapRequest updated', {id: self.id, zapRequest}) - }, - setPaymentRequest(paymentRequest: string) { - Database.updatePaymentRequest(self.id!, paymentRequest) - self.paymentRequest = paymentRequest - log.trace('[setZapRequest]', 'Transaction paymentRequest updated', {id: self.id, paymentRequest}) - }, - setExpiresAt(expiresAt: Date) { - Database.updateExpiresAt(self.id!, expiresAt) - self.expiresAt = expiresAt - log.trace('[setExpiresAt]', 'Transaction expiresAt updated', {id: self.id, expiresAt}) - }, - setSentFrom(sentFrom: string) { - Database.updateSentFrom(self.id!, sentFrom) - self.sentFrom = sentFrom - log.trace('[setSentFrom]', 'Transaction sentFrom updated', {id: self.id, sentFrom}) - }, - setSentTo(sentTo: string) { - Database.updateSentTo(self.id!, sentTo) - self.sentTo = sentTo - log.trace('[setSentTo]', 'Transaction sentTo updated', {id: self.id, sentTo}) - }, - setProfile(profile: string) { - Database.updateProfile(self.id!, profile) - self.profile = profile - log.trace('[setProfile]', 'Transaction profile updated', {id: self.id, profile}) - }, - setInputToken(inputToken: string) { - Database.updateInputToken(self.id!, inputToken) + pruneInputToken(inputToken: string) { self.inputToken = inputToken.slice(0, 40) - log.trace('[setInputToken]', 'Transaction inputToken updated', {id: self.id, inputToken}) + log.trace('[pruneInputToken]', 'Transaction inputToken pruned in store', { id: self.id }) }, - setOutputToken(outputToken: string) { - Database.updateOutputToken(self.id!, outputToken) + pruneOutputToken(outputToken: string) { self.outputToken = outputToken.slice(0, 40) - log.trace('[setOutputToken]', 'Transaction outputToken updated', {id: self.id}) + log.trace('[pruneOutputToken]', 'Transaction outputToken pruned in store', { id: self.id }) }, - setProof(proof: string) { - Database.updateProof(self.id!, proof) - self.proof = proof - log.trace('[setProof]', 'Transaction proof updated', {id: self.id, proof}) + update(fields: Partial) { + // Update multiple fields in database with a single query + Database.updateTransaction(self.id!, fields) + + // Update the model to keep store in sync + Object.keys(fields).forEach(key => { + ;(self as any)[key] = (fields as any)[key] + }) + + log.trace('[update]', 'Transaction updated', { id: self.id, fields: Object.keys(fields) }) }, - pruneInputToken(inputToken: string) { - self.inputToken = inputToken.slice(0, 40) - log.trace('[pruneInputToken]', 'Transaction inputToken pruned in store', {id: self.id}) - }, - pruneOutputToken(outputToken: string) { - self.outputToken = outputToken.slice(0, 40) - log.trace('[pruneOutputToken]', 'Transaction outputToken pruned in store', {id: self.id}) - }, - })) - + })) export interface Transaction extends Instance {} -/*export type Transaction = Partial> & { - amount: number - fee: number - unit: MintUnit - type: TransactionType - data: string - mint: string - status: TransactionStatus -}*/ -export interface TransactionSnapshotOut - extends SnapshotOut {} -export interface TransactionSnapshotIn - extends SnapshotIn {} \ No newline at end of file +export interface TransactionSnapshotOut extends SnapshotOut {} +export interface TransactionSnapshotIn extends SnapshotIn {} \ No newline at end of file diff --git a/src/screens/SendScreen.tsx b/src/screens/SendScreen.tsx index 1fdc00d0..984ff61d 100644 --- a/src/screens/SendScreen.tsx +++ b/src/screens/SendScreen.tsx @@ -460,12 +460,13 @@ export const SendScreen = observer(function SendScreen({ route }: Props) { if(paymentOption === SendOption.PAY_CASHU_PAYMENT_REQUEST) { if(decodedCashuPaymentRequest && decodedCashuPaymentRequest.id) { - // TODO make a single request - transaction.setPaymentId(decodedCashuPaymentRequest.id) - transaction.setPaymentRequest(encodedCashuPaymentRequest) - transaction.setProfile(JSON.stringify(contactToSendFrom)) - transaction.setSentTo(contactToSendTo.nip05 || contactToSendTo.name) // payee - transaction.setSentFrom(contactToSendFrom.nip05 || contactToSendFrom.name) // payer + transaction.update({ + paymentId: decodedCashuPaymentRequest.id, + paymentRequest: encodedCashuPaymentRequest, + profile: JSON.stringify(contactToSendFrom), + sentTo: contactToSendTo.nip05 || contactToSendTo.name, // payee + sentFrom: contactToSendFrom.nip05 || contactToSendFrom.name // payer + }) } } @@ -846,20 +847,18 @@ export const SendScreen = observer(function SendScreen({ route }: Props) { updated[2].sentToRelays = relaysToShareTo updated[2].sentEvent = sentEvent - transaction.setStatus( // status does not change, just add event and relay info to tx.data - TransactionStatus.PENDING, - JSON.stringify(updated) - ) + // status does not change, just add event and relay info to tx.data + transaction.update({ + status: TransactionStatus.PENDING, + data: JSON.stringify(updated) + }) } if(contactToSendTo) { - transaction.setProfile( - JSON.stringify(contactToSendTo) - ) - - transaction.setSentTo( - contactToSendTo.nip05handle ?? contactToSendTo.name! - ) + transaction.update({ + profile: JSON.stringify(contactToSendTo), + sentTo: contactToSendTo.nip05handle ?? contactToSendTo.name! + }) } } else { diff --git a/src/screens/TopupScreen.tsx b/src/screens/TopupScreen.tsx index 243f0e85..609945a3 100644 --- a/src/screens/TopupScreen.tsx +++ b/src/screens/TopupScreen.tsx @@ -544,10 +544,10 @@ export const TopupScreen = observer(function TopupScreen({ route }: Props) { updated[1].sentToRelays = relaysToShareTo // status does not change, just add event and relay info to tx.data - transaction.setStatus( - TransactionStatus.PENDING, - JSON.stringify(updated), - ) + transaction.update({ + status: TransactionStatus.PENDING, + data: JSON.stringify(updated), + }) } } else { setInfo(translate('topup.relayMissingSentEvent')) @@ -600,10 +600,10 @@ export const TopupScreen = observer(function TopupScreen({ route }: Props) { error: result, }) - transaction.setStatus( - TransactionStatus.ERROR, - JSON.stringify(updated), - ) + transaction.update({ + status: TransactionStatus.ERROR, + data: JSON.stringify(updated), + }) setResultModalInfo({ status: TransactionStatus.ERROR, diff --git a/src/screens/TranDetailScreen.tsx b/src/screens/TranDetailScreen.tsx index cc6932df..6106f649 100644 --- a/src/screens/TranDetailScreen.tsx +++ b/src/screens/TranDetailScreen.tsx @@ -135,7 +135,7 @@ export const TranDetailScreen = observer(function TranDetailScreen({ route }: Pr const onNoteSave = async function () { try { - transaction!.setNote(note) + transaction.update({noteToSelf: note}) setIsNoteEditing(false) } catch (e: any) { handleError(e) @@ -1658,7 +1658,7 @@ const TransferInfoBlock = function (props: { mint?: Mint colorScheme: 'dark' | 'light' }) { - const {transaction, mint} = props + const {transaction, mint, isDataParsable} = props const {proofsStore, transactionsStore} = useStores() const navigation = useNavigation() @@ -1701,6 +1701,7 @@ const TransferInfoBlock = function (props: { log.trace('[onRevertPreparedTransfer]', {tId: transaction.id}) const pendingProofs = proofsStore.getByTransactionId(transaction.id!, true) // PREPARED should always pending + const transactionData = isDataParsable ? JSON.parse(transaction.data) : [] if(pendingProofs.length > 0) { // remove it from pending proofs in the wallet @@ -1711,17 +1712,16 @@ const TransferInfoBlock = function (props: { const message = 'Ecash has been returned to spendable balance.' - const transactionDataUpdate = { + transactionData.push({ status: TransactionStatus.REVERTED, message, createdAt: new Date(), - } + }) - await transactionsStore.updateStatuses( - [transaction.id!], - TransactionStatus.REVERTED, - JSON.stringify(transactionDataUpdate), - ) + transaction.update({ + status: TransactionStatus.REVERTED, + data: JSON.stringify(transactionData) + }) setResultModalInfo({status: TransactionStatus.REVERTED, message}) toggleResultModal() diff --git a/src/screens/TransferScreen.tsx b/src/screens/TransferScreen.tsx index 4e50b9e7..eae4e9de 100644 --- a/src/screens/TransferScreen.tsx +++ b/src/screens/TransferScreen.tsx @@ -383,10 +383,14 @@ useEffect(() => { const profile = contactsStore.findByLud16(lnurlPayParams.address) if (profile) { - transaction.setSentTo(profile.nip05 || profile.name) - transaction.setProfile(JSON.stringify(profile)) + transaction.update({ + sentTo: profile.nip05 || profile.name, + profile: JSON.stringify(profile) + }) } else { - transaction.setSentTo(lnurlPayParams.address) + transaction.update({ + sentTo: lnurlPayParams.address + }) } } diff --git a/src/services/sqlite.ts b/src/services/sqlite.ts index e1938df1..d10a4480 100644 --- a/src/services/sqlite.ts +++ b/src/services/sqlite.ts @@ -254,6 +254,48 @@ const getDatabaseVersion = function (db: QuickSQLiteConnection): {version: numbe * Transactions */ + +const updateTransaction = function (id: number, fields: {[key:string]: any}): TransactionRecord { + + const allowedColumns = ['amount','fee','unit','data','sentFrom','sentTo','profile','memo','paymentId','quote','paymentRequest','zapRequest','inputToken','outputToken','proof','balanceAfter','noteToSelf','tags','status','expiresAt']; + + try { + // Filter keys against allowed columns to prevent SQL injection + const validKeys = Object.keys(fields).filter(key => allowedColumns.includes(key)); + + if (validKeys.length === 0) { + // No valid keys to update, return existing transaction + return getTransactionById(id); + } + + // Build SET clauses and parameters + const setClauses = validKeys.map(key => `${key} = ?`); + const params = validKeys.map(key => fields[key]); + params.push(id); // Add id at the end for WHERE clause + + const query = ` + UPDATE transactions + SET ${setClauses.join(', ')} + WHERE id = ? + `; + + const db = getInstance(); + db.execute(query, params); + + log.debug('[updateTransaction]', `Transaction updated in the database`, {id, fields: validKeys}); + + const updatedTx = getTransactionById(id); + return updatedTx as TransactionRecord; + } catch (e: any) { + throw new AppError( + Err.DATABASE_ERROR, + 'Could not update transaction in database', + e.message, + ); + } +} + + const getTransactions = function (limit: number, offset: number, onlyPending: boolean = false) { let query: string = '' try { @@ -545,35 +587,6 @@ const addTransactionAsync = async function (tx: Transaction): Promise 0) { - transaction.setFee(swapFeePaid) - } + + transaction.update({ + status: TransactionStatus.COMPLETED, + data: JSON.stringify(transactionData), + outputToken, + balanceAfter, + ...(receivedAmount !== amountToReceive && {receivedAmount}), + ...(swapFeePaid > 0 && {fee: swapFeePaid}) + }) return { taskFunction: RECEIVE_TASK, @@ -150,10 +143,10 @@ export const receiveTask = async function ( errorToken: e.params?.errorToken || undefined }) - transaction.setStatus( - TransactionStatus.ERROR, - JSON.stringify(transactionData), - ) + transaction.update({ + status: TransactionStatus.ERROR, + data: JSON.stringify(transactionData) + }) } log.error(e.name, e.message) @@ -210,21 +203,27 @@ export const receiveOfflinePrepareTask = async function ( } transaction = await transactionsStore.addTransaction(newTransaction) - transaction.setInputToken(encodedToken) + transaction.update({inputToken: encodedToken}) // Handle blocked mint const isBlocked = mintsStore.isBlocked(mintToReceive) if (isBlocked) { - const blockedTransaction = transaction.setStatus( - TransactionStatus.BLOCKED, - JSON.stringify(transactionData), - ) + transactionData.push({ + status: TransactionStatus.BLOCKED, + mintToReceive, + createdAt: new Date() + }) + + transaction.update({ + status: TransactionStatus.BLOCKED, + data: JSON.stringify(transactionData), + }) return { taskFunction: RECEIVE_OFFLINE_PREPARE_TASK, mintUrl: mintToReceive, - transaction: blockedTransaction, + transaction, message: `The mint ${mintToReceive} is blocked. You can unblock it in Settings.`, } as unknown as TransactionTaskResult } @@ -235,10 +234,10 @@ export const receiveOfflinePrepareTask = async function ( createdAt: new Date(), }) - transaction.setStatus( - TransactionStatus.PREPARED_OFFLINE, - JSON.stringify(transactionData), - ) + transaction.update( { + status: TransactionStatus.PREPARED_OFFLINE, + data: JSON.stringify(transactionData), + }) return { taskFunction: RECEIVE_OFFLINE_PREPARE_TASK, @@ -254,10 +253,10 @@ export const receiveOfflinePrepareTask = async function ( error: WalletUtils.formatError(e), }) - transaction.setStatus( - TransactionStatus.ERROR, - JSON.stringify(transactionData), - ) + transaction.update({ + status: TransactionStatus.ERROR, + data: JSON.stringify(transactionData), + }) } log.error(e.name, e.message) @@ -305,10 +304,16 @@ export const receiveOfflineCompleteTask = async function ( const isBlocked = mintsStore.isBlocked(mintToReceive) if (isBlocked) { - transaction.setStatus( - TransactionStatus.BLOCKED, - JSON.stringify(transactionData), - ) + transactionData.push({ + status: TransactionStatus.BLOCKED, + mintToReceive, + createdAt: new Date() + }) + + transaction.update({ + status: TransactionStatus.BLOCKED, + data: JSON.stringify(transactionData), + }) return { taskFunction: RECEIVE_OFFLINE_COMPLETE_TASK, @@ -329,11 +334,6 @@ export const receiveOfflineCompleteTask = async function ( transaction.id ) - // Update tx amount if full amount was not received - if (receivedAmount !== transaction.amount) { - transaction.setReceivedAmount(receivedAmount) - } - // Finally, update completed transaction transactionData.push({ status: TransactionStatus.COMPLETED, @@ -343,19 +343,16 @@ export const receiveOfflineCompleteTask = async function ( createdAt: new Date(), }) - transaction.setStatus( - TransactionStatus.COMPLETED, - JSON.stringify(transactionData), - ) - - transaction.setOutputToken(outputToken) - const balanceAfter = proofsStore.getUnitBalance(unit)?.unitBalance! - transaction.setBalanceAfter(balanceAfter) - - if(swapFeePaid > 0) { - transaction.setFee(swapFeePaid) - } + + transaction.update({ + status: TransactionStatus.COMPLETED, + data: JSON.stringify(transactionData), + outputToken, + balanceAfter, + ...(receivedAmount !== transaction.amount && {receivedAmount}), + ...(swapFeePaid > 0 && {fee: swapFeePaid}) + }) return { taskFunction: RECEIVE_OFFLINE_COMPLETE_TASK, @@ -375,10 +372,10 @@ export const receiveOfflineCompleteTask = async function ( error: WalletUtils.formatError(e), }) - transaction.setStatus( - TransactionStatus.ERROR, - JSON.stringify(transactionData), - ) + transaction.update({ + status: TransactionStatus.ERROR, + data: JSON.stringify(transactionData) + }) } return { @@ -450,10 +447,10 @@ export const receiveByCashuPaymentRequestTask = async function ( message: 'Mint is blocked in your Settings, ecash has not been received.', }) - transaction.setStatus( - TransactionStatus.BLOCKED, - JSON.stringify(transactionData), - ) + transaction.update({ + status: TransactionStatus.BLOCKED, + data: JSON.stringify(transactionData), + }) return { taskFunction: RECEIVE_BY_CASHU_PAYMENT_REQUEST_TASK, @@ -476,11 +473,6 @@ export const receiveByCashuPaymentRequestTask = async function ( ) - // Update tx amount if full amount was not received - if (receivedAmount !== amountToReceive) { - transaction.setReceivedAmount(receivedAmount) - } - // Finally, update completed transaction transactionData.push({ status: TransactionStatus.COMPLETED, @@ -490,19 +482,16 @@ export const receiveByCashuPaymentRequestTask = async function ( createdAt: new Date(), }) - transaction.setStatus( - TransactionStatus.COMPLETED, - JSON.stringify(transactionData), - ) - - transaction.setOutputToken(outputToken) - const balanceAfter = proofsStore.getUnitBalance(unit)?.unitBalance! - transaction.setBalanceAfter(balanceAfter) - - if(swapFeePaid > 0) { - transaction.setFee(swapFeePaid) - } + + transaction.update({ + status: TransactionStatus.COMPLETED, + data: JSON.stringify(transactionData), + outputToken, + balanceAfter, + ...(receivedAmount !== amountToReceive && {receivedAmount}), + ...(swapFeePaid > 0 && {fee: swapFeePaid}) + }) return { taskFunction: RECEIVE_BY_CASHU_PAYMENT_REQUEST_TASK, @@ -522,10 +511,10 @@ export const receiveByCashuPaymentRequestTask = async function ( errorToken: e.params?.errorToken || undefined }) - transaction.setStatus( - TransactionStatus.ERROR, - JSON.stringify(transactionData), - ) + transaction.update({ + status: TransactionStatus.ERROR, + data: JSON.stringify(transactionData) + }) } log.error(e.name, e.message) @@ -623,4 +612,4 @@ export const receiveSync = async function ( throw new AppError(Err.WALLET_ERROR, e.message, e.stack.slice(0, 200)) } } -} \ No newline at end of file +} diff --git a/src/services/wallet/revertTask.ts b/src/services/wallet/revertTask.ts index af4b61ba..2d5bdd31 100644 --- a/src/services/wallet/revertTask.ts +++ b/src/services/wallet/revertTask.ts @@ -87,18 +87,15 @@ try { createdAt: new Date(), }) - transaction.setStatus( - TransactionStatus.REVERTED, - JSON.stringify(transactionData), - ) - - transaction.setOutputToken(outputToken) const balanceAfter = proofsStore.getUnitBalance(unit)?.unitBalance! - transaction.setBalanceAfter(balanceAfter) - - if(mintFeePaid > 0) { - transaction.setFee(mintFeePaid) - } + + transaction.update({ + status: TransactionStatus.REVERTED, + data: JSON.stringify(transactionData), + outputToken, + balanceAfter, + ...(mintFeePaid > 0 && {fee: mintFeePaid}) + }) return { taskFunction: REVERT_TASK, @@ -116,10 +113,10 @@ try { errorToken: e.params?.errorToken || undefined }) - transaction.setStatus( - TransactionStatus.ERROR, - JSON.stringify(transactionData), - ) + transaction.update({ + status: TransactionStatus.ERROR, + data: JSON.stringify(transactionData) + }) } log.error(e.name, e.message) @@ -132,4 +129,4 @@ try { error: WalletUtils.formatError(e), } as TransactionTaskResult } -} \ No newline at end of file +} diff --git a/src/services/wallet/sendTask.ts b/src/services/wallet/sendTask.ts index 3688c685..5ccf042b 100644 --- a/src/services/wallet/sendTask.ts +++ b/src/services/wallet/sendTask.ts @@ -106,11 +106,6 @@ export const sendTask = async function ( createdAt: new Date(), }) - transaction.setStatus( - TransactionStatus.PREPARED, - JSON.stringify(transactionData), - ) - // Create sendable encoded token if (!memo || memo === '') { memo = 'Sent from Minibits' @@ -123,24 +118,25 @@ export const sendTask = async function ( memo, }) - transaction.setOutputToken(outputToken) + transaction.update({ + status: TransactionStatus.PREPARED, + data: JSON.stringify(transactionData), + outputToken + }) transactionData.push({ status: TransactionStatus.PENDING, createdAt: new Date(), }) - transaction.setStatus( - TransactionStatus.PENDING, - JSON.stringify(transactionData), - ) - const balanceAfter = proofsStore.getUnitBalance(unit)?.unitBalance! - transaction.setBalanceAfter(balanceAfter) - if(swapFeePaid > 0) { - transaction.setFee(swapFeePaid) - } + transaction.update({ + status: TransactionStatus.PENDING, + data: JSON.stringify(transactionData), + balanceAfter, + ...(swapFeePaid > 0 && {fee: swapFeePaid}) + }) log.trace('[send] totalBalance after', balanceAfter) @@ -204,10 +200,10 @@ export const sendTask = async function ( createdAt: new Date() }) - transaction.setStatus( - TransactionStatus.ERROR, - JSON.stringify(transactionData), - ) + transaction.update({ + status: TransactionStatus.ERROR, + data: JSON.stringify(transactionData) + }) } return { @@ -588,4 +584,4 @@ const getInactiveKeysetIds = function(mint: Mint) { } return [] -} \ No newline at end of file +} diff --git a/src/services/wallet/topupTask.ts b/src/services/wallet/topupTask.ts index aa9477ca..755cb751 100644 --- a/src/services/wallet/topupTask.ts +++ b/src/services/wallet/topupTask.ts @@ -101,15 +101,18 @@ export const topupTask = async function ( // contactToSendTo is to whom to send the request const contactTo = isStateTreeNode(contactToSendTo) ? getSnapshot(contactToSendTo) : contactToSendTo - // TODO make single insert - transaction.setQuote(mintQuote) - transaction.setPaymentId(paymentHash) - transaction.setPaymentRequest(encodedInvoice) - transaction.setExpiresAt(addSeconds(new Date(timestamp * 1000), expiry)) - - // SATS flow in reverse compared with payment request - transaction.setSentFrom(contactTo.nip05 || contactTo.name) - transaction.setSentTo(walletProfileStore.nip05) + // Bulk update quote, paymentId, paymentRequest, expiresAt, sentFrom, sentTo + const expiresAtDate = addSeconds(new Date(timestamp * 1000), expiry) + const sentFromValue = contactTo?.nip05 || contactTo?.name || '' + const sentToValue = walletProfileStore.nip05 || '' + transaction.update({ + quote: mintQuote, + paymentId: paymentHash, + paymentRequest: encodedInvoice, + expiresAt: expiresAtDate, + sentFrom: sentFromValue, + sentTo: sentToValue + }) log.trace('[topupTask] invoice', {amount, paymentHash, expiry, timestamp}) @@ -118,10 +121,11 @@ export const topupTask = async function ( createdAt: new Date() }) - transaction.setStatus( - TransactionStatus.PENDING, - JSON.stringify(transactionData), - ) + // Update status and data in one call + transaction.update({ + status: TransactionStatus.PENDING, + data: JSON.stringify(transactionData) + }) if(!nwcEvent) { const wsMint = new CashuMint(mintUrl) @@ -177,10 +181,11 @@ export const topupTask = async function ( createdAt: new Date() }) - transaction.setStatus( - TransactionStatus.ERROR, - JSON.stringify(transactionData), - ) + // Update status and data on error + transaction.update({ + status: TransactionStatus.ERROR, + data: JSON.stringify(transactionData) + }) } log.error(e.name, e.message) diff --git a/src/services/wallet/transferTask.ts b/src/services/wallet/transferTask.ts index fd2a9764..4bedc9a4 100644 --- a/src/services/wallet/transferTask.ts +++ b/src/services/wallet/transferTask.ts @@ -85,8 +85,8 @@ export const transferTask = async function ( const transactionId = transaction.id const paymentHash = LightningUtils.getInvoiceData(LightningUtils.decodeInvoice(encodedInvoice)).payment_hash - transaction.setPaymentId(paymentHash) - transaction.setQuote(meltQuote.quote) + // Replace individual setters with a single update + transaction.update({ paymentId: paymentHash, quote: meltQuote.quote }) if (amountToTransfer + meltQuote.fee_reserve > mintBalanceToTransferFrom.balances[unit]!) { throw new AppError( @@ -162,7 +162,7 @@ export const transferTask = async function ( unit, }) - // Update transaction status + // Update transaction status and inputToken in one call transactionData.push({ status: TransactionStatus.PREPARED, proofsToMeltFromAmount, @@ -171,18 +171,17 @@ export const transferTask = async function ( createdAt: new Date(), }) - transaction.setStatus( - TransactionStatus.PREPARED, - JSON.stringify(transactionData), - ) - const inputToken = getEncodedToken({ mint: mintUrl, proofs: proofsToMeltFrom, unit }) - transaction.setInputToken(inputToken) + transaction.update({ + status: TransactionStatus.PREPARED, + data: JSON.stringify(transactionData), + inputToken, + }) try { meltResponse = await walletStore.payLightningMelt( @@ -217,16 +216,18 @@ export const transferTask = async function ( // Spend pending proofs that were used to settle the lightning invoice proofsStore.removeProofs(proofsToMeltFrom as Proof[], true, false) - // Save preimage asap - if(meltResponse.quote.payment_preimage) { - transaction.setProof(meltResponse.quote.payment_preimage) - } - + // compute fees and change let totalFeePaid = proofsToMeltFromAmount - amountToTransfer let lightningFeePaid = totalFeePaid - meltFeeReserve let meltFeePaid = meltFeeReserve let returnedAmount = CashuUtils.getProofsAmount(meltResponse.change) + let outputToken: string | undefined + + if(meltResponse.quote.payment_preimage) { + // include proof in update payload + } + if(meltResponse.change.length > 0) { WalletUtils.addCashuProofs( @@ -239,40 +240,43 @@ export const transferTask = async function ( } ) - const outputToken = getEncodedToken({ + outputToken = getEncodedToken({ mint: mintUrl, proofs: meltResponse.change, unit, }) - transaction.setOutputToken(outputToken) - totalFeePaid = totalFeePaid - returnedAmount lightningFeePaid = totalFeePaid - meltFeeReserve } - // Save final fee in db - if(totalFeePaid !== transaction.fee) { - transaction.setFee(totalFeePaid) - } + const balanceAfter = proofsStore.getUnitBalance(unit)?.unitBalance! - // Update transaction status - transactionData.push({ + // build consolidated update payload + const completedDataItem: TransactionData = { status: TransactionStatus.COMPLETED, lightningFeePaid, meltFeePaid, returnedAmount, preimage: meltResponse.quote.payment_preimage, createdAt: new Date(), - }) - - transaction.setStatus( - TransactionStatus.COMPLETED, - JSON.stringify(transactionData), - ) + } + transactionData.push(completedDataItem) - const balanceAfter = proofsStore.getUnitBalance(unit)?.unitBalance! - transaction.setBalanceAfter(balanceAfter) + const updatePayload: any = { + status: TransactionStatus.COMPLETED, + data: JSON.stringify(transactionData), + fee: totalFeePaid, + balanceAfter, + } + if (outputToken) { + updatePayload.outputToken = outputToken + } + if (meltResponse.quote.payment_preimage) { + updatePayload.proof = meltResponse.quote.payment_preimage + } + + transaction.update(updatePayload) return { taskFunction: TRANSFER_TASK, @@ -405,10 +409,10 @@ export const transferTask = async function ( createdAt: new Date() }) - transaction.setStatus( - TransactionStatus.ERROR, - JSON.stringify(transactionData), - ) + transaction.update({ + status: TransactionStatus.ERROR, + data: JSON.stringify(transactionData), + }) } return taskResult diff --git a/src/services/walletService.ts b/src/services/walletService.ts index 04fcbe60..f2af181c 100644 --- a/src/services/walletService.ts +++ b/src/services/walletService.ts @@ -1172,6 +1172,7 @@ const handleInFlightByMintTask = async function (mint: Mint): Promise 0) { - transaction.setFee(swapFeePaid) + transaction.update({fee: swapFeePaid}) } break @@ -1310,24 +1310,23 @@ const handleInFlightByMintTask = async function (mint: Mint): Promise 0) { - transaction.setFee(swapFeePaid) + transaction.update({fee: swapFeePaid}) } break @@ -1370,20 +1369,21 @@ const handleInFlightByMintTask = async function (mint: Mint): Promise