Skip to content

Commit

Permalink
feat: add mint-tokens endpoint for p2sh wallet
Browse files Browse the repository at this point in the history
  • Loading branch information
alexruzenhack committed Jun 22, 2023
1 parent 3647d70 commit 0c8fc2e
Show file tree
Hide file tree
Showing 5 changed files with 280 additions and 7 deletions.
17 changes: 15 additions & 2 deletions __tests__/__fixtures__/http-fixtures.js
Original file line number Diff line number Diff line change
Expand Up @@ -696,7 +696,20 @@ export default {
address: 'WewDeXWyvHP7jJTs7tjLoQfoB72LLxJQqN',
timelock: 32522094000,
},
token: '03',
token: '0000073b972162f70061f61cf0082b7a47263cc1659a05976aca5cd01b3351ee',
spent_by: null,
selected_as_input: false,
},
{
value: 1,
token_data: 129,
script: 'qRTqJUJmzEmBNvhkmDuZ4JxcMh5/ioc=',
decoded: {
type: 'P2SH',
address: 'wgyUgNjqZ18uYr4YfE2ALW6tP5hd8MumH5',
timelock: null
},
token: '0000073b972162f70061f61cf0082b7a47263cc1659a05976aca5cd01b3351ee',
spent_by: null,
selected_as_input: false,
},
Expand All @@ -720,7 +733,7 @@ export default {
'00e161a6b0bee1781ea9300680913fb76fd0fac4acab527cd9626cc1514abdc9',
],
height: 19,
tokens: ['03']
tokens: ['0000073b972162f70061f61cf0082b7a47263cc1659a05976aca5cd01b3351ee']
},
{
tx_id:
Expand Down
8 changes: 4 additions & 4 deletions __tests__/mint-tokens.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe('mint-tokens api', () => {
const response = await TestUtils.request
.post('/wallet/mint-tokens')
.send({
token: '03',
token: '0000073b972162f70061f61cf0082b7a47263cc1659a05976aca5cd01b3351ee',
amount: 1,
})
.set({ 'x-wallet-id': walletId });
Expand All @@ -27,7 +27,7 @@ describe('mint-tokens api', () => {
const response = await TestUtils.request
.post('/wallet/mint-tokens')
.send({
token: '03',
token: '0000073b972162f70061f61cf0082b7a47263cc1659a05976aca5cd01b3351ee',
amount: '1',
})
.set({ 'x-wallet-id': walletId });
Expand All @@ -38,7 +38,7 @@ describe('mint-tokens api', () => {
it('should not mint a token without the required parameters', async () => {
['token', 'amount'].forEach(async field => {
const token = {
token: '03',
token: '0000073b972162f70061f61cf0082b7a47263cc1659a05976aca5cd01b3351ee',
amount: 1,
};
delete token[field];
Expand All @@ -55,7 +55,7 @@ describe('mint-tokens api', () => {
const promise1 = TestUtils.request
.post('/wallet/mint-tokens')
.send({
token: '03',
token: '0000073b972162f70061f61cf0082b7a47263cc1659a05976aca5cd01b3351ee',
amount: 1,
})
.set({ 'x-wallet-id': walletId });
Expand Down
210 changes: 210 additions & 0 deletions __tests__/p2sh/tx-proposal-mint-tokens.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import hathorLib from '@hathor/wallet-lib';
import TestUtils from '../test-utils';
import { TOKEN_DATA, AUTHORITY_VALUE } from '../integration/configuration/test-constants';

const walletId = 'stub_mint_tokens';

describe('mint-tokens tx-proposal api', () => {
beforeAll(async () => {
global.config.multisig = TestUtils.multisigData;
await TestUtils.startWallet(
{
walletId,
multisig: true,
preCalculatedAddresses: TestUtils.multisigAddresses
}
);
});

afterAll(async () => {
global.config.multisig = {};
await TestUtils.stopWallet({ walletId });
});

it('should return 200 with a valid body', async () => {
const response = await TestUtils.request
.post('/wallet/p2sh/tx-proposal/mint-tokens')
.send({
token: '0000073b972162f70061f61cf0082b7a47263cc1659a05976aca5cd01b3351ee',
amount: 1,
})
.set({ 'x-wallet-id': walletId });
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.txHex).toBeDefined();
const tx = hathorLib.helpersUtils
.createTxFromHex(response.body.txHex, new hathorLib.Network('testnet'));
expect(tx.outputs.map(o => o.decodedScript.address.base58))
.toEqual(expect.arrayContaining(['wbe2eJdyZVimA7nJjmBQnKYJSXmpnpMKgG']));
});

it('should not accept mint token with empty token', async () => {
const response = await TestUtils.request
.post('/wallet/p2sh/tx-proposal/mint-tokens')
.send({
token: '',
amount: 1,
})
.set({ 'x-wallet-id': walletId });
expect(response.status).toBe(400);
expect(response.body.success).toBe(false);
});

it('should not accept mint token with amount 0', async () => {
const response = await TestUtils.request
.post('/wallet/p2sh/tx-proposal/mint-tokens')
.send({
token: '0000073b972162f70061f61cf0082b7a47263cc1659a05976aca5cd01b3351ee',
amount: 0,
})
.set({ 'x-wallet-id': walletId });
expect(response.status).toBe(400);
expect(response.body.success).toBe(false);
});

it('should not accept mint token without funds to cover it', async () => {
const response = await TestUtils.request
.post('/wallet/p2sh/tx-proposal/mint-tokens')
.send({
token: '0000073b972162f70061f61cf0082b7a47263cc1659a05976aca5cd01b3351ee',
amount: 1_000_000_000_000, // 1 trillion
})
.set({ 'x-wallet-id': walletId });
expect(response.status).toBe(200);
expect(response.body.success).toBe(false);
});

it('should return 200 with a valid body selecting address', async () => {
const address = TestUtils.multisigAddresses[2];
const response = await TestUtils.request
.post('/wallet/p2sh/tx-proposal/mint-tokens')
.send({
token: '0000073b972162f70061f61cf0082b7a47263cc1659a05976aca5cd01b3351ee',
amount: 1,
address,
})
.set({ 'x-wallet-id': walletId });
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.txHex).toBeDefined();
const tx = hathorLib.helpersUtils.createTxFromHex(response.body.txHex, new hathorLib.Network('testnet'));
expect(tx.outputs.map(o => o.decodedScript.address.base58))
.toEqual(expect.arrayContaining(['wbe2eJdyZVimA7nJjmBQnKYJSXmpnpMKgG', address]));
});

it('should not accept mint token with empty address', async () => {
const response = await TestUtils.request
.post('/wallet/p2sh/tx-proposal/mint-tokens')
.send({
token: '0000073b972162f70061f61cf0082b7a47263cc1659a05976aca5cd01b3351ee',
amount: 1,
address: '',
})
.set({ 'x-wallet-id': walletId });
expect(response.status).toBe(400);
expect(response.body.success).toBe(false);
});

it('should return 200 with a valid body selecting change address', async () => {
const changeAddress = TestUtils.multisigAddresses[3];
const response = await TestUtils.request
.post('/wallet/p2sh/tx-proposal/mint-tokens')
.send({
token: '0000073b972162f70061f61cf0082b7a47263cc1659a05976aca5cd01b3351ee',
amount: 1,
change_address: changeAddress,
})
.set({ 'x-wallet-id': walletId });
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.txHex).toBeDefined();
const tx = hathorLib.helpersUtils.createTxFromHex(response.body.txHex, new hathorLib.Network('testnet'));
expect(tx.outputs.map(o => o.decodedScript.address.base58))
.toEqual(expect.arrayContaining(['wbe2eJdyZVimA7nJjmBQnKYJSXmpnpMKgG', changeAddress]));
});

it('should not accept mint token with empty change address', async () => {
const response = await TestUtils.request
.post('/wallet/p2sh/tx-proposal/mint-tokens')
.send({
token: '0000073b972162f70061f61cf0082b7a47263cc1659a05976aca5cd01b3351ee',
amount: 1,
change_address: '',
})
.set({ 'x-wallet-id': walletId });
expect(response.status).toBe(400);
expect(response.body.success).toBe(false);
});

it('should not accept mint token with a change address that does not belong to the wallet', async () => {
const changeAddress = TestUtils.addresses[0];
const response = await TestUtils.request
.post('/wallet/p2sh/tx-proposal/mint-tokens')
.send({
token: '0000073b972162f70061f61cf0082b7a47263cc1659a05976aca5cd01b3351ee',
amount: 1,
change_address: changeAddress,
})
.set({ 'x-wallet-id': walletId });
expect(response.status).toBe(200);
expect(response.body.success).toBe(false);
});

it('should return 200 with a valid body selecting an authority address', async () => {
const mintAuthorityAddress = TestUtils.multisigAddresses[2];
const response = await TestUtils.request
.post('/wallet/p2sh/tx-proposal/mint-tokens')
.send({
token: '0000073b972162f70061f61cf0082b7a47263cc1659a05976aca5cd01b3351ee',
amount: 1,
mint_authority_address: mintAuthorityAddress,
})
.set({ 'x-wallet-id': walletId });
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.txHex).toBeDefined();
const tx = hathorLib.helpersUtils.createTxFromHex(response.body.txHex, new hathorLib.Network('testnet'));
expect(tx.outputs.map(o => o.decodedScript.address.base58))
.toEqual(expect.arrayContaining(['wbe2eJdyZVimA7nJjmBQnKYJSXmpnpMKgG', mintAuthorityAddress]));
expect(tx.outputs).toHaveLength(3);
const authorityOutputs = tx.outputs.filter(o => TOKEN_DATA.isAuthorityToken(o.tokenData));
expect(authorityOutputs).toHaveLength(1);
expect(authorityOutputs[0].value).toBe(AUTHORITY_VALUE.MINT);
});

it('should not accept mint token with empty mint authority address', async () => {
const response = await TestUtils.request
.post('/wallet/p2sh/tx-proposal/mint-tokens')
.send({
token: '0000073b972162f70061f61cf0082b7a47263cc1659a05976aca5cd01b3351ee',
amount: 1,
mint_authority_address: '',
})
.set({ 'x-wallet-id': walletId });
expect(response.status).toBe(400);
expect(response.body.success).toBe(false);
});

it('should return 200 with a valid body selecting an external authority address', async () => {
const mintAuthorityAddress = TestUtils.addresses[1];
const response = await TestUtils.request
.post('/wallet/p2sh/tx-proposal/mint-tokens')
.send({
token: '0000073b972162f70061f61cf0082b7a47263cc1659a05976aca5cd01b3351ee',
amount: 1,
mint_authority_address: mintAuthorityAddress,
allow_external_mint_authority_address: true,
})
.set({ 'x-wallet-id': walletId });
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.txHex).toBeDefined();
const tx = hathorLib.helpersUtils.createTxFromHex(response.body.txHex, new hathorLib.Network('testnet'));
expect(tx.outputs.map(o => o.decodedScript.address.base58))
.toEqual(expect.arrayContaining(['wbe2eJdyZVimA7nJjmBQnKYJSXmpnpMKgG', mintAuthorityAddress]));
expect(tx.outputs).toHaveLength(3);
const authorityOutputs = tx.outputs.filter(o => TOKEN_DATA.isAuthorityToken(o.tokenData));
expect(authorityOutputs).toHaveLength(1);
expect(authorityOutputs[0].value).toBe(AUTHORITY_VALUE.MINT);
});
});
38 changes: 38 additions & 0 deletions src/controllers/wallet/p2sh/tx-proposal.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,43 @@ async function buildTxProposal(req, res) {
}
}

async function buildMintTokensTxProposal(req, res) {
const validationResult = parametersValidation(req);
if (!validationResult.success) {
res.status(400).json(validationResult);
return;
}

const {
token,
amount,
} = req.body;
const address = req.body.address || null;
const changeAddress = req.body.change_address || null;
const mintAuthorityAddress = req.body.mint_authority_address || null;
const allowExternalMintAuthorityAddress = req.body.allow_external_mint_authority_address || null;

try {
if (changeAddress && !await req.wallet.isAddressMine(changeAddress)) {
throw new Error('Change address is not from this wallet');
}

const mintTokenTransaction = await req.wallet.prepareMintTokensData(
token,
amount,
{
address,
changeAddress,
mintAuthorityAddress,
allowExternalMintAuthorityAddress,
}
);
res.send({ success: true, txHex: mintTokenTransaction.toHex() });
} catch (err) {
res.send({ success: false, error: err.message });
}
}

async function getMySignatures(req, res) {
const validationResult = parametersValidation(req);
if (!validationResult.success) {
Expand Down Expand Up @@ -144,6 +181,7 @@ async function signAndPush(req, res) {

module.exports = {
buildTxProposal,
buildMintTokensTxProposal,
getMySignatures,
signTx,
signAndPush,
Expand Down
14 changes: 13 additions & 1 deletion src/routes/wallet/p2sh/tx-proposal.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
*/

const { Router } = require('express');
const { checkSchema } = require('express-validator');
const { checkSchema, body } = require('express-validator');
const {
buildTxProposal,
getMySignatures,
buildMintTokensTxProposal,
signTx,
signAndPush,
} = require('../../../controllers/wallet/p2sh/tx-proposal.controller');
Expand Down Expand Up @@ -81,6 +82,17 @@ txProposalRouter.post(
buildTxProposal,
);

txProposalRouter.post(
'/mint-tokens',
body('token').isString().notEmpty(),
body('amount').isInt({ min: 1 }).toInt(),
body('address').isString().notEmpty().optional(),
body('change_address').isString().notEmpty().optional(),
body('mint_authority_address').isString().notEmpty().optional(),
body('allow_external_mint_authority_address').isBoolean().optional().toBoolean(),
buildMintTokensTxProposal,
);

/*
* XXX: Currently only works for P2SH MultiSig signatures, but can be enhanced to
* include P2PKH Signatures once the wallet-lib adds support.
Expand Down

0 comments on commit 0c8fc2e

Please sign in to comment.