diff --git a/packages/wallet-service/src/api/txProposalCreate.ts b/packages/wallet-service/src/api/txProposalCreate.ts index b860c99f..36844db6 100644 --- a/packages/wallet-service/src/api/txProposalCreate.ts +++ b/packages/wallet-service/src/api/txProposalCreate.ts @@ -17,6 +17,7 @@ import { getWallet, getWalletAddresses, getWalletAddressDetail, + incrementAddressSeqnum, markUtxosWithProposalId, } from '@src/db'; import { @@ -137,6 +138,11 @@ export const create = middy(walletIdProxyHandler(async (walletId, event) => { await markUtxosWithProposalId(mysql, txProposalId, inputUtxos); } + if (tx.isNanoContract()) { + const nanoHeader = tx.getNanoHeaders()[0]; + await incrementAddressSeqnum(mysql, walletId, nanoHeader.address.base58); + } + await commitTransaction(mysql); } catch (e) { await rollbackTransaction(mysql); diff --git a/packages/wallet-service/src/db/index.ts b/packages/wallet-service/src/db/index.ts index d636d28f..97fb2815 100644 --- a/packages/wallet-service/src/db/index.ts +++ b/packages/wallet-service/src/db/index.ts @@ -448,6 +448,22 @@ export const getWalletAddressDetail = async (mysql: ServerlessMysql, walletId: s return null; }; +/** + * Increment the seqnum of an address. + * + * @param mysql - Database connection + * @param walletId - Wallet id + * @param address - Address to increment seqnum for + */ +export const incrementAddressSeqnum = async (mysql: ServerlessMysql, walletId: string, address: string): Promise => { + await mysql.query(` + UPDATE \`address\` + SET \`seqnum\` = \`seqnum\` + 1 + WHERE \`wallet_id\` = ? + AND \`address\` = ?`, + [walletId, address]); +}; + /** * Initialize a wallet's transaction history. * diff --git a/packages/wallet-service/tests/db.test.ts b/packages/wallet-service/tests/db.test.ts index 6192d3ce..1434795b 100644 --- a/packages/wallet-service/tests/db.test.ts +++ b/packages/wallet-service/tests/db.test.ts @@ -19,6 +19,7 @@ import { getUtxosLockedAtHeight, getWallet, getWalletAddressDetail, + incrementAddressSeqnum, getWalletAddresses, getWalletTokens, getWalletBalances, @@ -926,6 +927,33 @@ test('getWalletAddressDetail', async () => { expect(detailNull).toBeNull(); }); +test('incrementAddressSeqnum', async () => { + expect.hasAssertions(); + const walletId = 'walletId'; + + await addToAddressTable(mysql, [{ + address: ADDRESSES[0], + index: 0, + walletId, + transactions: 0, + seqnum: 5, + }]); + + // seqnum should start at 5 + const before = await getWalletAddressDetail(mysql, walletId, ADDRESSES[0]); + expect(before.seqnum).toBe(5); + + // increment and verify + await incrementAddressSeqnum(mysql, walletId, ADDRESSES[0]); + const after1 = await getWalletAddressDetail(mysql, walletId, ADDRESSES[0]); + expect(after1.seqnum).toBe(6); + + // increment again + await incrementAddressSeqnum(mysql, walletId, ADDRESSES[0]); + const after2 = await getWalletAddressDetail(mysql, walletId, ADDRESSES[0]); + expect(after2.seqnum).toBe(7); +}); + test('getWalletBalances', async () => { expect.hasAssertions(); const walletId = 'walletId'; diff --git a/packages/wallet-service/tests/txProposal.test.ts b/packages/wallet-service/tests/txProposal.test.ts index 0466d5d0..38578167 100644 --- a/packages/wallet-service/tests/txProposal.test.ts +++ b/packages/wallet-service/tests/txProposal.test.ts @@ -4,6 +4,7 @@ import { destroy as txProposalDestroy } from '@src/api/txProposalDestroy'; import { getTxProposal, getUtxos, + getWalletAddressDetail, updateTxProposal, updateVersionData, } from '@src/db'; @@ -2097,3 +2098,51 @@ test('markUtxosWithProposalId should handle empty utxos array', async () => { expect(txProposal).not.toBeNull(); expect(txProposal.status).toBe(TxProposalStatus.OPEN); }); + +test('POST /txproposals with nano contract tx should increment caller address seqnum', async () => { + expect.hasAssertions(); + + await addToWalletTable(mysql, [{ + id: 'my-wallet', + xpubkey: 'xpubkey', + authXpubkey: 'auth_xpubkey', + status: 'ready', + maxGap: 5, + createdAt: 10000, + readyAt: 10001, + }]); + await addToAddressTable(mysql, [{ + address: ADDRESSES[0], + index: 0, + walletId: 'my-wallet', + transactions: 0, + seqnum: 3, + }]); + + // Verify initial seqnum + const before = await getWalletAddressDetail(mysql, 'my-wallet', ADDRESSES[0]); + expect(before.seqnum).toBe(3); + + // Mock createTxFromHex to return a nano contract transaction + const spy = jest.spyOn(hathorLib.helpersUtils, 'createTxFromHex').mockReturnValue({ + inputs: [], + outputs: [], + isNanoContract: () => true, + getNanoHeaders: () => [{ + address: { base58: ADDRESSES[0] }, + }], + } as any); + + const event = makeGatewayEventWithAuthorizer('my-wallet', null, JSON.stringify({ txHex: 'mockedhex' })); + const result = await txProposalCreate(event, null, null) as APIGatewayProxyResult; + const returnBody = JSON.parse(result.body as string); + + expect(result.statusCode).toBe(201); + expect(returnBody.success).toBe(true); + + // Verify seqnum was incremented + const after = await getWalletAddressDetail(mysql, 'my-wallet', ADDRESSES[0]); + expect(after.seqnum).toBe(4); + + spy.mockRestore(); +});