From 33fe4c6e3ef28d50fa8667b5050ac48d8c5df95c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Thu, 16 May 2024 11:32:47 -0300 Subject: [PATCH 1/5] chore: bump v0.28.0 (#445) --- 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 f71dc68c..f5126909 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "hathor-wallet-headless", - "version": "0.28.0-rc.2", + "version": "0.28.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "hathor-wallet-headless", - "version": "0.28.0-rc.2", + "version": "0.28.0", "license": "MIT", "dependencies": { "@dinamonetworks/hsm-dinamo": "4.9.1", diff --git a/package.json b/package.json index 6d753cf1..228f635e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hathor-wallet-headless", - "version": "0.28.0-rc.2", + "version": "0.28.0", "description": "Hathor Wallet Headless, i.e., without graphical user interface", "main": "index.js", "engines": { From 5c0f18784de7f2ea40c2b0cf6841d62c38b6b7ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Wed, 22 May 2024 10:39:06 -0300 Subject: [PATCH 2/5] docs: hsm docs (#448) * docs: hsm docs * chore: use latest version on docker example * chore: add new doc in toc --- docs/README.md | 1 + docs/hsm.md | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 docs/hsm.md diff --git a/docs/README.md b/docs/README.md index ade41301..59ba501f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -20,3 +20,4 @@ Documents in this directory: - [Feature: multi-signature wallets](multisig-wallets.md) - [Feature: atomic swaps](atomic-swaps.md) - [Feature: plugins](plugins.md) +- [Feature: HSM](hsm.md) diff --git a/docs/hsm.md b/docs/hsm.md new file mode 100644 index 00000000..1f247db9 --- /dev/null +++ b/docs/hsm.md @@ -0,0 +1,90 @@ +# Dinamo Networks HSM integration specs + +## Description + +The HSM integration allows you to connect your wallet with the HSM of Dinamo Networks. +This means the wallet is managed by the headless but the private keys are stored in the HSM. + +## Configuration + +The configuration requires the HSM host, user and password to connect to the HSM. + +You will also need to create an HSM key to use with the wallet. + +### Docker + +Environment variables: + +- `HEADLESS_HSM_HOST`: HSM host +- `HEADLESS_HSM_USERNAME`: HSM username +- `HEADLESS_HSM_PASSWORD`: HSM password + +CLI arguments: + +- `--hsm-host`: HSM host +- `--hsm-username`: HSM username +- `--hsm-password`: HSM password + +### Running locally + +When running locally you write the `src/config.js` file and set the configuration in it. +The config names are slightly different but work the same way as in the docker config. + +- `hsmHost` +- `hsmUsername` +- `hsmPassword` + +### Creating an HSM key + +You will need to run a command to create the HSM key in the docker container + +The following command will create a key named `abc`. + +```bash +docker run --env-file=./.env --entrypoint make hathornetwork/hathor-wallet-headless create_hsm_key keyname=abc +``` + +The `.env` file contains the configuration needed to connect to the HSM. +Once created you need to save the keyname chosen for your key. + +## Starting the wallet + +The HSM integration allows you to use the common endpoints of the headless wallet to manage the HSM wallet, but to do so you need to start the wallet with the `/hsm/start` endpoint. + +Example: + +```bash +curl -X POST -H "Content-Type: application/json" --data '{"hsm-key": "abc", "wallet-id": "w1"}' 'http://localhost:3000/hsm/start' +``` + +The `w1` wallet will now be connected to HSM, you can use the usual endpoints to send transactions and manage tokens. + + +
+ POST /hsm/start (Start a wallet with an HSM key) + +##### Parameters + +> | name | type | data type | description | location | +> | --- | --- | --- | --- | --- | +> | hsm-key | required | string | HSM key name | body | +> | wallet-id | required | string | Local ID of the wallet to create | body | + +##### Responses + +> | http code | content-type | response | +> | --- | --- | --- | +> | `200` | `application/json` | `{"success":true}` | +> | `200` | `application/json` | `{"success":false,"message":"Parameter 'wallet-id' is required"}` | +> | `200` | `application/json` | `{"success":false,"message":""}` | +> | `500` | `application/json` | `{"success":"false","message":"Failed to start wallet"}` | + +##### Example cURL + +> ```bash +> curl -X POST -H "Content-Type: application/json" \ +> --data '{"hsm-key": "cafe", "wallet-id": "cafe"}' \ +> 'http://localhost:3000/hsm/start' +> ``` + +
From c3f1488989df2300212e1bff172dfff8413ac5db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Wed, 29 May 2024 18:48:14 -0300 Subject: [PATCH 3/5] docs: fireblocks docs (#447) * docs: fireblocks docs * chore: use latest in docker example * chore: add doc to TOC * chore: add version --- docs/README.md | 3 +- docs/fireblocks.md | 93 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 docs/fireblocks.md diff --git a/docs/README.md b/docs/README.md index 59ba501f..2ac88995 100644 --- a/docs/README.md +++ b/docs/README.md @@ -20,4 +20,5 @@ Documents in this directory: - [Feature: multi-signature wallets](multisig-wallets.md) - [Feature: atomic swaps](atomic-swaps.md) - [Feature: plugins](plugins.md) -- [Feature: HSM](hsm.md) +- [Feature: Fireblocks](fireblocks.md) +- [Feature: HSM](hsm.md) \ No newline at end of file diff --git a/docs/fireblocks.md b/docs/fireblocks.md new file mode 100644 index 00000000..1d7cf9a0 --- /dev/null +++ b/docs/fireblocks.md @@ -0,0 +1,93 @@ +# Fireblocks integration + +## Description + +The Fireblocks integration allows you to connect your wallet with Fireblocks using the RAW transaction signing method. +This means that the transaction is managed by the headless wallet and not by the Fireblocks API or dashboard. +We do not use the Fireblocks SDK and implement the client using the Fireblocks API v1 directly (implemented and checked against version 1.7.4). + +## Configuration + +The configuration requires an api key and secret which can be created on the Fireblocks dashboard. +Under `Settings > Users` you can create a new API user with the `Editor` role and copy the API key and secret. + +The API key and secret are used to authenticate requests to the Fireblocks API. +You will also need the account level xPub to start your wallet. + +### Docker + +When running on docker you need to add the secret as a file in the container, this can be used with [volumes](https://docs.docker.com/storage/volumes/). +Then you need to configure the headless with the API key and path to the secret file in the container environment, you can use either cli arguments or environment variables to pass these configs. + +Environment variables: + +- `HEADLESS_FIREBLOCKS_API_KEY`: API key string +- `HEADLESS_FIREBLOCKS_API_SECRET_FILE`: path to API secret file + +CLI arguments: + +- `--fireblocks-api-key`: API key string +- `--fireblocks-api-secret-file`: path to API secret file + +### Running locally + +When running locally you write the `src/config.js` file and set the configuration in it. +The config names are slightly different but work the same way as in the docker config. + +- `fireblocksApiKey` +- `fireblocksApiSecretFile` + +You can optionally use the `fireblocksApiSecret` to configure the secret as a string, but it's not recommended. + +### Generating account level xPub + +First you need to get the root xPub from the Fireblocks dashboard. +Under `Settings > General > Extended public keys` you can copy your root xPub (ECDSA extended public key) + +Then you will need to derive it to the account level xPub using the following command: + +```bash +docker run -it --rm --entrypoint node hathornetwork/hathor-wallet-headless dist-scripts/fireblocks_derive_xpub.js xpub00... +``` + +Copy and save your account level xPub. + +Obs: if you wish to derive it using your own tools you need to use the `m/44/280/0` derivation path since Fireblocks do not use the usual BIP44 derivation path. + +## Starting the wallet + +The Fireblocks integration allows you to use the common endpoints of the headleess wallet to manage the Fireblocks wallet, but to do so you need to start the wallet with the `/fireblocks/start` endpoint. + +Example: + +```bash +curl -X POST -H "Content-Type: application/json" --data '{"xpub": "xpubABC...", "wallet-id": "w1"}' 'http://localhost:8000/fireblocks/start' +``` + +The `w1` wallet will now be connected to Fireblocks, you can use the usual endpoints to send transactions and manage tokens. + +
+ + POST /wallet/fireblocks/start (Start a wallet with the fireblocks integration) + +##### Parameters + +> | name | type | data type | description | location | +> | --- | --- | --- | --- | --- | +> | xpub | required | string | The xpub of the wallet | body | +> | wallet-id | required | string | create a wallet with this id | body | + +##### Responses + +> | http code | content-type | response | +> | --- | --- | --- | +> | `200` | `application/json` | `{"success":true}` | +> | `200` | `application/json` | `{"success": false, "message":"Bad Request"}` | + +##### Example cURL + +> ```javascript +> curl -X POST -H "Content-Type: application/json" --data '{"xpub-id": "cafe", "wallet-id": "cafe"}' 'http://localhost:8000/fireblocks/start' +> ``` + +
From 074f05f520cb9a035c97df6f1846fc31490c101f Mon Sep 17 00:00:00 2001 From: Campana Date: Mon, 17 Jun 2024 15:47:09 -0300 Subject: [PATCH 4/5] docs(security): update link to bug bounty program (#451) --- SECURITY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SECURITY.md b/SECURITY.md index fde19101..0930888d 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,3 +1,3 @@ # Security -Hathor Labs has a bounty program to encourage white hat hackers to collaborate in identifying security breaches and vulnerabilities in Hathor headless wallet. To know more about this, see [https://immunefi.com/bounty/hathornetwork/](https://immunefi.com/bounty/hathornetwork/). +Hathor Labs has a bounty program to encourage white hat hackers to collaborate in identifying security breaches and vulnerabilities in Hathor headless wallet. To know more about this, see [Bug bounty program at Hathor Network](https://hathor.network/bug-bounty/). From 4648701fd1458ea06877a81ed396e2c9e06f82f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Mon, 17 Jun 2024 23:13:06 -0300 Subject: [PATCH 5/5] feat: data token admin (#452) * feat: data on mint and melt tokens --- __tests__/integration/melt-tokens.test.js | 188 +++++++++++++++++--- __tests__/integration/mint-tokens.test.js | 88 ++++++++- __tests__/melt-tokens.test.js | 15 ++ __tests__/mint-tokens.test.js | 14 ++ package-lock.json | 173 ++++++------------ package.json | 4 +- src/api-docs.js | 22 +++ src/controllers/wallet/wallet.controller.js | 12 +- src/routes/wallet/wallet.routes.js | 8 +- 9 files changed, 372 insertions(+), 152 deletions(-) diff --git a/__tests__/integration/melt-tokens.test.js b/__tests__/integration/melt-tokens.test.js index 72e19e7e..85760d33 100644 --- a/__tests__/integration/melt-tokens.test.js +++ b/__tests__/integration/melt-tokens.test.js @@ -1,10 +1,15 @@ -import { transactionUtils, constants, network, scriptsUtils } from '@hathor/wallet-lib'; +import { transactionUtils, constants, network, scriptsUtils, ScriptData } from '@hathor/wallet-lib'; import { TestUtils } from './utils/test-utils-integration'; import { WALLET_CONSTANTS } from './configuration/test-constants'; import { WalletHelper } from './utils/wallet-helper'; describe('melt tokens', () => { let wallet1; + const totalMinted = 1000; + const initialHTR = 10; + let meltedAmount = 0; + let htrMelted = 0; + const tokenA = { name: 'Token A', symbol: 'TKA', @@ -18,11 +23,11 @@ describe('melt tokens', () => { await WalletHelper.startMultipleWalletsForTest([wallet1]); // Creating a token for the tests - await wallet1.injectFunds(10, 0); + await wallet1.injectFunds(20, 0); const tkAtx = await wallet1.createToken({ name: tokenA.name, symbol: tokenA.symbol, - amount: 800, + amount: 1000, address: await wallet1.getAddressAt(0), change_address: await wallet1.getAddressAt(0) }); @@ -30,7 +35,7 @@ describe('melt tokens', () => { /** * Status: - * wallet1[0]: 2 HTR , 800 TKA + * wallet1[0]: 10 HTR , 1000 TKA */ }); @@ -151,7 +156,7 @@ describe('melt tokens', () => { .send({ token: tokenA.uid, address: await wallet1.getAddressAt(1), - amount: 1000 + amount: totalMinted + 100, }) .set({ 'x-wallet-id': wallet1.walletId }); @@ -173,6 +178,13 @@ describe('melt tokens', () => { }) .set({ 'x-wallet-id': wallet1.walletId }); + meltedAmount += 300; + htrMelted += 3; + /** + * Status: + * wallet1[0]: 13 HTR , 700 TKA + */ + expect(response.body.success).toBe(true); await TestUtils.waitForTxReceived(wallet1.walletId, response.body.hash); @@ -185,12 +197,12 @@ describe('melt tokens', () => { const addr4htr = await wallet1.getAddressInfo(4); const addr4tka = await wallet1.getAddressInfo(4, tokenA.uid); expect(addr4htr.total_amount_available).toBe(0); - expect(addr4tka.total_amount_available).toBe(500); + expect(addr4tka.total_amount_available).toBe(totalMinted - meltedAmount); const balance1htr = await wallet1.getBalance(); const balance1tka = await wallet1.getBalance(tokenA.uid); - expect(balance1htr.available).toBe(2 + 3); - expect(balance1tka.available).toBe(800 - 300); + expect(balance1htr.available).toBe(initialHTR + htrMelted); + expect(balance1tka.available).toBe(totalMinted - meltedAmount); }); it('should melt with deposit address only', async () => { @@ -207,6 +219,13 @@ describe('melt tokens', () => { }) .set({ 'x-wallet-id': wallet1.walletId }); + meltedAmount += 100; + htrMelted += 1; + /** + * Status: + * wallet1[0]: 14 HTR , 600 TKA + */ + expect(response.body.success).toBe(true); await TestUtils.waitForTxReceived(wallet1.walletId, response.body.hash); @@ -218,8 +237,8 @@ describe('melt tokens', () => { const balance1htr = await wallet1.getBalance(); const balance1tka = await wallet1.getBalance(tokenA.uid); - expect(balance1htr.available).toBe(2 + 3 + 1); - expect(balance1tka.available).toBe(800 - 300 - 100); + expect(balance1htr.available).toBe(initialHTR + htrMelted); + expect(balance1tka.available).toBe(totalMinted - meltedAmount); }); it('should melt with change address only', async () => { @@ -232,6 +251,13 @@ describe('melt tokens', () => { }) .set({ 'x-wallet-id': wallet1.walletId }); + meltedAmount += 100; + htrMelted += 1; + /** + * Status: + * wallet1[0]: 15 HTR , 500 TKA + */ + expect(response.body.success).toBe(true); await TestUtils.waitForTxReceived(wallet1.walletId, response.body.hash); @@ -239,12 +265,12 @@ describe('melt tokens', () => { const addr8htr = await wallet1.getAddressInfo(8); const addr8tka = await wallet1.getAddressInfo(8, tokenA.uid); expect(addr8htr.total_amount_available).toBe(0); - expect(addr8tka.total_amount_available).toBe(300); + expect(addr8tka.total_amount_available).toBe(totalMinted - meltedAmount); const balance1htr = await wallet1.getBalance(); const balance1tka = await wallet1.getBalance(tokenA.uid); - expect(balance1htr.available).toBe(2 + 3 + 1 + 1); - expect(balance1tka.available).toBe(800 - 300 - 100 - 100); + expect(balance1htr.available).toBe(initialHTR + htrMelted); + expect(balance1tka.available).toBe(totalMinted - meltedAmount); }); it('should melt with mandatory parameters', async () => { @@ -256,14 +282,21 @@ describe('melt tokens', () => { }) .set({ 'x-wallet-id': wallet1.walletId }); + meltedAmount += 100; + htrMelted += 1; + /** + * Status: + * wallet1[0]: 16 HTR , 400 TKA + */ + expect(response.body.success).toBe(true); await TestUtils.waitForTxReceived(wallet1.walletId, response.body.hash); const balance1htr = await wallet1.getBalance(); const balance1tka = await wallet1.getBalance(tokenA.uid); - expect(balance1htr.available).toBe(2 + 3 + 1 + 1 + 1); // 8 - expect(balance1tka.available).toBe(800 - 300 - 100 - 100 - 100); // 200 + expect(balance1htr.available).toBe(initialHTR + htrMelted); // 16 + expect(balance1tka.available).toBe(totalMinted - meltedAmount); // 400 }); it('should not retrieve funds when melting below 100 tokens', async () => { @@ -275,14 +308,20 @@ describe('melt tokens', () => { }) .set({ 'x-wallet-id': wallet1.walletId }); + meltedAmount += 50; + /** + * Status: + * wallet1[0]: 16 HTR , 350 TKA + */ + expect(response.body.success).toBe(true); await TestUtils.waitForTxReceived(wallet1.walletId, response.body.hash); const balance1htr = await wallet1.getBalance(); const balance1tka = await wallet1.getBalance(tokenA.uid); - expect(balance1htr.available).toBe(8); - expect(balance1tka.available).toBe(150); + expect(balance1htr.available).toBe(initialHTR + htrMelted); + expect(balance1tka.available).toBe(totalMinted - meltedAmount); }); it('should retrieve funds rounded down when not melting multiples of 100', async () => { @@ -291,18 +330,25 @@ describe('melt tokens', () => { .send({ token: tokenA.uid, address: await wallet1.getAddressAt(1), - amount: 100 + amount: 110 }) .set({ 'x-wallet-id': wallet1.walletId }); + meltedAmount += 110; + htrMelted += 1; + /** + * Status: + * wallet1[0]: 17 HTR , 240 TKA + */ + expect(response.body.success).toBe(true); await TestUtils.waitForTxReceived(wallet1.walletId, response.body.hash); const balance1htr = await wallet1.getBalance(); const balance1tka = await wallet1.getBalance(tokenA.uid); - expect(balance1htr.available).toBe(9); - expect(balance1tka.available).toBe(50); + expect(balance1htr.available).toBe(initialHTR + htrMelted); + expect(balance1tka.available).toBe(totalMinted - meltedAmount); }); it('should melt and send melt output to the correct address', async () => { @@ -313,18 +359,24 @@ describe('melt tokens', () => { token: tokenA.uid, address: await wallet1.getAddressAt(16), melt_authority_address: address0, - amount: 30 + amount: 20 }) .set({ 'x-wallet-id': wallet1.walletId }); + meltedAmount += 20; + /** + * Status: + * wallet1[0]: 17 HTR , 220 TKA + */ + const transaction = response.body; expect(transaction.success).toBe(true); await TestUtils.waitForTxReceived(wallet1.walletId, response.body.hash); const balance1htr = await wallet1.getBalance(); const balance1tka = await wallet1.getBalance(tokenA.uid); - expect(balance1htr.available).toBe(9); - expect(balance1tka.available).toBe(20); + expect(balance1htr.available).toBe(initialHTR + htrMelted); + expect(balance1tka.available).toBe(totalMinted - meltedAmount); // Validating a new melt authority was created by default const authorityOutputs = transaction.outputs.filter( @@ -338,7 +390,89 @@ describe('melt tokens', () => { expect(p2pkh.address.base58).toEqual(address0); }); + it('should melt tokens and add data outputs to the transaction', async () => { + const response = await TestUtils.request + .post('/wallet/melt-tokens') + .send({ + token: tokenA.uid, + amount: 10, + data: ['foobar1', 'foobar2'], + }) + .set({ 'x-wallet-id': wallet1.walletId }); + + meltedAmount += 10; + htrMelted -= 2; // we create 2 data outputs and no melted htr + /** + * Status: + * wallet1[0]: 15 HTR , 210 TKA + */ + + expect(response.body.success).toBe(true); + + await TestUtils.waitForTxReceived(wallet1.walletId, response.body.hash); + + const balance1htr = await wallet1.getBalance(); + const balance1tka = await wallet1.getBalance(tokenA.uid); + expect(balance1htr.available).toBe(initialHTR + htrMelted); + expect(balance1tka.available).toBe(totalMinted - meltedAmount); + + const transaction = response.body; + const dataOutput1 = transaction.outputs[1]; + const dataOutput2 = transaction.outputs[0]; + const script1 = Array.from((new ScriptData('foobar1')).createScript()); + const script2 = Array.from((new ScriptData('foobar2')).createScript()); + + expect(dataOutput1.token_data).toBe(0); + expect(dataOutput1.value).toBe(1); + expect(dataOutput1.script.data).toEqual(script1); + + expect(dataOutput2.token_data).toBe(0); + expect(dataOutput2.value).toBe(1); + expect(dataOutput2.script.data).toEqual(script2); + }); + + it('should melt tokens and add data outputs to the transaction at the start of the outputs', async () => { + const response = await TestUtils.request + .post('/wallet/melt-tokens') + .send({ + token: tokenA.uid, + amount: 10, + data: ['foobar'], + unshift_data: false, + }) + .set({ 'x-wallet-id': wallet1.walletId }); + + meltedAmount += 10; + htrMelted -= 1; // we create 1 data outputs and no melted htr + /** + * Status: + * wallet1[0]: 14 HTR , 200 TKA + */ + + expect(response.body.success).toBe(true); + + await TestUtils.waitForTxReceived(wallet1.walletId, response.body.hash); + + const balance1htr = await wallet1.getBalance(); + const balance1tka = await wallet1.getBalance(tokenA.uid); + expect(balance1htr.available).toBe(initialHTR + htrMelted); + expect(balance1tka.available).toBe(totalMinted - meltedAmount); + + const transaction = response.body; + const dataOutput = transaction.outputs[transaction.outputs.length - 1]; + const script = Array.from((new ScriptData('foobar')).createScript()); + + expect(dataOutput.token_data).toBe(0); + expect(dataOutput.value).toBe(1); + expect(dataOutput.script.data).toEqual(script); + }); + it('should melt allowing external authority address', async () => { + // XXX: This test should be the last test since it sends the melt authority to the burn address + if (totalMinted - meltedAmount <= 0) { + // when we reach this step the wallet should have at least 1 token to melt and end the tests + throw new Error('No tokens to melt'); + } const externalAddress = TestUtils.getBurnAddress(); const response = await TestUtils.request .post('/wallet/melt-tokens') @@ -346,7 +480,7 @@ describe('melt tokens', () => { token: tokenA.uid, address: await wallet1.getAddressAt(17), melt_authority_address: externalAddress, - amount: 20 + amount: totalMinted - meltedAmount, }) .set({ 'x-wallet-id': wallet1.walletId }); @@ -359,17 +493,19 @@ describe('melt tokens', () => { address: await wallet1.getAddressAt(17), melt_authority_address: externalAddress, allow_external_melt_authority_address: true, - amount: 20 + amount: totalMinted - meltedAmount, }) .set({ 'x-wallet-id': wallet1.walletId }); + htrMelted += Math.floor((totalMinted - meltedAmount) / 100); + const transaction = response2.body; expect(transaction.success).toBe(true); await TestUtils.waitForTxReceived(wallet1.walletId, response2.body.hash); const balance1htr = await wallet1.getBalance(); const balance1tka = await wallet1.getBalance(tokenA.uid); - expect(balance1htr.available).toBe(9); + expect(balance1htr.available).toBe(initialHTR + htrMelted); expect(balance1tka.available).toBe(0); // Validating a new melt authority was created by default diff --git a/__tests__/integration/mint-tokens.test.js b/__tests__/integration/mint-tokens.test.js index 2b2f2d71..d22923ec 100644 --- a/__tests__/integration/mint-tokens.test.js +++ b/__tests__/integration/mint-tokens.test.js @@ -1,4 +1,4 @@ -import { transactionUtils, constants, network, scriptsUtils } from '@hathor/wallet-lib'; +import { transactionUtils, constants, network, scriptsUtils, ScriptData } from '@hathor/wallet-lib'; import { TestUtils } from './utils/test-utils-integration'; import { WALLET_CONSTANTS } from './configuration/test-constants'; import { WalletHelper } from './utils/wallet-helper'; @@ -18,7 +18,7 @@ describe('mint token', () => { await WalletHelper.startMultipleWalletsForTest([wallet1]); // Creating a token for the tests - await wallet1.injectFunds(12, 0); + await wallet1.injectFunds(20, 0); const tkAtx = await wallet1.createToken({ name: tokenA.name, symbol: tokenA.symbol, @@ -111,13 +111,14 @@ describe('mint token', () => { }); // Insufficient funds + // Current funds: 15 HTR + 500 TKA it('should not mint with insufficient funds', async () => { const response = await TestUtils.request .post('/wallet/mint-tokens') .send({ token: tokenA.uid, - amount: 1000 + amount: 1600 }) .set({ 'x-wallet-id': wallet1.walletId }); @@ -138,6 +139,8 @@ describe('mint token', () => { }) .set({ 'x-wallet-id': wallet1.walletId }); + // Current funds: 14 HTR + 550 TKA + expect(response.body.success).toBe(true); await TestUtils.waitForTxReceived(wallet1.walletId, response.body.hash); @@ -156,6 +159,8 @@ describe('mint token', () => { }) .set({ 'x-wallet-id': wallet1.walletId }); + // Current funds: 13 HTR + 610 TKA + const transaction = response.body; expect(transaction.success).toBe(true); const htrOutputIndex = transaction.outputs.findIndex(o => o.token_data === 0); @@ -182,6 +187,8 @@ describe('mint token', () => { }) .set({ 'x-wallet-id': wallet1.walletId }); + // Current funds: 12 HTR + 680 TKA + expect(response.body.success).toBe(true); await TestUtils.waitForTxReceived(wallet1.walletId, response.body.hash); @@ -209,6 +216,8 @@ describe('mint token', () => { }) .set({ 'x-wallet-id': wallet1.walletId }); + // Current funds: 11 HTR + 760 TKA + const transaction = response.body; expect(transaction.success).toBe(true); const htrOutputIndex = transaction.outputs.findIndex(o => o.token_data === 0); @@ -236,6 +245,8 @@ describe('mint token', () => { }) .set({ 'x-wallet-id': wallet1.walletId }); + // Current funds: 10 HTR + 860 TKA + const transaction = response.body; expect(transaction.success).toBe(true); await TestUtils.waitForTxReceived(wallet1.walletId, response.body.hash); @@ -255,13 +266,75 @@ describe('mint token', () => { expect(p2pkh.address.base58).toEqual(address0); }); + it('should mint and create data outputs', async () => { + const response = await TestUtils.request + .post('/wallet/mint-tokens') + .send({ + token: tokenA.uid, + amount: 100, + data: ['foobar1', 'foobar2'], + }) + .set({ 'x-wallet-id': wallet1.walletId }); + + // Current funds: 7 HTR + 960 TKA + + const transaction = response.body; + expect(transaction.success).toBe(true); + // If unshift_data is not specified, the data output will be the first output + const dataOutput1 = transaction.outputs[1]; + const dataOutput2 = transaction.outputs[0]; + + await TestUtils.waitForTxReceived(wallet1.walletId, response.body.hash); + + const script1 = Array.from((new ScriptData('foobar1')).createScript()); + const script2 = Array.from((new ScriptData('foobar2')).createScript()); + + expect(dataOutput1.token_data).toBe(0); + expect(dataOutput1.value).toBe(1); + expect(dataOutput1.script.data).toEqual(script1); + + expect(dataOutput2.token_data).toBe(0); + expect(dataOutput2.value).toBe(1); + expect(dataOutput2.script.data).toEqual(script2); + const tkaBalance = await wallet1.getBalance(tokenA.uid); + expect(tkaBalance.available).toBe(960); + }); + + it('should mint and create a data output at first position', async () => { + const response = await TestUtils.request + .post('/wallet/mint-tokens') + .send({ + token: tokenA.uid, + amount: 100, + data: ['foobar'], + unshift_data: false, + }) + .set({ 'x-wallet-id': wallet1.walletId }); + + // Current funds: 4 HTR + 1060 TKA + + const transaction = response.body; + expect(transaction.success).toBe(true); + const dataOutput = transaction.outputs[transaction.outputs.length - 1]; + const script = Array.from((new ScriptData('foobar')).createScript()); + + await TestUtils.waitForTxReceived(wallet1.walletId, response.body.hash); + + expect(dataOutput.token_data).toBe(0); + expect(dataOutput.value).toBe(1); + expect(dataOutput.script.data).toEqual(script); + const tkaBalance = await wallet1.getBalance(tokenA.uid); + expect(tkaBalance.available).toBe(1060); + }); + it('should mint allowing external authority address', async () => { + // XXX: This test should be the last test since it sends the mint authority to the burn address const externalAddress = TestUtils.getBurnAddress(); const response = await TestUtils.request .post('/wallet/mint-tokens') .send({ token: tokenA.uid, - address: await wallet1.getAddressAt(17), + address: await wallet1.getAddressAt(20), mint_authority_address: externalAddress, amount: 100 }) @@ -269,11 +342,12 @@ describe('mint token', () => { expect(response.body.success).toBe(false); + // The following request loses the mint authority const response2 = await TestUtils.request .post('/wallet/mint-tokens') .send({ token: tokenA.uid, - address: await wallet1.getAddressAt(17), + address: await wallet1.getAddressAt(20), mint_authority_address: externalAddress, allow_external_mint_authority_address: true, amount: 100 @@ -284,8 +358,8 @@ describe('mint token', () => { expect(transaction.success).toBe(true); await TestUtils.waitForTxReceived(wallet1.walletId, response2.body.hash); - const addr17 = await wallet1.getAddressInfo(17, tokenA.uid); - expect(addr17.total_amount_available).toBe(100); + const addr20 = await wallet1.getAddressInfo(20, tokenA.uid); + expect(addr20.total_amount_available).toBe(100); // Validating a new mint authority was created by default const authorityOutputs = transaction.outputs.filter( diff --git a/__tests__/melt-tokens.test.js b/__tests__/melt-tokens.test.js index 7f613331..06721bf8 100644 --- a/__tests__/melt-tokens.test.js +++ b/__tests__/melt-tokens.test.js @@ -150,4 +150,19 @@ describe('melt-tokens api', () => { expect(response2.status).toBe(200); expect(response2.body.success).toBe(false); }); + + it('should return 200 when sending data parameters', async () => { + const response = await TestUtils.request + .post('/wallet/melt-tokens') + .send({ + token: + '00da712d64e04866c8c9aa8fceca70e80d1693864176b6b443220cf29adab5ed', + amount: 1, + data: ['foobar', 'barfoo'], + unshift_data: true, + }) + .set({ 'x-wallet-id': walletId }); + expect(response.status).toBe(200); + expect(response.body.hash).toBeDefined(); + }); }); diff --git a/__tests__/mint-tokens.test.js b/__tests__/mint-tokens.test.js index ebb2b604..a058a4b9 100644 --- a/__tests__/mint-tokens.test.js +++ b/__tests__/mint-tokens.test.js @@ -73,4 +73,18 @@ describe('mint-tokens api', () => { expect(response2.status).toBe(200); expect(response2.body.success).toBe(false); }); + + it('should return 200 when sending data parameters', async () => { + const response = await TestUtils.request + .post('/wallet/mint-tokens') + .send({ + token: '0000073b972162f70061f61cf0082b7a47263cc1659a05976aca5cd01b3351ee', + amount: 1, + data: ['foobar', 'barfoo'], + unshift_data: true, + }) + .set({ 'x-wallet-id': walletId }); + expect(response.status).toBe(200); + expect(response.body.hash).toBeDefined(); + }); }); diff --git a/package-lock.json b/package-lock.json index f5126909..e1f02ec9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,14 +11,14 @@ "dependencies": { "@dinamonetworks/hsm-dinamo": "4.9.1", "@hathor/healthcheck-lib": "0.1.0", - "@hathor/wallet-lib": "1.4.1", - "axios": "0.21.4", + "@hathor/wallet-lib": "1.7.0", + "axios": "1.6.8", "express": "4.18.2", "express-validator": "6.10.0", - "jsonwebtoken": "^9.0.2", + "jsonwebtoken": "9.0.2", "lodash": "4.17.21", "morgan": "1.10.0", - "uuid4": "^2.0.3", + "uuid4": "2.0.3", "winston": "3.12.0", "yargs": "17.7.2" }, @@ -2168,20 +2168,24 @@ "integrity": "sha512-Oi223+iKye5cmPyMIqp64E/ZP+in0JndN/s9uEigmXxt6wRhwciCPbzSY4S2oicy1uNqhv7lLdyUc3O/P3sCzQ==" }, "node_modules/@hathor/wallet-lib": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@hathor/wallet-lib/-/wallet-lib-1.4.1.tgz", - "integrity": "sha512-uSOHVPr4oTRliSTnPizGRgMlnI9n4wjZVWxOQKsfeO/jJQK80NvY4BAfHtQRuODByC6XDNc7QmJWQ/j2u260uQ==", - "dependencies": { - "axios": "^0.21.4", - "bitcore-lib": "^8.25.10", - "bitcore-mnemonic": "^8.25.10", - "buffer": "^6.0.3", - "crypto-js": "^3.1.9-1", - "isomorphic-ws": "^4.0.1", - "level": "^8.0.0", - "lodash": "^4.17.21", - "long": "^4.0.0", - "ws": "^7.5.9" + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@hathor/wallet-lib/-/wallet-lib-1.7.0.tgz", + "integrity": "sha512-5Qm7Ib27ky510+20h0SO4WVBdSnFxYBilu6NNJoQBbHkqAQNc0yCd7hU01LuvMvLyvk7g1K9gB6r1i7UOWWF+g==", + "dependencies": { + "axios": "1.6.8", + "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.0", + "lodash": "4.17.21", + "long": "4.0.0", + "ws": "8.17.0" + }, + "engines": { + "node": ">=20.0.0", + "npm": ">=10.0.0" } }, "node_modules/@humanwhocodes/config-array": { @@ -3616,8 +3620,7 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/available-typed-arrays": { "version": "1.0.7", @@ -3635,11 +3638,13 @@ } }, "node_modules/axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", "dependencies": { - "follow-redirects": "^1.14.0" + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "node_modules/axios-mock-adapter": { @@ -4019,56 +4024,16 @@ } }, "node_modules/bech32": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", - "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" - }, - "node_modules/bigi": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/bigi/-/bigi-1.4.2.tgz", - "integrity": "sha512-ddkU+dFIuEIW8lE7ZwdIAf2UPoM90eaprg5m3YXAVVTmKlqV/9BX4A2M8BOK2yOq6/VgZFVhK6QAxJebhlbhzw==" - }, - "node_modules/bip-schnorr": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/bip-schnorr/-/bip-schnorr-0.6.4.tgz", - "integrity": "sha512-dNKw7Lea8B0wMIN4OjEmOk/Z5qUGqoPDY0P2QttLqGk1hmDPytLWW8PR5Pb6Vxy6CprcdEgfJpOjUu+ONQveyg==", - "dependencies": { - "bigi": "^1.4.2", - "ecurve": "^1.0.6", - "js-sha256": "^0.9.0", - "randombytes": "^2.1.0", - "safe-buffer": "^5.2.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/bip-schnorr/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.3.tgz", + "integrity": "sha512-yuVFUvrNcoJi0sv5phmqc6P+Fl1HjRDRNOOkHY2X/3LBy2bIGNSFx4fZ95HMaXHupuS7cZR15AsvtmCIF4UEyg==" }, "node_modules/bitcore-lib": { - "version": "8.25.47", - "resolved": "https://registry.npmjs.org/bitcore-lib/-/bitcore-lib-8.25.47.tgz", - "integrity": "sha512-qDZr42HuP4P02I8kMGZUx/vvwuDsz8X3rQxXLfM0BtKzlQBcbSM7ycDkDN99Xc5jzpd4fxNQyyFXOmc6owUsrQ==", + "version": "8.25.10", + "resolved": "https://registry.npmjs.org/bitcore-lib/-/bitcore-lib-8.25.10.tgz", + "integrity": "sha512-MyHpSg7aFRHe359RA/gdkaQAal3NswYZTLEuu0tGX1RGWXAYN9i/24fsjPqVKj+z0ua+gzAT7aQs0KiKXWCgKA==", "dependencies": { - "bech32": "=2.0.0", - "bip-schnorr": "=0.6.4", + "bech32": "=1.1.3", "bn.js": "=4.11.8", "bs58": "^4.0.1", "buffer-compare": "=1.1.1", @@ -4083,11 +4048,11 @@ "integrity": "sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==" }, "node_modules/bitcore-mnemonic": { - "version": "8.25.47", - "resolved": "https://registry.npmjs.org/bitcore-mnemonic/-/bitcore-mnemonic-8.25.47.tgz", - "integrity": "sha512-wTa0imZZpFTqwlpyokvU8CNl+YdaIvQIrWKp/0AEL9gPX2vuzBnE+U8Ok6D5lHCnbG6dvmoesmtyf6R3aYI86A==", + "version": "8.25.10", + "resolved": "https://registry.npmjs.org/bitcore-mnemonic/-/bitcore-mnemonic-8.25.10.tgz", + "integrity": "sha512-FeXxO37BLV5JRvxPmVFB91zRHalavV8H4TdQGt1/hz0AkoPymIV68OkuB+TptpjeYgatcgKPoPvPhglJkTzFQQ==", "dependencies": { - "bitcore-lib": "^8.25.47", + "bitcore-lib": "^8.25.10", "unorm": "^1.4.1" }, "peerDependencies": { @@ -4527,7 +4492,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -4764,9 +4728,9 @@ } }, "node_modules/crypto-js": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz", - "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, "node_modules/data-view-buffer": { "version": "1.0.1", @@ -4893,7 +4857,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -4963,15 +4926,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/ecurve": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/ecurve/-/ecurve-1.0.6.tgz", - "integrity": "sha512-/BzEjNfiSuB7jIWKcS/z8FK9jNjmEWvUV2YZ4RLSmcDtP7Lq0m6FvDuSnJpBlDpGRpfRQeTLGLBI8H+kEv0r+w==", - "dependencies": { - "bigi": "^1.1.0", - "safe-buffer": "^5.0.1" - } - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -4984,9 +4938,9 @@ "dev": true }, "node_modules/elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.5.tgz", + "integrity": "sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw==", "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -6176,7 +6130,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -7063,9 +7016,9 @@ } }, "node_modules/isomorphic-ws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", - "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", + "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", "peerDependencies": { "ws": "*" } @@ -8917,11 +8870,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/js-sha256": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", - "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==" - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -9979,6 +9927,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -10043,14 +9996,6 @@ } ] }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -11458,15 +11403,15 @@ "dev": true }, "node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", + "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", "engines": { - "node": ">=8.3.0" + "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { diff --git a/package.json b/package.json index 228f635e..e8a2bd79 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,8 @@ "dependencies": { "@dinamonetworks/hsm-dinamo": "4.9.1", "@hathor/healthcheck-lib": "0.1.0", - "@hathor/wallet-lib": "1.4.1", - "axios": "0.21.4", + "@hathor/wallet-lib": "1.7.0", + "axios": "1.6.8", "express": "4.18.2", "express-validator": "6.10.0", "jsonwebtoken": "9.0.2", diff --git a/src/api-docs.js b/src/api-docs.js index 50bd0ee9..d9af37be 100644 --- a/src/api-docs.js +++ b/src/api-docs.js @@ -1512,6 +1512,17 @@ const defaultApiDocs = { type: 'boolean', description: 'If the mint authority address is allowed to be from another wallet. Default is false.' }, + unshift_data: { + type: 'boolean', + description: 'Add data outputs at the beginning of the outputs. Default is true.' + }, + data: { + type: 'array', + items: { + type: 'string' + }, + description: 'List of utf-8 encoded strings to create a data output for each.' + }, } }, examples: { @@ -2978,6 +2989,17 @@ const defaultApiDocs = { type: 'boolean', description: 'If the melt authority address is allowed to be from another wallet. Default is false.' }, + unshift_data: { + type: 'boolean', + description: 'Add data outputs at the beginning of the outputs. Default is true.' + }, + data: { + type: 'array', + items: { + type: 'string' + }, + description: 'List of utf-8 encoded strings to create a data output for each.' + }, } }, examples: { diff --git a/src/controllers/wallet/wallet.controller.js b/src/controllers/wallet/wallet.controller.js index d0e04ca6..1b981305 100755 --- a/src/controllers/wallet/wallet.controller.js +++ b/src/controllers/wallet/wallet.controller.js @@ -573,6 +573,8 @@ async function mintTokens(req, res) { const changeAddress = req.body.change_address || null; const mintAuthorityAddress = req.body.mint_authority_address || null; const allowExternalMintAuthorityAddress = req.body.allow_external_mint_authority_address || false; + const unshiftData = req.body.unshift_data; + const data = req.body.data || null; try { if (changeAddress && !await wallet.isAddressMine(changeAddress)) { @@ -585,7 +587,9 @@ async function mintTokens(req, res) { address, changeAddress, mintAuthorityAddress, - allowExternalMintAuthorityAddress + allowExternalMintAuthorityAddress, + unshiftData, + data, } ); res.send({ success: true, ...mapTxReturn(response) }); @@ -615,6 +619,8 @@ async function meltTokens(req, res) { const depositAddress = req.body.deposit_address || null; const meltAuthorityAddress = req.body.melt_authority_address || null; const allowExternalMeltAuthorityAddress = req.body.allow_external_melt_authority_address || false; + const unshiftData = req.body.unshift_data; + const data = req.body.data || null; try { if (changeAddress && !await wallet.isAddressMine(changeAddress)) { @@ -627,7 +633,9 @@ async function meltTokens(req, res) { address: depositAddress, changeAddress, meltAuthorityAddress, - allowExternalMeltAuthorityAddress + allowExternalMeltAuthorityAddress, + unshiftData, + data, } ); res.send({ success: true, ...mapTxReturn(response) }); diff --git a/src/routes/wallet/wallet.routes.js b/src/routes/wallet/wallet.routes.js index cc2325bf..a39d055a 100644 --- a/src/routes/wallet/wallet.routes.js +++ b/src/routes/wallet/wallet.routes.js @@ -366,7 +366,7 @@ walletRouter.post( body('melt_authority_address').isString().optional(), body('allow_external_melt_authority_address').isBoolean().optional().toBoolean(), body('data').isArray().optional(), - body('data.*').isString(), + body('data.*').isString().isLength({ max: MAX_DATA_SCRIPT_LENGTH }), createToken ); @@ -382,6 +382,9 @@ walletRouter.post( body('change_address').isString().optional(), body('mint_authority_address').isString().optional(), body('allow_external_mint_authority_address').isBoolean().optional().toBoolean(), + body('unshift_data').default('true').isBoolean().toBoolean(), + body('data').isArray().optional(), + body('data.*').isString().isLength({ max: MAX_DATA_SCRIPT_LENGTH }), mintTokens ); @@ -397,6 +400,9 @@ walletRouter.post( body('deposit_address').isString().optional(), body('melt_authority_address').isString().optional(), body('allow_external_melt_authority_address').isBoolean().optional().toBoolean(), + body('unshift_data').default('true').isBoolean().toBoolean(), + body('data').isArray().optional(), + body('data.*').isString().isLength({ max: MAX_DATA_SCRIPT_LENGTH }), meltTokens );