From 4edac363591dd28472d2963434dddff84f7ef5fd Mon Sep 17 00:00:00 2001 From: Gabriel Levcovitz Date: Mon, 18 Nov 2024 12:56:41 -0300 Subject: [PATCH 1/3] ci: add support for node v22 (#507) --- .github/workflows/apidocs-upload.yml | 2 +- .github/workflows/apidocs.yml | 2 +- .github/workflows/integration-test.yml | 13 ++++++----- .github/workflows/lint.yml | 2 +- .github/workflows/main.yml | 3 ++- .nvmrc | 2 +- Dockerfile | 2 +- __tests__/healthcheck.test.js | 32 +++++++++++++------------- 8 files changed, 30 insertions(+), 28 deletions(-) diff --git a/.github/workflows/apidocs-upload.yml b/.github/workflows/apidocs-upload.yml index 385a1b43..7c7f61cc 100644 --- a/.github/workflows/apidocs-upload.yml +++ b/.github/workflows/apidocs-upload.yml @@ -62,7 +62,7 @@ jobs: strategy: matrix: - node-version: [20.x] + node-version: [22.x] steps: # https://github.com/actions/checkout/releases/tag/v4.0.0 diff --git a/.github/workflows/apidocs.yml b/.github/workflows/apidocs.yml index 29a14c2c..bb277213 100644 --- a/.github/workflows/apidocs.yml +++ b/.github/workflows/apidocs.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: - node-version: [20.x] + node-version: [22.x] steps: # https://github.com/actions/checkout/releases/tag/v4.0.0 diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 5eccbd72..91db0e8b 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -24,7 +24,7 @@ jobs: strategy: matrix: - node-version: [20.x] + node-version: [20.x, 22.x] steps: - name: Checkout @@ -44,6 +44,7 @@ jobs: run: npm run test_integration - name: Upload coverage + if: ${{ matrix.node-version == '22.x' }} # https://github.com/codecov/codecov-action/releases/tag/v4.5.0 uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 with: @@ -56,7 +57,7 @@ jobs: # https://github.com/actions/upload-artifact/releases/tag/v4.3.6 uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a with: - name: test-transacion-logs + name: test-transaction-logs-${{ matrix.node-version }} path: tmp - name: Collect docker logs on failure @@ -64,16 +65,16 @@ jobs: # https://github.com/jwalton/gh-docker-logs/releases/tag/v1.0.0 uses: jwalton/gh-docker-logs@2a058a3b4c97aa3524391cb33bb5e917913ed676 with: - dest: './docker-logs' + dest: ./docker-logs-${{ matrix.node-version }} - name: Tar logs if: failure() - run: tar cvzf ./docker-logs.tgz ./docker-logs + run: tar cvzf ./docker-logs-${{ matrix.node-version }}.tgz ./docker-logs-${{ matrix.node-version }} - name: Upload logs to GitHub if: failure() # https://github.com/actions/upload-artifact/releases/tag/v4.3.6 uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a with: - name: logs.tgz - path: ./docker-logs.tgz + name: logs-${{ matrix.node-version }}.tgz + path: ./docker-logs-${{ matrix.node-version }}.tgz diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2be69c1a..8582c178 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,7 +19,7 @@ jobs: timeout-minutes: 10 strategy: matrix: - node-version: [20.x] + node-version: [20.x, 22.x] steps: # https://github.com/actions/checkout/releases/tag/v4.1.7 - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c6a92ca2..27d5cdaa 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,7 +18,7 @@ jobs: timeout-minutes: 40 # default is 360 strategy: matrix: - node-version: [20.x] + node-version: [20.x, 22.x] steps: - name: Checkout # https://github.com/actions/checkout/releases/tag/v4.1.7 @@ -40,6 +40,7 @@ jobs: run: npm run test - name: Upload coverage + if: ${{ matrix.node-version == '22.x' }} # https://github.com/codecov/codecov-action/releases/tag/v4.5.0 uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 with: diff --git a/.nvmrc b/.nvmrc index 9a2a0e21..53d1c14d 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v20 +v22 diff --git a/Dockerfile b/Dockerfile index cd84b335..3f2695f6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -ARG IMG=node:20.11-alpine3.19 +ARG IMG=node:22.11-alpine3.19 FROM $IMG as builder diff --git a/__tests__/healthcheck.test.js b/__tests__/healthcheck.test.js index da5da946..64dc482e 100644 --- a/__tests__/healthcheck.test.js +++ b/__tests__/healthcheck.test.js @@ -29,8 +29,8 @@ describe('healthcheck api', () => { describe('/health', () => { it('should return 400 when the x-wallet-id is invalid', async () => { const response = await TestUtils.request - .query({ wallet_ids: 'invalid' }) - .get('/health'); + .get('/health') + .query({ wallet_ids: 'invalid' }); expect(response.status).toBe(400); expect(response.body).toStrictEqual({ @@ -51,12 +51,12 @@ describe('healthcheck api', () => { it('should return 200 when all components are healthy', async () => { const response = await TestUtils.request + .get('/health') .query({ include_tx_mining: true, include_fullnode: true, wallet_ids: `${walletId},${anotherWalletId}` - }) - .get('/health'); + }); expect(response.status).toBe(200); expect(response.body).toStrictEqual({ @@ -121,8 +121,8 @@ describe('healthcheck api', () => { wallet.state = HathorWallet.SYNCING; const response = await TestUtils.request - .query({ include_tx_mining: true, include_fullnode: true, wallet_ids: walletId }) - .get('/health'); + .get('/health') + .query({ include_tx_mining: true, include_fullnode: true, wallet_ids: walletId }); expect(response.status).toBe(503); expect(response.body).toStrictEqual({ @@ -179,8 +179,8 @@ describe('healthcheck api', () => { TestUtils.httpMock.onGet('http://fakehost:8083/v1a/health').reply(200, { status: 'fail' }); const response = await TestUtils.request - .query({ include_tx_mining: true, include_fullnode: true, wallet_ids: walletId }) - .get('/health'); + .get('/health') + .query({ include_tx_mining: true, include_fullnode: true, wallet_ids: walletId }); expect(response.status).toBe(503); expect(response.body).toStrictEqual({ @@ -229,8 +229,8 @@ describe('healthcheck api', () => { TestUtils.httpMock.onGet('http://fakehost:8083/v1a/health').reply(503, { status: 'fail' }); const response = await TestUtils.request - .query({ include_tx_mining: true, include_fullnode: true, wallet_ids: walletId }) - .get('/health'); + .get('/health') + .query({ include_tx_mining: true, include_fullnode: true, wallet_ids: walletId }); expect(response.status).toBe(503); expect(response.body).toStrictEqual({ @@ -282,8 +282,8 @@ describe('healthcheck api', () => { ); const response = await TestUtils.request - .query({ include_tx_mining: true, include_fullnode: true, wallet_ids: walletId }) - .get('/health'); + .get('/health') + .query({ include_tx_mining: true, include_fullnode: true, wallet_ids: walletId }); expect(response.status).toBe(503); expect(response.body).toStrictEqual({ @@ -330,8 +330,8 @@ describe('healthcheck api', () => { it('should not include the fullnode when the parameter is missing', async () => { const response = await TestUtils.request - .query({ include_tx_mining: true, wallet_ids: walletId }) - .get('/health'); + .get('/health') + .query({ include_tx_mining: true, wallet_ids: walletId }); expect(response.status).toBe(200); expect(response.body).toStrictEqual({ @@ -367,8 +367,8 @@ describe('healthcheck api', () => { it('should not include the tx mining service when the parameter is missing', async () => { const response = await TestUtils.request - .query({ include_fullnode: true, wallet_ids: walletId }) - .get('/health'); + .get('/health') + .query({ include_fullnode: true, wallet_ids: walletId }); expect(response.status).toBe(200); expect(response.body).toStrictEqual({ From d7eec4400599ff6d484db4a67fca3202ea50b2d1 Mon Sep 17 00:00:00 2001 From: Gabriel Levcovitz Date: Tue, 17 Dec 2024 12:16:50 -0300 Subject: [PATCH 2/3] feat: implement support for bigint output values (#509) --- .eslintrc | 1 + .github/workflows/integration-test.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/main.yml | 2 +- __tests__/__fixtures__/http-fixtures.js | 84 +++++++++++++++++- .../atomic-swap/get-my-signatures.test.js | 4 +- .../atomic-swap/tx-proposal-create.test.js | 6 +- __tests__/decode.test.js | 18 ++-- __tests__/integration/atomic-swap.test.js | 12 +-- __tests__/integration/create-nft.test.js | 16 ++-- __tests__/integration/create-token.test.js | 16 ++-- __tests__/integration/melt-tokens.test.js | 4 +- __tests__/integration/mint-tokens.test.js | 4 +- __tests__/integration/multisig.test.js | 2 +- __tests__/push-tx-hex.test.js | 4 +- __tests__/schemas.test.js | 16 ++++ __tests__/tx-proposal/build-tx.test.js | 36 ++++---- __tests__/tx.helper.test.js | 38 ++++++++ package-lock.json | 43 ++++++---- package.json | 7 +- src/app.js | 15 +++- .../atomic-swap/tx-proposal.controller.js | 2 +- src/controllers/wallet/wallet.controller.js | 3 +- src/helpers/tx.helper.js | 17 ++-- src/routes/wallet/p2sh/tx-proposal.routes.js | 20 +++-- src/routes/wallet/wallet.routes.js | 45 +++++----- src/schemas.js | 86 ++++++++++++------- 27 files changed, 346 insertions(+), 159 deletions(-) create mode 100644 __tests__/schemas.test.js create mode 100644 __tests__/tx.helper.test.js diff --git a/.eslintrc b/.eslintrc index e19ab04e..547109b9 100644 --- a/.eslintrc +++ b/.eslintrc @@ -56,6 +56,7 @@ "**/*.js" ], "env": { + "es2020": true, "jest": true } } diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 91db0e8b..bde09011 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -24,7 +24,7 @@ jobs: strategy: matrix: - node-version: [20.x, 22.x] + node-version: [22.x] steps: - name: Checkout diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 8582c178..d6f2e8b7 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,7 +19,7 @@ jobs: timeout-minutes: 10 strategy: matrix: - node-version: [20.x, 22.x] + node-version: [22.x] steps: # https://github.com/actions/checkout/releases/tag/v4.1.7 - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 27d5cdaa..02b0e554 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,7 +18,7 @@ jobs: timeout-minutes: 40 # default is 360 strategy: matrix: - node-version: [20.x, 22.x] + node-version: [22.x] steps: - name: Checkout # https://github.com/actions/checkout/releases/tag/v4.1.7 diff --git a/__tests__/__fixtures__/http-fixtures.js b/__tests__/__fixtures__/http-fixtures.js index 0acc1197..92b50f57 100644 --- a/__tests__/__fixtures__/http-fixtures.js +++ b/__tests__/__fixtures__/http-fixtures.js @@ -824,6 +824,7 @@ export default { tokens: ['04'] }, ], + has_more: false, }, '/v1a/push_tx': { success: true, @@ -1018,9 +1019,64 @@ export default { '/transaction': { success: true, tx: { - hash: '00000008707722cde59ac9e7f4d44efbd3a5bd5f244223816ee676d328943b1b' + hash: '00000008707722cde59ac9e7f4d44efbd3a5bd5f244223816ee676d328943b1b', + version: 4, + nc_blueprint_id: '3cb032600bdf7db784800e4ea911b10676fa2f67591f82bb62628c234e771595', + nonce: '0', + timestamp: 1572636346, + weight: 1, + signal_bits: 0, + parents: ['1234', '5678'], + inputs: [], + outputs: [ + { + value: 6400, + token_data: 0, + script: 'dqkUsmE9zshkgB58piBD7ETUV0e/NgmIrA==', + decoded: { + type: 'P2PKH', + address: 'WewDeXWyvHP7jJTs7tjLoQfoB72LLxJQqN', + timelock: null, + value: 6400, + }, + token: '00', + spent_by: null, + selected_as_input: false, + }, + { + value: 6400, + token_data: 0, + script: 'qRTqJUJmzEmBNvhkmDuZ4JxcMh5/ioc=', + decoded: { + type: 'MultiSig', + address: 'wgyUgNjqZ18uYr4YfE2ALW6tP5hd8MumH5', + timelock: null, + value: 6400, + }, + token: '00', + spent_by: null, + selected_as_input: false, + }, + ], + tokens: [], + raw: '', }, + spent_outputs: {}, meta: { + hash: '5c02adea056d7b43e83171a0e2d226d564c791d583b32e9a404ef53a2e1b363a', + spent_outputs: [], + received_by: [], + children: [], + conflict_with: [], + voided_by: [], + twins: [], + accumulated_weight: 1, + score: 0, + height: 0, + min_height: 0, + feature_activation_bit_counts: null, + first_block: null, + validation: 'full', first_block_height: 1234, }, }, @@ -1030,6 +1086,32 @@ export default { hash: '5c02adea056d7b43e83171a0e2d226d564c791d583b32e9a404ef53a2e1b363a', version: 4, nc_blueprint_id: '3cb032600bdf7db784800e4ea911b10676fa2f67591f82bb62628c234e771595', + nonce: '0', + timestamp: 1572636346, + weight: 1, + signal_bits: 0, + parents: ['1234', '5678'], + inputs: [], + outputs: [], + tokens: [], + raw: '', + }, + spent_outputs: {}, + meta: { + hash: '5c02adea056d7b43e83171a0e2d226d564c791d583b32e9a404ef53a2e1b363a', + spent_outputs: [], + received_by: [], + children: [], + conflict_with: [], + voided_by: [], + twins: [], + accumulated_weight: 1, + score: 0, + height: 0, + min_height: 0, + feature_activation_bit_counts: null, + first_block: null, + validation: 'full' }, }, '/getmininginfo': { diff --git a/__tests__/atomic-swap/get-my-signatures.test.js b/__tests__/atomic-swap/get-my-signatures.test.js index 283d79cf..ea909dc3 100644 --- a/__tests__/atomic-swap/get-my-signatures.test.js +++ b/__tests__/atomic-swap/get-my-signatures.test.js @@ -101,10 +101,10 @@ describe('get-my-signatures api', () => { fromPartialTxSpy.mockImplementation((pt, storage) => createProposal( storage, [ - new ProposalInput(fakeTxId, 0, 10, TestUtils.addresses[0]), + new ProposalInput(fakeTxId, 0, 10n, TestUtils.addresses[0]), ], [ - new ProposalOutput(10, scriptFromAddress(TestUtils.addresses[1])), + new ProposalOutput(10n, scriptFromAddress(TestUtils.addresses[1])), ], )); diff --git a/__tests__/atomic-swap/tx-proposal-create.test.js b/__tests__/atomic-swap/tx-proposal-create.test.js index 59c37148..c6779758 100644 --- a/__tests__/atomic-swap/tx-proposal-create.test.js +++ b/__tests__/atomic-swap/tx-proposal-create.test.js @@ -15,8 +15,8 @@ describe('create tx-proposal api', () => { index: 0, token: hathorLib.constants.NATIVE_TOKEN_UID, address: TestUtils.addresses[0], - value: 10, - authorities: 0, + value: 10n, + authorities: 0n, timelock: null, type: 1, height: null, @@ -238,7 +238,7 @@ describe('create tx-proposal api', () => { index: 1, token: fakeUid, address: TestUtils.addresses[0], - value: 10, + value: 10n, authorities: 0, timelock: null, type: 1, diff --git a/__tests__/decode.test.js b/__tests__/decode.test.js index ba91b1b2..a836f95c 100644 --- a/__tests__/decode.test.js +++ b/__tests__/decode.test.js @@ -146,7 +146,7 @@ describe('decode api', () => { let script = new P2PKH(address); partialTx.outputs.push( new ProposalOutput( - 10, + 10n, script.createScript(), { token: fakeToken1, tokenData: 1 } ) @@ -154,28 +154,19 @@ describe('decode api', () => { address = new Address(TestUtils.addresses[1]); script = new P2PKH(address); - partialTx.outputs.push(new ProposalOutput(20, script.createScript())); + partialTx.outputs.push(new ProposalOutput(20n, script.createScript())); partialTx.inputs.push( new ProposalInput( fakeInputHash, 1, - 30, + 30n, TestUtils.addresses[2], { token: fakeToken2, tokenData: 1 }, ) ); - const txHistoryResponse = httpFixtures['/thin_wallet/address_history']; - const txHistory = txHistoryResponse.history; - const fakeTx = txHistory[0]; - const fakeTxResponse = { - success: true, - tx: fakeTx, - meta: { - first_block_height: 1234, - }, - }; + const fakeTxResponse = httpFixtures['/transaction']; TestUtils.httpMock.onGet('/transaction').reply(200, fakeTxResponse); // 1 input, 2 outputs @@ -199,6 +190,7 @@ describe('decode api', () => { address: 'wgyUgNjqZ18uYr4YfE2ALW6tP5hd8MumH5', type: 'MultiSig', timelock: null, + value: 6400, }, script: expect.any(String), token: '00', diff --git a/__tests__/integration/atomic-swap.test.js b/__tests__/integration/atomic-swap.test.js index c68562ba..bac295a3 100644 --- a/__tests__/integration/atomic-swap.test.js +++ b/__tests__/integration/atomic-swap.test.js @@ -865,8 +865,8 @@ describe('send tx (HTR)', () => { outputs: [expect.objectContaining({ // only 1 output isChange: true, token: '00', - value: 2, - authorities: 0, + value: 2n, + authorities: 0n, decodedScript: expect.objectContaining({ address: expect.objectContaining({ base58: changeAddr }), }), @@ -905,8 +905,8 @@ describe('send tx (HTR)', () => { outputs: [expect.objectContaining({ // only 1 output isChange: true, token: '00', - value: 2, - authorities: 0, + value: 2n, + authorities: 0n, decodedScript: expect.objectContaining({ address: expect.objectContaining({ base58: expect.toBeInArray(wallet1.addresses), @@ -936,8 +936,8 @@ describe('send tx (HTR)', () => { outputs: [expect.objectContaining({ isChange: false, token: '00', - value: 4, - authorities: 0, + value: 4n, + authorities: 0n, decodedScript: expect.objectContaining({ address: expect.objectContaining({ base58: recvAddr }), }), diff --git a/__tests__/integration/create-nft.test.js b/__tests__/integration/create-nft.test.js index 022a8d2e..59c23815 100644 --- a/__tests__/integration/create-nft.test.js +++ b/__tests__/integration/create-nft.test.js @@ -170,7 +170,7 @@ describe('create-nft routes', () => { // Validating authority tokens const authorityOutputs = nftTx.outputs.filter(o => TOKEN_DATA.isAuthorityToken(o.token_data)); expect(authorityOutputs.length).toBe(1); - expect(authorityOutputs[0].value).toBe(AUTHORITY_VALUE.MINT); + expect(BigInt(authorityOutputs[0].value)).toBe(AUTHORITY_VALUE.MINT); }); it('should create nft with melt authority', async () => { @@ -193,7 +193,7 @@ describe('create-nft routes', () => { // Validating authority tokens const authorityOutputs = nftTx.outputs.filter(o => TOKEN_DATA.isAuthorityToken(o.token_data)); expect(authorityOutputs.length).toBe(1); - expect(authorityOutputs[0].value).toBe(AUTHORITY_VALUE.MELT); + expect(BigInt(authorityOutputs[0].value)).toBe(AUTHORITY_VALUE.MELT); }); it('should create nft with mint and melt authorities', async () => { @@ -217,8 +217,8 @@ describe('create-nft routes', () => { // Validating authority tokens const authorityOutputs = nftTx.outputs.filter(o => TOKEN_DATA.isAuthorityToken(o.token_data)); expect(authorityOutputs.length).toBe(2); - expect(authorityOutputs.find(o => o.value === AUTHORITY_VALUE.MINT)).toBeTruthy(); - expect(authorityOutputs.find(o => o.value === AUTHORITY_VALUE.MELT)).toBeTruthy(); + expect(authorityOutputs.find(o => BigInt(o.value) === AUTHORITY_VALUE.MINT)).toBeTruthy(); + expect(authorityOutputs.find(o => BigInt(o.value) === AUTHORITY_VALUE.MELT)).toBeTruthy(); }); it('should create the NFT and send authority outputs to the correct address', async () => { @@ -248,14 +248,14 @@ describe('create-nft routes', () => { ); expect(authorityOutputs).toHaveLength(2); const mintOutput = authorityOutputs.filter( - o => o.value === constants.TOKEN_MINT_MASK + o => BigInt(o.value) === constants.TOKEN_MINT_MASK ); const mintP2pkh = scriptsUtils.parseP2PKH(Buffer.from(mintOutput[0].script.data), network); // Validate that the mint output was sent to the correct address expect(mintP2pkh.address.base58).toEqual(address0); const meltOutput = authorityOutputs.filter( - o => o.value === constants.TOKEN_MELT_MASK + o => BigInt(o.value) === constants.TOKEN_MELT_MASK ); const meltP2pkh = scriptsUtils.parseP2PKH(Buffer.from(meltOutput[0].script.data), network); // Validate that the melt output was sent to the correct address @@ -320,14 +320,14 @@ describe('create-nft routes', () => { ); expect(authorityOutputs).toHaveLength(2); const mintOutput = authorityOutputs.filter( - o => o.value === constants.TOKEN_MINT_MASK + o => BigInt(o.value) === constants.TOKEN_MINT_MASK ); const mintP2pkh = scriptsUtils.parseP2PKH(Buffer.from(mintOutput[0].script.data), network); // Validate that the mint output was sent to the correct address expect(mintP2pkh.address.base58).toEqual(address2idx0); const meltOutput = authorityOutputs.filter( - o => o.value === constants.TOKEN_MELT_MASK + o => BigInt(o.value) === constants.TOKEN_MELT_MASK ); const meltP2pkh = scriptsUtils.parseP2PKH(Buffer.from(meltOutput[0].script.data), network); // Validate that the melt output was sent to the correct address diff --git a/__tests__/integration/create-token.test.js b/__tests__/integration/create-token.test.js index e28e6af6..03c17af8 100644 --- a/__tests__/integration/create-token.test.js +++ b/__tests__/integration/create-token.test.js @@ -292,7 +292,7 @@ describe('create token', () => { o => transactionUtils.isAuthorityOutput({ token_data: o.tokenData }) ); expect(authorityOutputs.length).toBe(1); - expect(authorityOutputs[0].value).toBe(constants.TOKEN_MINT_MASK); + expect(BigInt(authorityOutputs[0].value)).toBe(constants.TOKEN_MINT_MASK); }); it('should create token with only melt authority', async () => { @@ -319,7 +319,7 @@ describe('create token', () => { o => transactionUtils.isAuthorityOutput({ token_data: o.tokenData }) ); expect(authorityOutputs.length).toBe(1); - expect(authorityOutputs[0].value).toBe(constants.TOKEN_MELT_MASK); + expect(BigInt(authorityOutputs[0].value)).toBe(constants.TOKEN_MELT_MASK); }); it('should create token with mint and melt authorities', async () => { @@ -346,8 +346,8 @@ describe('create token', () => { o => transactionUtils.isAuthorityOutput({ token_data: o.tokenData }) ); expect(authorityOutputs.length).toBe(2); - expect(authorityOutputs.find(o => o.value === constants.TOKEN_MINT_MASK)).toBeTruthy(); - expect(authorityOutputs.find(o => o.value === constants.TOKEN_MELT_MASK)).toBeTruthy(); + expect(authorityOutputs.find(o => BigInt(o.value) === constants.TOKEN_MINT_MASK)).toBeTruthy(); + expect(authorityOutputs.find(o => BigInt(o.value) === constants.TOKEN_MELT_MASK)).toBeTruthy(); }); it('should create the token and send authority outputs to the correct address', async () => { @@ -378,14 +378,14 @@ describe('create token', () => { ); expect(authorityOutputs).toHaveLength(2); const mintOutput = authorityOutputs.filter( - o => o.value === constants.TOKEN_MINT_MASK + o => BigInt(o.value) === constants.TOKEN_MINT_MASK ); const mintP2pkh = scriptsUtils.parseP2PKH(Buffer.from(mintOutput[0].script.data), network); // Validate that the mint output was sent to the correct address expect(mintP2pkh.address.base58).toEqual(address0); const meltOutput = authorityOutputs.filter( - o => o.value === constants.TOKEN_MELT_MASK + o => BigInt(o.value) === constants.TOKEN_MELT_MASK ); const meltP2pkh = scriptsUtils.parseP2PKH(Buffer.from(meltOutput[0].script.data), network); // Validate that the melt output was sent to the correct address @@ -454,14 +454,14 @@ describe('create token', () => { ); expect(authorityOutputs).toHaveLength(2); const mintOutput = authorityOutputs.filter( - o => o.value === constants.TOKEN_MINT_MASK + o => BigInt(o.value) === constants.TOKEN_MINT_MASK ); const mintP2pkh = scriptsUtils.parseP2PKH(Buffer.from(mintOutput[0].script.data), network); // Validate that the mint output was sent to the correct address expect(mintP2pkh.address.base58).toEqual(address2idx0); const meltOutput = authorityOutputs.filter( - o => o.value === constants.TOKEN_MELT_MASK + o => BigInt(o.value) === constants.TOKEN_MELT_MASK ); const meltP2pkh = scriptsUtils.parseP2PKH(Buffer.from(meltOutput[0].script.data), network); // Validate that the melt output was sent to the correct address diff --git a/__tests__/integration/melt-tokens.test.js b/__tests__/integration/melt-tokens.test.js index 85760d33..af402326 100644 --- a/__tests__/integration/melt-tokens.test.js +++ b/__tests__/integration/melt-tokens.test.js @@ -384,7 +384,7 @@ describe('melt tokens', () => { ); expect(authorityOutputs).toHaveLength(1); const authorityOutput = authorityOutputs[0]; - expect(authorityOutput.value).toEqual(constants.TOKEN_MELT_MASK); + expect(BigInt(authorityOutput.value)).toEqual(constants.TOKEN_MELT_MASK); const p2pkh = scriptsUtils.parseP2PKH(Buffer.from(authorityOutput.script.data), network); // Validate that the authority output was sent to the correct address expect(p2pkh.address.base58).toEqual(address0); @@ -514,7 +514,7 @@ describe('melt tokens', () => { ); expect(authorityOutputs).toHaveLength(1); const authorityOutput = authorityOutputs[0]; - expect(authorityOutput.value).toEqual(constants.TOKEN_MELT_MASK); + expect(BigInt(authorityOutput.value)).toEqual(constants.TOKEN_MELT_MASK); const p2pkh = scriptsUtils.parseP2PKH(Buffer.from(authorityOutput.script.data), network); // Validate that the authority output was sent to the correct address expect(p2pkh.address.base58).toEqual(externalAddress); diff --git a/__tests__/integration/mint-tokens.test.js b/__tests__/integration/mint-tokens.test.js index d22923ec..66ae426e 100644 --- a/__tests__/integration/mint-tokens.test.js +++ b/__tests__/integration/mint-tokens.test.js @@ -260,7 +260,7 @@ describe('mint token', () => { ); expect(authorityOutputs).toHaveLength(1); const authorityOutput = authorityOutputs[0]; - expect(authorityOutput.value).toEqual(constants.TOKEN_MINT_MASK); + expect(BigInt(authorityOutput.value)).toEqual(constants.TOKEN_MINT_MASK); const p2pkh = scriptsUtils.parseP2PKH(Buffer.from(authorityOutput.script.data), network); // Validate that the authority output was sent to the correct address expect(p2pkh.address.base58).toEqual(address0); @@ -367,7 +367,7 @@ describe('mint token', () => { ); expect(authorityOutputs).toHaveLength(1); const authorityOutput = authorityOutputs[0]; - expect(authorityOutput.value).toEqual(constants.TOKEN_MINT_MASK); + expect(BigInt(authorityOutput.value)).toEqual(constants.TOKEN_MINT_MASK); const p2pkh = scriptsUtils.parseP2PKH(Buffer.from(authorityOutput.script.data), network); // Validate that the authority output was sent to the correct address expect(p2pkh.address.base58).toEqual(externalAddress); diff --git a/__tests__/integration/multisig.test.js b/__tests__/integration/multisig.test.js index c42c49b6..0130a2c3 100644 --- a/__tests__/integration/multisig.test.js +++ b/__tests__/integration/multisig.test.js @@ -313,7 +313,7 @@ describe('send tx (HTR)', () => { if (decoded.address.base58 === burnAddress) { // This is the intended output expect(decoded.getType()).toBe('p2pkh'); - expect(output.value).toBe(10); + expect(output.value).toBe(10n); continue; } diff --git a/__tests__/push-tx-hex.test.js b/__tests__/push-tx-hex.test.js index 3df54f0b..0c784482 100644 --- a/__tests__/push-tx-hex.test.js +++ b/__tests__/push-tx-hex.test.js @@ -49,7 +49,7 @@ describe('push tx api', () => { error: 'Boom!', }); - createSpy.mockImplementation(() => createTxToPush([new Input(FAKE_TX_ID, 0)], [new Output(1, Buffer.from('CAFECAFE', 'hex'))])); + createSpy.mockImplementation(() => createTxToPush([new Input(FAKE_TX_ID, 0)], [new Output(1n, Buffer.from('CAFECAFE', 'hex'))])); const miningSpy = jest.spyOn(SendTransaction.prototype, 'runFromMining').mockImplementationOnce(async () => { throw new Error('Another boom!'); }); @@ -69,7 +69,7 @@ describe('push tx api', () => { }); it('should push valid transactions', async () => { - const tx = createTxToPush([new Input(FAKE_TX_ID, 0)], [new Output(1, Buffer.from('CAFECAFE', 'hex'))]); + const tx = createTxToPush([new Input(FAKE_TX_ID, 0)], [new Output(1n, Buffer.from('CAFECAFE', 'hex'))]); const response = await TestUtils.request .post('/push-tx') .send({ txHex: tx.toHex() }) diff --git a/__tests__/schemas.test.js b/__tests__/schemas.test.js new file mode 100644 index 00000000..cce64a0a --- /dev/null +++ b/__tests__/schemas.test.js @@ -0,0 +1,16 @@ +import { bigIntSanitizer } from '../src/schemas'; + +describe('test bigIntSanitizer', () => { + it('should return expected value', () => { + expect(bigIntSanitizer(null)).toStrictEqual(null); + expect(bigIntSanitizer(undefined)).toStrictEqual(undefined); + expect(bigIntSanitizer(123)).toStrictEqual(123n); + expect(bigIntSanitizer(123n)).toStrictEqual(123n); + expect(bigIntSanitizer(12345678901234567890n)).toStrictEqual(12345678901234567890n); + expect(bigIntSanitizer('123')).toStrictEqual(123n); + expect(bigIntSanitizer('12345678901234567890')).toStrictEqual(12345678901234567890n); + + expect(bigIntSanitizer('not a bigint')).toStrictEqual(undefined); + expect(bigIntSanitizer(123.456)).toStrictEqual(undefined); + }); +}); diff --git a/__tests__/tx-proposal/build-tx.test.js b/__tests__/tx-proposal/build-tx.test.js index efdf988b..eaeb4423 100644 --- a/__tests__/tx-proposal/build-tx.test.js +++ b/__tests__/tx-proposal/build-tx.test.js @@ -255,30 +255,30 @@ describe('create tx-proposal api', () => { expect(tx.getDataToSignHash().toString('hex')).toEqual(response.body.dataToSignHash); expect(tx.outputs).toEqual(expect.arrayContaining([ - expect.objectContaining({ value: 1, tokenData: 0, decodedScript: { data: 'cafed00d' } }), + expect.objectContaining({ value: 1n, tokenData: 0, decodedScript: { data: 'cafed00d' } }), expect.objectContaining({ - value: 10, + value: 10n, tokenData: 0, decodedScript: expect.objectContaining({ address: expect.objectContaining({ base58: TestUtils.addresses[0] }) }), }), expect.objectContaining({ - value: 15, + value: 15n, tokenData: 1, decodedScript: expect.objectContaining({ address: expect.objectContaining({ base58: TestUtils.addresses[1] }) }), }), expect.objectContaining({ - value: 20, + value: 20n, tokenData: 0, decodedScript: expect.objectContaining({ address: expect.objectContaining({ base58: TestUtils.multisigAddresses[0] }) }), }), expect.objectContaining({ - value: 25, + value: 25n, tokenData: 1, decodedScript: expect.objectContaining({ address: expect.objectContaining({ base58: TestUtils.multisigAddresses[1] }) @@ -291,17 +291,17 @@ describe('create tx-proposal api', () => { // HTR inputs utxos // The helper getUtxosToFillTx will filter utxos to send the least amount of inputs. spyUtxos.mockReturnValueOnce({ - total_amount_available: 6400, + total_amount_available: 6400n, utxos: [ - { amount: 6400, tx_id: TX_ID, index: 0 }, + { amount: 6400n, tx_id: TX_ID, index: 0 }, ], }); // custom token utxos // The helper will add both since we need them to fill the output spyUtxos.mockReturnValueOnce({ - total_amount_available: 100, + total_amount_available: 100n, utxos: [ - { amount: 100, tx_id: TOKEN_UID, index: 1 }, + { amount: 100n, tx_id: TOKEN_UID, index: 1 }, ], }); let response = await TestUtils.request @@ -332,9 +332,9 @@ describe('create tx-proposal api', () => { type: 'query', max_utxos: 27, filter_address: TestUtils.addresses[2], - amount_smaller_than: 20, - amount_bigger_than: 10, - maximum_amount: 100, + amount_smaller_than: 20n, + amount_bigger_than: 10n, + maximum_amount: 100n, token: NATIVE_TOKEN_UID, only_available_utxos: true, }); @@ -342,18 +342,18 @@ describe('create tx-proposal api', () => { type: 'query', max_utxos: 27, filter_address: TestUtils.addresses[2], - amount_smaller_than: 20, - amount_bigger_than: 10, - maximum_amount: 100, + amount_smaller_than: 20n, + amount_bigger_than: 10n, + maximum_amount: 100n, token: TOKEN_UID, only_available_utxos: true, }); // Will run a similar query but passing only 1 parameter spyUtxos.mockReturnValueOnce({ - total_amount_available: 6400, + total_amount_available: 6400n, utxos: [ - { amount: 6400, tx_id: TX_ID, index: 0 }, + { amount: 6400n, tx_id: TX_ID, index: 0 }, ], }); response = await TestUtils.request @@ -401,7 +401,7 @@ describe('create tx-proposal api', () => { const tx = helpersUtils.createTxFromHex(response.body.txHex, new Network('testnet')); expect(tx.outputs).toEqual(expect.arrayContaining([ expect.objectContaining({ - value: 1, + value: 1n, tokenData: 0, decodedScript: expect.objectContaining({ address: expect.objectContaining({ base58: TestUtils.addresses[0] }) diff --git a/__tests__/tx.helper.test.js b/__tests__/tx.helper.test.js new file mode 100644 index 00000000..f9046f77 --- /dev/null +++ b/__tests__/tx.helper.test.js @@ -0,0 +1,38 @@ +import { getUtxosToFillTx } from '../src/helpers/tx.helper'; + +describe('test getUtxosToFillTx', () => { + const fakeWallet = { + getUtxos(_options) { + return { + utxos: [ + { amount: 1n }, + { amount: 2n }, + { amount: 3n }, + ], + }; + } + }; + + it('should return expected utxos', async () => { + const result1 = await getUtxosToFillTx(fakeWallet, 1n, {}); + expect(result1).toStrictEqual([{ amount: 1n }]); + + const result2 = await getUtxosToFillTx(fakeWallet, 2n, {}); + expect(result2).toStrictEqual([{ amount: 2n }]); + + const result3 = await getUtxosToFillTx(fakeWallet, 3n, {}); + expect(result3).toStrictEqual([{ amount: 3n }]); + + const result4 = await getUtxosToFillTx(fakeWallet, 4n, {}); + expect(result4).toStrictEqual([{ amount: 3n }, { amount: 2n }]); + + const result5 = await getUtxosToFillTx(fakeWallet, 5n, {}); + expect(result5).toStrictEqual([{ amount: 3n }, { amount: 2n }]); + + const result6 = await getUtxosToFillTx(fakeWallet, 6n, {}); + expect(result6).toStrictEqual([{ amount: 3n }, { amount: 2n }, { amount: 1n }]); + + const result7 = await getUtxosToFillTx(fakeWallet, 7n, {}); + expect(result7).toStrictEqual(null); + }); +}); diff --git a/package-lock.json b/package-lock.json index 2a262392..7dabc744 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,14 +11,15 @@ "dependencies": { "@dinamonetworks/hsm-dinamo": "4.9.1", "@hathor/healthcheck-lib": "0.1.0", - "@hathor/wallet-lib": "1.14.0", - "axios": "1.7.2", + "@hathor/wallet-lib": "2.0.1", + "axios": "1.7.7", "express": "4.18.2", "express-validator": "6.10.0", "jsonwebtoken": "9.0.2", "lodash": "4.17.21", "morgan": "1.10.0", "uuid4": "2.0.3", + "validator": "13.11.0", "winston": "3.12.0", "yargs": "17.7.2" }, @@ -41,7 +42,7 @@ "supertest": "6.3.4" }, "engines": { - "node": ">=20.0.0", + "node": ">=22.0.0", "npm": ">=10.0.0" } }, @@ -2169,26 +2170,27 @@ "integrity": "sha512-Oi223+iKye5cmPyMIqp64E/ZP+in0JndN/s9uEigmXxt6wRhwciCPbzSY4S2oicy1uNqhv7lLdyUc3O/P3sCzQ==" }, "node_modules/@hathor/wallet-lib": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@hathor/wallet-lib/-/wallet-lib-1.14.0.tgz", - "integrity": "sha512-la6JWWob5142g10ORBW5zgGZRvS+UvnANpUN6Zg+O1+O7yKeiRZrjO9UquxKUiyyFqiw8rWGKkMRfrcrW8flBw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@hathor/wallet-lib/-/wallet-lib-2.0.1.tgz", + "integrity": "sha512-wqqQsZP6p4cXlkDc/kZuOkZoh1XZprEjR0VkJvdy5IvgQtjYvHv0Fklz3EAhhh/zBPIeytD1wD0AvaRzhP6fEQ==", "license": "MIT", "dependencies": { "abstract-level": "1.0.4", - "axios": "1.7.2", + "axios": "1.7.7", "bitcore-lib": "8.25.10", "bitcore-mnemonic": "8.25.10", "buffer": "6.0.3", "crypto-js": "4.2.0", "isomorphic-ws": "5.0.0", "level": "8.0.1", + "level-transcoder": "1.0.1", "lodash": "4.17.21", - "long": "5.2.3", "queue-microtask": "1.2.3", - "ws": "8.17.1" + "ws": "8.17.1", + "zod": "3.23.8" }, "engines": { - "node": ">=20.0.0", + "node": ">=22.0.0", "npm": ">=10.0.0" } }, @@ -3718,9 +3720,10 @@ } }, "node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -9243,11 +9246,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "node_modules/long": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" - }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -11551,6 +11549,15 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 0aaa120f..c62027cd 100644 --- a/package.json +++ b/package.json @@ -4,16 +4,17 @@ "description": "Hathor Wallet Headless, i.e., without graphical user interface", "main": "index.js", "engines": { - "node": ">=20.0.0", + "node": ">=22.0.0", "npm": ">=10.0.0" }, "dependencies": { "@dinamonetworks/hsm-dinamo": "4.9.1", "@hathor/healthcheck-lib": "0.1.0", - "@hathor/wallet-lib": "1.14.0", - "axios": "1.7.2", + "@hathor/wallet-lib": "2.0.1", + "axios": "1.7.7", "express": "4.18.2", "express-validator": "6.10.0", + "validator": "13.11.0", "jsonwebtoken": "9.0.2", "lodash": "4.17.21", "morgan": "1.10.0", diff --git a/src/app.js b/src/app.js index 8500e9ef..977212bb 100644 --- a/src/app.js +++ b/src/app.js @@ -16,6 +16,8 @@ import mainRouter from './routes/index.routes'; import { initHathorLib } from './helpers/wallet.helper'; import { loggerMiddleware } from './middlewares/logger.middleware'; +const { bigIntUtils } = require('@hathor/wallet-lib'); + // Initializing Hathor Lib const createApp = config => { @@ -25,7 +27,18 @@ const createApp = config => { // Initializing ExpressJS const app = express(); - app.use(express.json()); + + // To deal with BigInts in JSONs, which are used mainly in transaction output values, we need + // to configure both a custom JSON replacer and a reviver, below. + + // We configure a custom JSON replacer that Express will use to stringify API responses. + app.set('json replacer', bigIntUtils.JSONBigInt.bigIntReplacer); + + // We configure a custom JSON reviver that Express will use to parse API requests. + app.use(express.json({ + reviver: bigIntUtils.JSONBigInt.bigIntReviver, + })); + app.use(express.urlencoded({ extended: true })); app.use(morgan(config.httpLogFormat || 'combined', { stream: logger.stream })); app.use(loggerMiddleware); diff --git a/src/controllers/wallet/atomic-swap/tx-proposal.controller.js b/src/controllers/wallet/atomic-swap/tx-proposal.controller.js index f87fa228..15396573 100644 --- a/src/controllers/wallet/atomic-swap/tx-proposal.controller.js +++ b/src/controllers/wallet/atomic-swap/tx-proposal.controller.js @@ -83,7 +83,7 @@ async function buildTxProposal(req, res) { const addressIndex = await wallet.getAddressIndex(txout.decoded.address); const addressPath = addressIndex ? await req.wallet.getAddressPathForIndex(addressIndex) : ''; - let authorities = 0; + let authorities = 0n; if (transactionUtils.isMint(txout)) { authorities += TOKEN_MINT_MASK; } diff --git a/src/controllers/wallet/wallet.controller.js b/src/controllers/wallet/wallet.controller.js index 4d3a58f5..72b8d53c 100755 --- a/src/controllers/wallet/wallet.controller.js +++ b/src/controllers/wallet/wallet.controller.js @@ -8,6 +8,7 @@ const { txApi, walletApi, WalletType, constants: hathorLibConstants, helpersUtils, errors, tokensUtils, transactionUtils, PartialTx } = require('@hathor/wallet-lib'); const { matchedData } = require('express-validator'); // import is used because there is an issue with winston logger when using require ref: #262 +const { JSONBigInt } = require('@hathor/wallet-lib/lib/utils/bigint'); const { parametersValidation } = require('../../helpers/validations.helper'); const { friendlyWalletState, cantSendTxErrorMessage } = require('../../helpers/constants'); const { mapTxReturn, prepareTxFunds, getTx, markUtxosSelectedAsInput, runSendTransaction } = require('../../helpers/tx.helper'); @@ -510,7 +511,7 @@ async function sendTx(req, res) { const ret = { success: false, error: err.message }; if (debug) { logger.debug('/send-tx failed', { - body: JSON.stringify(req.body), + body: JSONBigInt.stringify(req.body), response: JSON.stringify(ret), }); } diff --git a/src/helpers/tx.helper.js b/src/helpers/tx.helper.js index 5eb60fa9..3ca7dd73 100644 --- a/src/helpers/tx.helper.js +++ b/src/helpers/tx.helper.js @@ -51,7 +51,7 @@ function mapTxReturn(tx) { * XXX This can be refactored to use the lib methods, specially the storage.selectUtxos * * @param {HathorWallet} wallet The wallet object - * @param {number} sumOutputs The sum of outputs of the transaction I need to fill + * @param {bigint} sumOutputs The sum of outputs of the transaction I need to fill * @param {Object} options The options to filter the utxos * (see utxo-filter API to see the possibilities) */ @@ -69,7 +69,12 @@ async function getUtxosToFillTx(wallet, sumOutputs, options) { const { utxos } = utxosDetails; // Sort utxos with larger amounts first - utxos.sort((a, b) => b.amount - a.amount); + utxos.sort((a, b) => { + if (a.amount < b.amount) { + return 1; + } + return a.amount > b.amount ? -1 : 0; + }); if (utxos[0].amount > sumOutputs) { // If I have a single utxo capable of providing the full amount @@ -87,7 +92,7 @@ async function getUtxosToFillTx(wallet, sumOutputs, options) { return [utxos[firstSmallerIndex - 1]]; } // Else I get the utxos in order until the full amount is filled - let total = 0; + let total = 0n; const retUtxos = []; for (const utxo of utxos) { retUtxos.push(utxo); @@ -132,7 +137,7 @@ async function prepareTxFunds(wallet, outputs, inputs, defaultToken = NATIVE_TOK * @typedef TokenOutput * A structure to help calculate how many tokens will be needed on send-tx's automatic inputs * @property {string} tokenUid Hash identification of the token - * @property {number} amount Amount of tokens necessary on the inputs + * @property {bigint} amount Amount of tokens necessary on the inputs */ /** @@ -151,14 +156,14 @@ async function prepareTxFunds(wallet, outputs, inputs, defaultToken = NATIVE_TOK // Updating the `tokens` amount if (!tokens.has(output.token)) { - tokens.set(output.token, { tokenUid: output.token, amount: 0 }); + tokens.set(output.token, { tokenUid: output.token, amount: 0n }); } if (output.type === 'data') { // The data output requires that the user burns 0.01 HTR // this must be set here, in order to make the filter_address query // work if the inputs are selected by this method - output.value = 1; + output.value = 1n; } const sumObject = tokens.get(output.token); diff --git a/src/routes/wallet/p2sh/tx-proposal.routes.js b/src/routes/wallet/p2sh/tx-proposal.routes.js index 09c0f772..f7d5bcdf 100644 --- a/src/routes/wallet/p2sh/tx-proposal.routes.js +++ b/src/routes/wallet/p2sh/tx-proposal.routes.js @@ -17,6 +17,10 @@ const { signAndPush, } = require('../../../controllers/wallet/p2sh/tx-proposal.controller'); const { patchExpressRouter } = require('../../../patch'); +const { + bigIntSanitizer, + bigIntValidator +} = require('../../../schemas'); const txProposalRouter = patchExpressRouter(Router({ mergeParams: true })); @@ -37,12 +41,12 @@ txProposalRouter.post( 'outputs.*.value': { in: ['body'], errorMessage: 'Invalid output value', - isInt: { - options: { - min: 1, - }, + custom: { + options: bigIntValidator({ min: 1 }), + }, + customSanitizer: { + options: bigIntSanitizer, }, - toInt: true, }, 'outputs.*.token': { in: ['body'], @@ -96,7 +100,7 @@ txProposalRouter.post( '/create-token', body('name').isString().notEmpty(), body('symbol').isString().notEmpty(), - body('amount').isInt({ min: 1 }).toInt(), + body('amount').custom(bigIntValidator({ min: 1 })).customSanitizer(bigIntSanitizer), body('address').isString().notEmpty().optional(), body('change_address').isString().notEmpty().optional(), body('create_mint').isBoolean().optional(), @@ -112,7 +116,7 @@ txProposalRouter.post( txProposalRouter.post( '/mint-tokens', body('token').isString().notEmpty(), - body('amount').isInt({ min: 1 }).toInt(), + body('amount').custom(bigIntValidator({ min: 1 })).customSanitizer(bigIntSanitizer), body('address').isString().notEmpty().optional(), body('change_address').isString().notEmpty().optional(), body('create_mint').isBoolean().optional(), @@ -125,7 +129,7 @@ txProposalRouter.post( txProposalRouter.post( '/melt-tokens', body('token').isString().notEmpty(), - body('amount').isInt({ min: 1 }).toInt(), + body('amount').custom(bigIntValidator({ min: 1 })).customSanitizer(bigIntSanitizer), body('deposit_address').isString().notEmpty().optional(), body('change_address').isString().notEmpty().optional(), body('create_melt').isBoolean().optional(), diff --git a/src/routes/wallet/wallet.routes.js b/src/routes/wallet/wallet.routes.js index ecb8d906..641f17da 100644 --- a/src/routes/wallet/wallet.routes.js +++ b/src/routes/wallet/wallet.routes.js @@ -15,7 +15,10 @@ const { getAddressIndex, getTxConfirmationBlocks, utxosSelectedAsInput, } = require('../../controllers/wallet/wallet.controller'); -const { txHexSchema, partialTxSchema } = require('../../schemas'); +const { + txHexSchema, partialTxSchema, bigIntSanitizer, + bigIntValidator +} = require('../../schemas'); const p2shRouter = require('./p2sh/p2sh.routes'); const atomicSwapRouter = require('./atomic-swap/atomic-swap.routes'); const txProposalRouter = require('./tx-proposal/tx-proposal.routes'); @@ -134,12 +137,12 @@ walletRouter.post( }, value: { in: ['body'], - isInt: { - options: { - min: 1 - } + custom: { + options: bigIntValidator({ min: 1 }), + }, + customSanitizer: { + options: bigIntSanitizer, }, - toInt: true }, change_address: { in: ['body'], @@ -208,12 +211,12 @@ walletRouter.post( }, 'outputs.*.value': { in: ['body'], - isInt: { - options: { - min: 1 - } + custom: { + options: bigIntValidator({ min: 1 }), + }, + customSanitizer: { + options: bigIntSanitizer, }, - toInt: true, optional: true }, 'outputs.*.token': { @@ -357,7 +360,7 @@ walletRouter.post( '/create-token', body('name').isString(), body('symbol').isString(), - body('amount').isInt({ min: 1 }).toInt(), + body('amount').custom(bigIntValidator({ min: 1 })).customSanitizer(bigIntSanitizer), body('address').isString().optional(), body('change_address').isString().optional(), body('create_mint').isBoolean().optional().toBoolean(), @@ -378,7 +381,7 @@ walletRouter.post( walletRouter.post( '/mint-tokens', body('token').isString(), - body('amount').isInt({ min: 1 }).toInt(), + body('amount').custom(bigIntValidator({ min: 1 })).customSanitizer(bigIntSanitizer), body('address').isString().optional(), body('change_address').isString().optional(), body('mint_authority_address').isString().optional(), @@ -396,7 +399,7 @@ walletRouter.post( walletRouter.post( '/melt-tokens', body('token').isString(), - body('amount').isInt({ min: 1 }).toInt(), + body('amount').custom(bigIntValidator({ min: 1 })).customSanitizer(bigIntSanitizer), body('change_address').isString().optional(), body('deposit_address').isString().optional(), body('melt_authority_address').isString().optional(), @@ -416,9 +419,9 @@ walletRouter.get( query('max_utxos').isInt().optional().toInt(), query('token').isString().optional(), query('filter_address').isString().optional(), - query('amount_smaller_than').isInt().optional().toInt(), - query('amount_bigger_than').isInt().optional().toInt(), - query('maximum_amount').isInt().optional().toInt(), + query('amount_smaller_than').custom(bigIntValidator()).optional().customSanitizer(bigIntSanitizer), + query('amount_bigger_than').custom(bigIntValidator()).optional().customSanitizer(bigIntSanitizer), + query('maximum_amount').custom(bigIntValidator()).optional().customSanitizer(bigIntSanitizer), query('only_available_utxos').isBoolean().optional().toBoolean(), utxoFilter ); @@ -433,9 +436,9 @@ walletRouter.post( body('max_utxos').isInt().optional().toInt(), body('token').isString().optional(), body('filter_address').isString().optional(), - body('amount_smaller_than').isInt().optional().toInt(), - body('amount_bigger_than').isInt().optional().toInt(), - body('maximum_amount').isInt().optional().toInt(), + body('amount_smaller_than').custom(bigIntValidator()).optional().customSanitizer(bigIntSanitizer), + body('amount_bigger_than').custom(bigIntValidator()).optional().customSanitizer(bigIntSanitizer), + body('maximum_amount').custom(bigIntValidator()).optional().customSanitizer(bigIntSanitizer), utxoConsolidation ); @@ -447,7 +450,7 @@ walletRouter.post( '/create-nft', body('name').isString(), body('symbol').isString(), - body('amount').isInt({ min: 1 }).toInt(), + body('amount').custom(bigIntValidator({ min: 1 })).customSanitizer(bigIntSanitizer), body('data').isString().isLength({ max: MAX_DATA_SCRIPT_LENGTH }), body('address').isString().optional(), body('change_address').isString().optional(), diff --git a/src/schemas.js b/src/schemas.js index c6bf025a..42e9aac2 100644 --- a/src/schemas.js +++ b/src/schemas.js @@ -6,6 +6,9 @@ */ import { walletUtils } from '@hathor/wallet-lib'; +import { bigIntCoercibleSchema, parseSchema } from '@hathor/wallet-lib/lib/utils/bigint'; + +const validator = require('validator'); export const txHexSchema = { txHex: { @@ -139,12 +142,12 @@ export const atomicSwapCreateSchema = { 'send.tokens.*.value': { in: ['body'], errorMessage: 'Invalid value', - isInt: { - options: { - min: 1, - }, + custom: { + options: bigIntValidator({ min: 1 }), + }, + customSanitizer: { + options: bigIntSanitizer, }, - toInt: true, }, 'receive.tokens': { in: ['body'], @@ -162,12 +165,12 @@ export const atomicSwapCreateSchema = { 'receive.tokens.*.value': { in: ['body'], errorMessage: 'Invalid value', - isInt: { - options: { - min: 1, - }, + custom: { + options: bigIntValidator({ min: 1 }), + }, + customSanitizer: { + options: bigIntSanitizer, }, - toInt: true, }, 'receive.tokens.*.timelock': { in: ['body'], @@ -252,13 +255,13 @@ export const txBuildSchema = { 'outputs.*.value': { in: ['body'], errorMessage: 'Invalid value', - isInt: { - options: { - min: 1, - }, + custom: { + options: bigIntValidator({ min: 1 }), }, - toInt: true, optional: true, + customSanitizer: { + options: bigIntSanitizer, + }, }, 'outputs.*.token': { in: ['body'], @@ -360,35 +363,35 @@ export const queryInputSchema = { 'inputs.*.amount_smaller_than': { in: ['body'], errorMessage: 'Invalid amount_smaller_than', - isInt: { - options: { - min: 2, - }, + custom: { + options: bigIntValidator({ min: 2 }), + }, + customSanitizer: { + options: bigIntSanitizer, }, - toInt: true, optional: true, }, 'inputs.*.amount_bigger_than': { in: ['body'], errorMessage: 'Invalid amount_bigger_than', - isInt: { - options: { - min: 1, - }, + custom: { + options: bigIntValidator({ min: 1 }), + }, + customSanitizer: { + options: bigIntSanitizer, }, - toInt: true, optional: true, }, 'inputs.*.maximum_amount': { in: ['body'], errorMessage: 'Invalid maximum_amount', - isInt: { - options: { - min: 1, - }, + custom: { + options: bigIntValidator({ min: 1 }), }, - toInt: true, optional: true, + customSanitizer: { + options: bigIntSanitizer, + }, }, }; @@ -518,7 +521,9 @@ export const nanoContractData = { 'data.actions.*.amount': { in: ['body'], errorMessage: 'Invalid action amount.', - isInt: true, + customSanitizer: { + options: bigIntSanitizer, + }, }, 'data.actions.*.address': { in: ['body'], @@ -533,3 +538,22 @@ export const nanoContractData = { optional: true, }, }; + +export function bigIntSanitizer(value) { + try { + return value === undefined || value === null + ? value + : parseSchema(value, bigIntCoercibleSchema); + } catch { + return undefined; + } +} + +/** + * This function substitutes express-validator's default `isInt` validator, as it doesn't work with + * BigInts. It uses the exact same underlying validator from the validatorjs package, so it accepts + * the same kinds of options. + */ +export function bigIntValidator(options) { + return value => validator.isInt(String(value), options); +} From 88387b16b28738fb91dd74585ca92ba64c41ab3b Mon Sep 17 00:00:00 2001 From: Gabriel Levcovitz Date: Tue, 17 Dec 2024 19:02:36 -0300 Subject: [PATCH 3/3] chore: bump v0.34.0-rc.1 (#514) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7dabc744..729494ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "hathor-wallet-headless", - "version": "0.33.0", + "version": "0.34.0-rc.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "hathor-wallet-headless", - "version": "0.33.0", + "version": "0.34.0-rc.1", "license": "MIT", "dependencies": { "@dinamonetworks/hsm-dinamo": "4.9.1", diff --git a/package.json b/package.json index c62027cd..d9837bd6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hathor-wallet-headless", - "version": "0.33.0", + "version": "0.34.0-rc.1", "description": "Hathor Wallet Headless, i.e., without graphical user interface", "main": "index.js", "engines": {