diff --git a/tests/address-tests.js b/tests/address-tests.js index a3188a78..d0171412 100644 --- a/tests/address-tests.js +++ b/tests/address-tests.js @@ -3,49 +3,45 @@ const { expect } = require('chai') const { toHex, getApplication, removeMasterNode } = require('./helpers/common'); const { TEST_DATA } = require('./helpers/data'); const { mergePagedScreens } = require("./helpers/screen"); -const { AuthTokenFlows } = require('./helpers/flow'); +const { authTokenFlows } = require('./helpers/flow'); describe("Address Tests", function () { context("Address Commands", function () { - new AuthTokenFlows("can derive address", () => { return { address: TEST_DATA.address0 }; }).do( - function () { - return this.test.device.deriveAddress(this.address.path.toString()); - }, - function (derivedAddress) { - const deriveAddressFlow = [ - { header: null, body: 'Confirm Send Address' }, - { header: 'Path', body: removeMasterNode(this.address.path.toString()) }, - { header: null, body: 'Approve' }, - { header: null, body: 'Reject' } - ]; - if (this.auth) { - deriveAddressFlow.splice(2, 0, { header: 'Application', body: getApplication(this.test.device) }); + authTokenFlows("can derive address") + .init(async ({test, auth}) => { + const address = TEST_DATA.address0; + const flow = [{ header: null, body: 'Confirm Send Address' }, + { header: 'Path', body: removeMasterNode(address.path.toString()) }]; + if (auth) { + flow.push({ header: 'Application', body: getApplication(test.device) }) } - expect(this.flows[0]).to.be.deep.equal(deriveAddressFlow); - expect(derivedAddress).to.be.deep.equal({ - addressHex: toHex(this.address.toBytes()) + flow.push({ header: null, body: 'Approve' }, { header: null, body: 'Reject' }); + return { address, flow, flowsCount: 1 }; + }) + .shouldSucceed(({flow, flows, address}, derived) => { + expect(flows[0]).to.be.deep.equal(flow); + expect(derived).to.be.deep.equal({ + addressHex: toHex(address.toBytes()) }); - } - ); + }) + .run(({test, address}) => test.device.deriveAddress(address.path.toString())); - new AuthTokenFlows("can show address", () => { return { address: TEST_DATA.address0 }; }).do( - function () { - return this.test.device.showAddress(this.address.path.toString()); - }, - function (show) { - const addressFlow = [ - { header: null, body: 'Confirm Address' }, - { header: 'Path', body: removeMasterNode(this.address.path.toString()) }, - { header: 'Address', body: this.address.toBase58() }, - { header: null, body: 'Approve' }, - { header: null, body: 'Reject' } - ]; - if (this.auth) { - addressFlow.splice(3, 0, { header: 'Application', body: getApplication(this.test.device) }); + authTokenFlows("can show address") + .init(async ({test, auth}) => { + const address = TEST_DATA.address0; + const flow = [{ header: null, body: 'Confirm Address' }, + { header: 'Path', body: removeMasterNode(address.path.toString()) }, + { header: 'Address', body: address.toBase58() }]; + if (auth) { + flow.push({ header: 'Application', body: getApplication(test.device) }) } - expect(mergePagedScreens(this.flows[0])).to.be.deep.equal(addressFlow); + flow.push({ header: null, body: 'Approve' }, { header: null, body: 'Reject' }); + return { address, flow, flowsCount: 1 }; + }) + .shouldSucceed(({flow, flows}, show) => { + expect(mergePagedScreens(flows[0])).to.be.deep.equal(flow); expect(show).to.be.true; - } - ); + }) + .run(({test, address}) => test.device.showAddress(address.path.toString())); }); }); diff --git a/tests/helpers/data.js b/tests/helpers/data.js index 398a84e1..a6b71dcc 100644 --- a/tests/helpers/data.js +++ b/tests/helpers/data.js @@ -13,7 +13,8 @@ class ExtendedAddress { this.network = network; this.address = address; this.path = DerivationPath.new(path[0], [path[1]]); - this.index = path[1]; + this.acc_index = path[0]; + this.addr_index = path[1]; } toBase58() { diff --git a/tests/helpers/flow.js b/tests/helpers/flow.js index e9c75c88..3311fd6c 100644 --- a/tests/helpers/flow.js +++ b/tests/helpers/flow.js @@ -14,55 +14,83 @@ function restorePromiseError(promise) { } class AuthTokenFlows { - constructor(name, before, count = [1, 1]) { - this.name = name; - this.before = before; - this.count = count; + constructor(name) { + this._name = name; + this._before = null; + this.shouldSucceed(null); + this.shouldFail(null); } - do(action, success, failure) { - const count = this.count; - const before = this.before; + init(before) { + this._before = before; + return this; + } - success = success ?? (function (result) { + shouldSucceed(success) { + this._success = success ?? (function (_, result) { throw new Error(`Success called: ${JSON.stringify(result)}`); }); - failure = failure ?? (function (error) { + return this; + } + + shouldFail(failure) { + this._failure = failure ?? (function (_, error) { throw new Error(`Failure called: ${error}`); }); + return this; + } - const run = auth => { - it(`${this.name}${auth ? ' (with auth token)' : ''}`, async function () { - this.timeout(30_000); - const params = before(); - Object.assign(params, { test: this, auth }); - this.device.useAuthToken(auth); - this.screens.removeCurrentScreen(); // Wait for new screen in the readFlow - const promise = suppressPomiseError(action.call(params)); - const flows = []; - for (let i = 0; i < count[auth]; i++) { - let flow = await this.screens.readFlow(); - flows.push(mergePagedScreens(flow)); - await this.screens.clickOn('Approve'); - if (i != count[auth] - 1 && await this.screens.isReadyMainScreen()) { // we have more flows - this.screens.removeCurrentScreen(); // Wait for new screen in the readFlow - } - } - Object.assign(params, { flows }); - let result; - try { - result = await restorePromiseError(promise); - } catch (error) { - failure.call(params, error); - return; - } - success.call(params, result); - }); - } + run(action) { + this.__run(false, action); + this.__run(true, action); + } + + __run(auth, action) { + const before = this._before; + const success = this._success; + const failure = this._failure; - run(0); - run(1); + it(`${this._name}${auth ? ' (with auth token)' : ''}`, async function() { + this.timeout(30_000); + this.device.useAuthToken(auth); + const params = { test: this, auth }; + if (before) { + Object.assign(params, await before(params)); + } + const flowsCount = params.flowsCount; + if (typeof flowsCount !== "number") { + throw new Error("flowsCount should be defined and to be a number"); + } + this.screens.removeCurrentScreen(); // Wait for new screen in the readFlow + // Call action + const promise = suppressPomiseError(action(params)); + // Read flows + const flows = []; + for (let i = 0; i < flowsCount; i++) { + let flow = await this.screens.readFlow(); + flows.push(mergePagedScreens(flow)); + await this.screens.clickOn('Approve'); + if (i != flowsCount - 1 && await this.screens.isReadyMainScreen()) { // we have more flows + this.screens.removeCurrentScreen(); // Wait for new screen in the readFlow + } + } + params.flows = flows; + // Call success or error + let result; + try { + result = await restorePromiseError(promise); + } catch (error) { + failure(params, error); + return; + } + success(params, result); + }); + return null; } } +exports.authTokenFlows = function(name) { + return new AuthTokenFlows(name); +}; + exports.AuthTokenFlows = AuthTokenFlows; diff --git a/tests/helpers/transaction.js b/tests/helpers/transaction.js index 93b6f163..e6afe809 100644 --- a/tests/helpers/transaction.js +++ b/tests/helpers/transaction.js @@ -3,9 +3,9 @@ const Buffer = require('buffer').Buffer; const common = require('./common'); const { TEST_DATA } = require('./data'); -function createErgoBox(recipient, txId, index, tokens = new ergo.Tokens()) { +function createErgoBox(recipient, txId, index, amount, tokens) { const ergoBox = new ergo.ErgoBox( - ergo.BoxValue.from_i64(ergo.I64.from_str('1000000000')), + ergo.BoxValue.from_i64(ergo.I64.from_str(amount)), 0, ergo.Contract.pay_to_address(recipient), txId, @@ -40,6 +40,11 @@ function toToken(token) { }; } +function isTokensEqual(t1, t2) { + return t1.id().to_str() === t2.id().to_str() && + t1.amount().as_i64().to_str() === t2.amount().as_i64().to_str(); +} + function toBoxCandidate(output) { return { value: output.value().as_i64().to_str(), @@ -50,128 +55,90 @@ function toBoxCandidate(output) { }; } -class ErgoUnsignedTransactionBuilder { +class ErgoTxBuilder { constructor() { this.amount = ergo.I64.from_str('0'); - this.inputs = ergo.ErgoBoxes.empty(); - this.dataInputs = new ergo.DataInputs(); - this.outputs = ergo.ErgoBoxCandidates.empty(); - this.feeAmount = ergo.TxBuilder.SUGGESTED_TX_FEE(); - this.changeAddress = null; - } - - input(ergoBox) { - this.inputs.add(ergoBox); - return this; - } - - dataInput(ergoBox) { - this.dataInputs.add(new ergo.DataInput(ergoBox.box_id())); - return this; - } - - output(boxCandidate) { - this.amount = this.amount.checked_add(boxCandidate.value().as_i64()); - this.outputs.add(boxCandidate); - return this; - } - - fee(amount) { - this.feeAmount = amount; - } - - change(address) { - this.changeAddress = address; - return this; - } - - build() { - const targetBalance = ergo.BoxValue.from_i64(this.amount.checked_add(this.feeAmount.as_i64())); - const targetTokens = new ergo.Tokens(); - common.toArray(this.outputs) - .flatMap(output => common.toArray(output.tokens())) - .forEach(token => targetTokens.add(token)); - const boxSelection = new ergo.SimpleBoxSelector().select(this.inputs, targetBalance, targetTokens); - const txBuilder = ergo.TxBuilder.new( - boxSelection, - this.outputs, - 0, - this.feeAmount, - this.changeAddress - ); - txBuilder.set_data_inputs(this.dataInputs); - return txBuilder.build(); - } -} - -class UnsignedTransactionBuilder { - constructor() { - this.network = TEST_DATA.network; - this.ergoBuilder = new ErgoUnsignedTransactionBuilder(); this.inputs = []; this.dataInputs = []; this.outputs = []; - this.distinctTokenIds = []; + this.feeAmount = ergo.TxBuilder.SUGGESTED_TX_FEE(); + this.burningTokens = []; + this.mintingOutputs = []; + this.changeAddress = null; this.changeMap = null; - this.ergoTransaction = null; } - inputFrom(ergoBox, path) { - const contextExtension = new ergo.ContextExtension(); - const unsignedBox = toUnsignedBox(ergoBox, contextExtension, path.toString()); - this.inputs.push(unsignedBox); - this.ergoBuilder.input(ergoBox); - return this; + input(extendedAddress, txId, index, amount, tokens) { + const ergoTokens = new ergo.Tokens(); + if (tokens) { + tokens.forEach(t => { + const token = new ergo.Token( + ergo.TokenId.from_str(t.id), + ergo.TokenAmount.from_i64(ergo.I64.from_str(t.amount)) + ); + ergoTokens.add(token); + }); + } + const eTxId = ergo.TxId.from_str(txId); + const ergoBox = createErgoBox(extendedAddress.address, eTxId, index, amount, ergoTokens); + return this.boxInput(ergoBox, extendedAddress.path); } - input(extendedAddress, txId, index, tokens = new ergo.Tokens()) { - const ergoBox = createErgoBox(extendedAddress.address, txId, index, tokens); + boxInput(ergoBox, path) { const contextExtension = new ergo.ContextExtension(); - const unsignedBox = toUnsignedBox(ergoBox, contextExtension, extendedAddress.path.toString()); - this.inputs.push(unsignedBox); - this.ergoBuilder.input(ergoBox); + const box = toUnsignedBox(ergoBox, contextExtension, path.toString()); + this.inputs.push({box, ergo: ergoBox}); return this; } dataInput(address, txId, index) { - const ergoBox = createErgoBox(address, txId, index); - const dataInput = ergoBox.box_id().to_str(); - this.dataInputs.push(dataInput); - this.ergoBuilder.dataInput(ergoBox); + const eTxId = ergo.TxId.from_str(txId); + const ergoBox = createErgoBox( + address, eTxId, index, '100000000', new ergo.Tokens() + ); + this.dataInputs.push(new ergo.DataInput(ergoBox.box_id())); return this; } - output(value, address, tokens = new ergo.Tokens()) { + output(address, value, send_tokens, mint_token_amount) { const builder = new ergo.ErgoBoxCandidateBuilder( ergo.BoxValue.from_i64(ergo.I64.from_str(value)), ergo.Contract.pay_to_address(address), 0 ); - common.toArray(tokens) - .forEach(token => builder.add_token(token.id(), token.amount())); + if (send_tokens) { + send_tokens.forEach(t => { + builder.add_token( + ergo.TokenId.from_str(t.id), + ergo.TokenAmount.from_i64(ergo.I64.from_str(t.amount)) + ); + }); + } + if (mint_token_amount) { + const id = ergo.TokenId.from_str(this.inputs[0].ergo.box_id().to_str()); + const amount = ergo.TokenAmount.from_i64(ergo.I64.from_str(mint_token_amount)); + builder.add_token(id, amount); + this.mintingOutputs.push(new ergo.Token(id, amount)); + } else { + this.mintingOutputs.push(null); + } const output = builder.build(); - const boxCandidate = toBoxCandidate(output); - this.outputs.push(boxCandidate); - this.ergoBuilder.output(output); + this.amount = this.amount.checked_add(output.value().as_i64()); + this.outputs.push(output); return this; } - fee(value) { - const amount = ergo.BoxValue.from_i64(ergo.I64.from_str(value)); - const builder = new ergo.ErgoBoxCandidateBuilder( - amount, - ergo.Contract.pay_to_address(common.getMinerAddress(this.network)), - 0 - ); - const output = builder.build(); - const boxCandidate = toBoxCandidate(output); - this.outputs.push(boxCandidate); - this.ergoBuilder.fee(amount); + burn(tokens) { + const ergoTokens = tokens.map(t => new ergo.Token( + ergo.TokenId.from_str(t.id), + ergo.TokenAmount.from_i64(ergo.I64.from_str(t.amount)) + )); + this.burningTokens.push(...ergoTokens); return this; } - tokenIds(tokenIds) { - this.distinctTokenIds = tokenIds; + fee(amount) { + this.feeAmount = amount ? ergo.BoxValue.from_i64(ergo.I64.from_str(amount)) : null; return this; } @@ -180,37 +147,120 @@ class UnsignedTransactionBuilder { address: extendedAddress.toBase58(), path: extendedAddress.path.toString(), }; - const sum = array => array - .map(e => ergo.I64.from_str(e.value)) - .reduce((a, b) => a.checked_add(b), ergo.I64.from_str('0')); - const amount = sum(this.inputs).as_num() - sum(this.outputs).as_num(); - const builder = new ergo.ErgoBoxCandidateBuilder( - ergo.BoxValue.from_i64(ergo.I64.from_str(amount.toString())), - ergo.Contract.pay_to_address(extendedAddress.address), - 0 - ); - const output = builder.build(); - const boxCandidate = toBoxCandidate(output); - this.outputs.push(boxCandidate); - this.ergoBuilder.change(extendedAddress.address); + this.changeAddress = extendedAddress.address; return this; } - build(buildErgo = true) { - if (buildErgo) { - this.ergoTransaction = this.ergoBuilder.build(); - this.dataInputs = common.toArray(this.ergoTransaction.data_inputs()).map(toDataInput); - this.outputs = common.toArray(this.ergoTransaction.output_candidates()).map(toBoxCandidate); - this.distinctTokenIds = this.ergoTransaction.distinct_token_ids(); + build() { + const targetBalance = ergo.BoxValue.from_i64(this.amount.checked_add(this.feeAmount.as_i64())); + + const targetTokens = new ergo.Tokens(); + this.outputs.flatMap((output, idx) => { + const minting = this.mintingOutputs[idx]; + return common.toArray(output.tokens()).filter( + token => minting ? !isTokensEqual(token, minting) : true + ) + }).forEach(token => targetTokens.add(token)); + this.burningTokens.forEach(token => targetTokens.add(token)); + + const inputs = ergo.ErgoBoxes.empty(); + this.inputs.forEach(input => inputs.add(input.ergo)); + + const outputs = ergo.ErgoBoxCandidates.empty(); + this.outputs.forEach(output => outputs.add(output)); + + const dataInputs = new ergo.DataInputs(); + this.dataInputs.forEach(input => dataInputs.add(input)); + + const boxSelection = new ergo.SimpleBoxSelector().select(inputs, targetBalance, targetTokens); + + const txBuilder = ergo.TxBuilder.new( + boxSelection, + outputs, + 0, + this.feeAmount, + this.changeAddress + ); + if (this.burningTokens.length > 0) { + const burn = new ergo.Tokens(); + this.burningTokens.forEach(token => burn.add(token)); + txBuilder.set_token_burn_permit(burn); + } + txBuilder.set_data_inputs(dataInputs); + const ergoTx = txBuilder.build(); + + const txInputs = common.toArray(ergoTx.inputs()).map( + txinp => this.inputs.find( + i => txinp.box_id().to_str() === i.ergo.box_id().to_str() + ) + ); + + const appTx = { + inputs: txInputs.map(i => i.box), + dataInputs: common.toArray(ergoTx.data_inputs()).map(toDataInput), + outputs: common.toArray(ergoTx.output_candidates()).map(toBoxCandidate), + distinctTokenIds: ergoTx.distinct_token_ids(), + changeMap: this.changeMap, + }; + return { appTx, ergoTx, uInputs: txInputs.map(i => i.ergo) }; + } +} + +class TxBuilder extends ErgoTxBuilder { + constructor(network = TEST_DATA.network) { + super() + this.network = network + } + + buildAppTx() { + const inputs = this.inputs.map(i => i.box); + const outputs = this.outputs.map(toBoxCandidate); + + // add fee output + if (this.feeAmount) { + const feeBuilder = new ergo.ErgoBoxCandidateBuilder( + this.feeAmount, + ergo.Contract.pay_to_address(common.getMinerAddress(this.network)), + 0 + ); + outputs.push(toBoxCandidate(feeBuilder.build())); + } + + // Calculate chage + if (this.changeAddress) { + const sum = array => array + .map(e => ergo.I64.from_str(e.value)) + .reduce((a, b) => a.checked_add(b), ergo.I64.from_str('0')); + const amount = sum(inputs).as_num() - sum(outputs).as_num(); + + const changeBuilder = new ergo.ErgoBoxCandidateBuilder( + ergo.BoxValue.from_i64(ergo.I64.from_str(amount.toString())), + ergo.Contract.pay_to_address(this.changeAddress.address), + 0 + ); + outputs.push(toBoxCandidate(changeBuilder.build())); } + + let distinctTokenIds = [] + outputs.forEach(output => { + output.tokens.forEach(({id}) => { + if (!distinctTokenIds.includes(id)) { + distinctTokenIds.push(id); + } + }); + }); + distinctTokenIds = distinctTokenIds.map( + id => ergo.TokenId.from_str(id).as_bytes() + ); + return { - inputs: this.inputs, - dataInputs: this.dataInputs, - outputs: this.outputs, - distinctTokenIds: this.distinctTokenIds, + inputs: inputs, + dataInputs: this.dataInputs.map(toDataInput), + outputs: outputs, changeMap: this.changeMap, + distinctTokenIds: distinctTokenIds }; } } -exports.UnsignedTransactionBuilder = UnsignedTransactionBuilder; +exports.TxBuilder = TxBuilder; \ No newline at end of file diff --git a/tests/package.json b/tests/package.json index 2015f14a..096e40c9 100644 --- a/tests/package.json +++ b/tests/package.json @@ -15,7 +15,7 @@ "dependencies": { "@ledgerhq/hw-transport-node-hid": "6.28.5", "@ledgerhq/hw-transport-node-speculos-http": "6.28.5", - "ergo-lib-wasm-nodejs": "v0.21.1", + "ergo-lib-wasm-nodejs": "v0.26.0", "ledger-ergo-js": "^0.1.14" } } diff --git a/tests/public-key-tests.js b/tests/public-key-tests.js index 653714fd..7b7abf86 100644 --- a/tests/public-key-tests.js +++ b/tests/public-key-tests.js @@ -2,30 +2,28 @@ const { expect } = require('chai') .use(require('chai-bytes')); const { toHex, getApplication, removeMasterNode } = require('./helpers/common'); const { TEST_DATA } = require('./helpers/data'); -const { AuthTokenFlows } = require('./helpers/flow'); +const { authTokenFlows } = require('./helpers/flow'); describe("Public Key Tests", function () { context("Public Key Commands", function () { - new AuthTokenFlows("can get extended public key", () => { return { account: TEST_DATA.account }; }).do( - function () { - return this.test.device.getExtendedPublicKey(this.account.path.toString()) - }, - function (extendedPublicKey) { - const getExtendedPublicKeyFlow = [ - { header: null, body: 'Ext PubKey Export' }, - { header: 'Path', body: removeMasterNode(this.account.path.toString()) }, - { header: null, body: 'Approve' }, - { header: null, body: 'Reject' } - ]; - if (this.auth) { - getExtendedPublicKeyFlow.splice(2, 0, { header: 'Application', body: getApplication(this.test.device) }); + authTokenFlows("can get extended public key") + .init(async ({test, auth}) => { + const account = TEST_DATA.account; + const flow = [{ header: null, body: 'Ext PubKey Export' }, + { header: 'Path', body: removeMasterNode(account.path.toString()) }]; + if (auth) { + flow.push({ header: 'Application', body: getApplication(test.device) }); } - expect(this.flows[0]).to.be.deep.equal(getExtendedPublicKeyFlow); - expect(extendedPublicKey).to.be.deep.equal({ - publicKey: toHex(this.account.publicKey.pub_key_bytes()), - chainCode: toHex(this.account.publicKey.chain_code()), + flow.push({ header: null, body: 'Approve' }, { header: null, body: 'Reject' }); + return { account, flow, flowsCount: 1 }; + }) + .shouldSucceed(({flow, flows, account}, extPubKey) => { + expect(flows[0]).to.be.deep.equal(flow); + expect(extPubKey).to.be.deep.equal({ + publicKey: toHex(account.publicKey.pub_key_bytes()), + chainCode: toHex(account.publicKey.chain_code()), }); - } - ); + }) + .run(({test, account}) => test.device.getExtendedPublicKey(account.path.toString())); }); }); diff --git a/tests/transaction-tests.js b/tests/transaction-tests.js index b5ada157..88f3cd2d 100644 --- a/tests/transaction-tests.js +++ b/tests/transaction-tests.js @@ -1,61 +1,52 @@ -const { expect } = require('chai') - .use(require('chai-bytes')); -const { Transaction, TxId, Tokens, Token, TokenId, TokenAmount, I64, ErgoBox } = require('ergo-lib-wasm-nodejs'); +const { expect } = require('chai').use(require('chai-bytes')); +const { Transaction, ErgoBox } = require('ergo-lib-wasm-nodejs'); const { toNetwork, getApplication, removeMasterNode, ellipsize } = require('./helpers/common'); const { TEST_DATA } = require('./helpers/data'); -const { AuthTokenFlows } = require('./helpers/flow'); -const { UnsignedTransactionBuilder } = require('./helpers/transaction'); +const { authTokenFlows } = require('./helpers/flow'); +const { TxBuilder } = require('./helpers/transaction'); -const signTxFlowCount = [4, 4]; -const signTxFlowCount22 = [signTxFlowCount[0]+1, signTxFlowCount[1]+1]; +const txId = "0000000000000000000000000000000000000000000000000000000000000000"; -function signTxFlows({ model, device }, auth, from, to, change, tokens = undefined) { - const flows = [ - [ - { header: null, body: 'Confirm Attest Input' }, - { header: null, body: 'Approve' }, - { header: null, body: 'Reject' } - ], - [ - { header: 'P2PK Signing', body: removeMasterNode(from.path.toString()) }, - { header: 'Application', body: '0x00000000' }, - { header: null, body: 'Approve' }, - { header: null, body: 'Reject' } - ], - [ - { header: null, body: 'Confirm Output' }, - { header: 'Address', body: to.toBase58() }, - { header: 'Output Value', body: '0.100000000 ERG' }, - { header: null, body: 'Approve' }, - { header: null, body: 'Reject' } - ], - [ - { header: null, body: 'Confirm Output' }, - { header: 'Change', body: removeMasterNode(change.path.toString()) }, - { header: null, body: 'Approve' }, - { header: null, body: 'Reject' } - ], - [ - { header: null, body: 'Approve Signing' }, - { header: 'P2PK Path', body: removeMasterNode(from.path.toString()) }, - { header: 'Transaction Amount', body: '0.100000000 ERG' }, - { header: 'Transaction Fee', body: '0.001000000 ERG' }, - { header: null, body: 'Approve' }, - { header: null, body: 'Reject' } - ] - ]; - if (tokens) { - flows[2].splice(3, 0, ...tokens); - } +function signTxFlows({ device }, auth, from, to, change, tokens_to = undefined, tokens_tx = undefined) { + let i = 0; + const flows = []; + // attest input screen + flows[i] = [{ header: null, body: 'Confirm Attest Input' }]; if (auth) { - flows[0].splice(1, 0, { header: 'Application', body: getApplication(device) }); - flows[1].splice(1, 1); + flows[i].push({ header: 'Application', body: getApplication(device) }); + } + flows[i++].push({ header: null, body: 'Approve' }, { header: null, body: 'Reject' }); + // accept tx screen + flows[i] = [{ header: 'P2PK Signing', body: removeMasterNode(from.path.toString()) }]; + if (!auth) { + flows[i].push({ header: 'Application', body: '0x00000000' }); + } + flows[i++].push({ header: null, body: 'Approve' }, { header: null, body: 'Reject' }); + // output screen + if (to) { + flows[i] = [{ header: null, body: 'Confirm Output' }, + { header: 'Address', body: to.toBase58() }, + { header: 'Output Value', body: '0.100000000 ERG' }]; + if (tokens_to) { flows[i].push(...tokens_to); } + flows[i++].push({ header: null, body: 'Approve' }, { header: null, body: 'Reject' }); } - if (change.index <= 19) { - flows.splice(3, 1); + // change screen + if (change && (from.acc_index != change.acc_index || change.addr_index >= 19)) { + flows[i++] = [{ header: null, body: 'Confirm Output' }, + { header: 'Change', body: removeMasterNode(change.path.toString()) }, + { header: null, body: 'Approve' }, + { header: null, body: 'Reject' }]; + } + if (to && change) { + flows[i] = [{ header: null, body: 'Approve Signing' }, + { header: 'P2PK Path', body: removeMasterNode(from.path.toString()) }, + { header: 'Transaction Amount', body: '0.100000000 ERG' }, + { header: 'Transaction Fee', body: '0.001000000 ERG' }]; + if (tokens_tx) { flows[i].push(...tokens_tx); } + flows[i++].push({ header: null, body: 'Approve' }, { header: null, body: 'Reject' }); } return flows; -}; +} function verifySignatures(unsigned, signatures, ergoBox) { const signed = Transaction.from_unsigned_tx(unsigned, signatures); @@ -65,31 +56,22 @@ function verifySignatures(unsigned, signatures, ergoBox) { describe("Transaction Tests", function () { context("Transaction Commands", function () { - new AuthTokenFlows("can attest input", () => { - return { - unsignedBox: new UnsignedTransactionBuilder() - .input(TEST_DATA.address0, TxId.zero(), 0) - .output('100000000', TEST_DATA.address1.address) - .change(TEST_DATA.changeAddress) - .build() - .inputs[0] - }; - }).do( - function () { - return this.test.device.attestInput(this.unsignedBox); - }, - function (attestedBox) { - const attestInputFlow = [ - { header: null, body: 'Confirm Attest Input' }, - { header: null, body: 'Approve' }, - { header: null, body: 'Reject' } - ]; - if (this.auth) { - attestInputFlow.splice(1, 0, { header: 'Application', body: getApplication(this.test.device) }); + authTokenFlows("can attest input") + .init(async ({test, auth}) => { + const unsignedBox = new TxBuilder() + .input(TEST_DATA.address0, txId, 0, '1000000000') + .inputs[0].box; + const flow = [{ header: null, body: 'Confirm Attest Input' }]; + if (auth) { + flow.push({ header: 'Application', body: getApplication(test.device) }); } - expect(this.flows[0]).to.be.deep.equal(attestInputFlow); + flow.push({ header: null, body: 'Approve' }, { header: null, body: 'Reject' }); + return { unsignedBox, flow, flowsCount: 1 }; + }) + .shouldSucceed(({flow, flows, unsignedBox}, attestedBox) => { + expect(flows[0]).to.be.deep.equal(flow); expect(attestedBox).to.have.property('box'); - expect(attestedBox.box).to.be.deep.equal(this.unsignedBox); + expect(attestedBox.box).to.be.deep.equal(unsignedBox); expect(attestedBox).to.have.property('frames'); expect(attestedBox.frames).to.have.length(1); const frame = attestedBox.frames[0]; @@ -100,316 +82,274 @@ describe("Transaction Tests", function () { expect(frame.tokens).to.be.empty; expect(frame.attestation).to.exist; expect(frame.buffer).to.exist; - } - ); + }) + .run(({test, unsignedBox}) => test.device.attestInput(unsignedBox)); - new AuthTokenFlows("can sign tx", () => { - const from = TEST_DATA.address0; - const to = TEST_DATA.address1; - const change = TEST_DATA.changeAddress; - const builder = new UnsignedTransactionBuilder() - .input(from, TxId.zero(), 0) - .dataInput(from.address, TxId.zero(), 0) - .output('100000000', to.address) - .fee('1000000') - .change(change); - return { from, to, change, builder }; - }, signTxFlowCount).do( - function () { - const unsignedTransaction = this.builder.build(); - return this.test.device.signTx(unsignedTransaction, toNetwork(TEST_DATA.network)) - }, - function (signatures) { - let flows = signTxFlows(this.test, this.auth, this.from, this.to, this.change); - expect(this.flows).to.be.deep.equal(flows); + authTokenFlows("can sign tx") + .init(async ({test, auth}) => { + const from = TEST_DATA.address0; + const to = TEST_DATA.address1; + const change = TEST_DATA.changeAddress; + const {appTx, ergoTx, uInputs} = new TxBuilder() + .input(from, txId, 0, '1000000000') + .dataInput(from.address, txId, 1) + .output(to.address, '100000000') + .fee('1000000') + .change(change) + .build(); + const expectedFlows = signTxFlows(test, auth, from, to, change); + return { appTx, ergoTx, input: uInputs[0], + expectedFlows, flowsCount: expectedFlows.length, + network: TEST_DATA.network }; + }) + .shouldSucceed(({ergoTx, input, expectedFlows, flows}, signatures) => { + expect(flows).to.be.deep.equal(expectedFlows); expect(signatures).to.have.length(1); - const ergoBox = this.builder.ergoBuilder.inputs.get(0); - verifySignatures(this.builder.ergoTransaction, signatures, ergoBox); - } - ); + verifySignatures(ergoTx, signatures, input); + }) + .run(({test, appTx, network}) => test.device.signTx(appTx, toNetwork(network))); - new AuthTokenFlows("can sign tx change 22", () => { - const from = TEST_DATA.address0; - const to = TEST_DATA.address1; - const change = TEST_DATA.changeAddress22; - const builder = new UnsignedTransactionBuilder() - .input(from, TxId.zero(), 0) - .dataInput(from.address, TxId.zero(), 0) - .output('100000000', to.address) - .fee('1000000') - .change(change); - return { from, to, change, builder }; - }, signTxFlowCount22).do( - function () { - const unsignedTransaction = this.builder.build(); - return this.test.device.signTx(unsignedTransaction, toNetwork(TEST_DATA.network)) - }, - function (signatures) { - let flows = signTxFlows(this.test, this.auth, this.from, this.to, this.change); - expect(this.flows).to.be.deep.equal(flows); + authTokenFlows("can sign tx change 22") + .init(async ({test, auth}) => { + const from = TEST_DATA.address0; + const to = TEST_DATA.address1; + const change = TEST_DATA.changeAddress22; + const {appTx, ergoTx, uInputs} = new TxBuilder() + .input(from, txId, 0, '1000000000') + .dataInput(from.address, txId, 1) + .output(to.address, '100000000') + .fee('1000000') + .change(change) + .build(); + const expectedFlows = signTxFlows(test, auth, from, to, change); + return { appTx, ergoTx, input: uInputs[0], + expectedFlows, flowsCount: expectedFlows.length }; + }) + .shouldSucceed(({ergoTx, input, expectedFlows, flows}, signatures) => { + expect(flows).to.be.deep.equal(expectedFlows); expect(signatures).to.have.length(1); - const ergoBox = this.builder.ergoBuilder.inputs.get(0); - verifySignatures(this.builder.ergoTransaction, signatures, ergoBox); - } - ); + verifySignatures(ergoTx, signatures, input); + }) + .run(({test, appTx}) => test.device.signTx(appTx, toNetwork(TEST_DATA.network))); - new AuthTokenFlows("can sign tx with additional registers", () => { - const from = TEST_DATA.address0; - const to = TEST_DATA.address1; - const change = TEST_DATA.changeAddress; - const ergoBox = ErgoBox.from_json(`{ - "boxId": "ef16f4a6db61a1c31aea55d3bf10e1fb6443cf08cff4a1cf2e3a4780e1312dba", - "value": 1000000000, - "ergoTree": "${from.address.to_ergo_tree().to_base16_bytes()}", - "assets": [], - "additionalRegisters": { - "R5": "0e050102030405", - "R4": "04f601" - }, - "creationHeight": 0, - "transactionId": "0000000000000000000000000000000000000000000000000000000000000000", - "index": 0 - }`); - const builder = new UnsignedTransactionBuilder() - .inputFrom(ergoBox, from.path) - .output('100000000', to.address) - .fee('1000000') - .change(change); - return { from, to, change, builder }; - }, signTxFlowCount).do( - function () { - const unsignedTransaction = this.builder.build(); - return this.test.device.signTx(unsignedTransaction, toNetwork(TEST_DATA.network)) - }, - function (signatures) { - let flows = signTxFlows(this.test, this.auth, this.from, this.to, this.change); - expect(this.flows).to.be.deep.equal(flows); + authTokenFlows("can sign tx with additional registers") + .init(({test, auth}) => { + const from = TEST_DATA.address0; + const to = TEST_DATA.address1; + const change = TEST_DATA.changeAddress; + const ergoBox = ErgoBox.from_json(`{ + "boxId": "ef16f4a6db61a1c31aea55d3bf10e1fb6443cf08cff4a1cf2e3a4780e1312dba", + "value": 1000000000, + "ergoTree": "${from.address.to_ergo_tree().to_base16_bytes()}", + "assets": [], + "additionalRegisters": { + "R5": "0e050102030405", + "R4": "04f601" + }, + "creationHeight": 0, + "transactionId": "0000000000000000000000000000000000000000000000000000000000000000", + "index": 0 + }`); + const {appTx, ergoTx, uInputs} = new TxBuilder() + .boxInput(ergoBox, from.path) + .output(to.address, '100000000') + .fee('1000000') + .change(change) + .build(); + const expectedFlows = signTxFlows(test, auth, from, to, change); + return { appTx, ergoTx, input: uInputs[0], + expectedFlows, flowsCount: expectedFlows.length }; + }) + .shouldSucceed(({ergoTx, input, expectedFlows, flows}, signatures) => { + expect(flows).to.be.deep.equal(expectedFlows); expect(signatures).to.have.length(1); - const ergoBox = this.builder.ergoBuilder.inputs.get(0); - verifySignatures(this.builder.ergoTransaction, signatures, ergoBox); - } - ); + verifySignatures(ergoTx, signatures, input); + }) + .run(({test, appTx}) => test.device.signTx(appTx, toNetwork(TEST_DATA.network))); - new AuthTokenFlows("can sign tx with zero data inputs", () => { - const from = TEST_DATA.address0; - const to = TEST_DATA.address1; - const change = TEST_DATA.changeAddress; - const builder = new UnsignedTransactionBuilder() - .input(from, TxId.zero(), 0) - .output('100000000', to.address) - .fee('1000000') - .change(change); - return { from, to, change, builder }; - }, signTxFlowCount).do( - function () { - const unsignedTransaction = this.builder.build(); - return this.test.device.signTx(unsignedTransaction, toNetwork(TEST_DATA.network)); - }, - function (signatures) { - let flows = signTxFlows(this.test, this.auth, this.from, this.to, this.change); - expect(this.flows).to.be.deep.equal(flows); + authTokenFlows("can sign tx with zero data inputs") + .init(async ({test, auth}) => { + const from = TEST_DATA.address0; + const to = TEST_DATA.address1; + const change = TEST_DATA.changeAddress; + const {appTx, ergoTx, uInputs} = new TxBuilder() + .input(from, txId, 0, '1000000000') + .output(to.address, '100000000') + .fee('1000000') + .change(change) + .build(); + const expectedFlows = signTxFlows(test, auth, from, to, change); + return { appTx, ergoTx, input: uInputs[0], + expectedFlows, flowsCount: expectedFlows.length }; + }) + .shouldSucceed(({ergoTx, input, expectedFlows, flows}, signatures) => { + expect(flows).to.be.deep.equal(expectedFlows); expect(signatures).to.have.length(1); - const ergoBox = this.builder.ergoBuilder.inputs.get(0); - verifySignatures(this.builder.ergoTransaction, signatures, ergoBox); - } - ); + verifySignatures(ergoTx, signatures, input); + }) + .run(({test, appTx}) => test.device.signTx(appTx, toNetwork(TEST_DATA.network))); - new AuthTokenFlows("can not sign tx with zero inputs", () => { - return { - unsignedTransaction: new UnsignedTransactionBuilder() - .dataInput(TEST_DATA.address0.address, TxId.zero(), 0) - .output('100000000', TEST_DATA.address1.address) - .build(false) - }; - }, [0, 0]).do( - function () { - return this.test.device.signTx(this.unsignedTransaction, toNetwork(TEST_DATA.network)); - }, - null, - function (error) { + authTokenFlows("can not sign tx with zero inputs") + .init(async () => { + const tx = new TxBuilder() + .dataInput(TEST_DATA.address0.address, txId, 0) + .output(TEST_DATA.address1.address, '100000000') + .fee(null) + .buildAppTx(); + return { tx, flowsCount: 0 }; + }) + .shouldFail((_, error) => { expect(error).to.be.an('error'); expect(error.name).to.be.equal('DeviceError'); expect(error.message).to.be.equal('Bad input count'); - } - ); + }) + .run(({test, tx}) => test.device.signTx(tx, toNetwork(TEST_DATA.network))); - new AuthTokenFlows("can not sign tx with zero outputs", () => { - const address = TEST_DATA.address0; - const unsignedTransaction = new UnsignedTransactionBuilder() - .input(address, TxId.zero(), 0) - .dataInput(address.address, TxId.zero(), 0) - .build(false); - return { address, unsignedTransaction }; - }, [2, 2]).do( - function () { - return this.test.device.signTx(this.unsignedTransaction, toNetwork(TEST_DATA.network)); - }, - null, - function (error) { - const signTxNoOutputsFlows = [ - [ - { header: null, body: 'Confirm Attest Input' }, - { header: null, body: 'Approve' }, - { header: null, body: 'Reject' } - ], - [ - { header: 'P2PK Signing', body: removeMasterNode(this.address.path.toString()) }, - { header: 'Application', body: '0x00000000' }, - { header: null, body: 'Approve' }, - { header: null, body: 'Reject' } - ] - ]; - if (this.auth) { - signTxNoOutputsFlows[0].splice(1, 0, { header: 'Application', body: getApplication(this.test.device) }); - signTxNoOutputsFlows[1].splice(1, 1); - } - expect(this.flows).to.be.deep.equal(signTxNoOutputsFlows); + authTokenFlows("can not sign tx with zero outputs") + .init(async ({test, auth}) => { + const from = TEST_DATA.address0; + const tx = new TxBuilder() + .input(from, txId, 0, '1000000000') + .dataInput(from.address, txId, 0) + .fee(null) + .buildAppTx(); + const expectedFlows = signTxFlows(test, auth, from, null, null); + return { tx, expectedFlows, flowsCount: expectedFlows.length }; + }) + .shouldFail(({flows, expectedFlows}, error) => { + expect(flows).to.be.deep.equal(expectedFlows); expect(error).to.be.an('error'); expect(error.name).to.be.equal('DeviceError'); expect(error.message).to.be.equal('Bad output count'); - } - ); + }) + .run(({test, tx}) => test.device.signTx(tx, toNetwork(TEST_DATA.network))); - new AuthTokenFlows("can sign tx with tokens", () => { - const from = TEST_DATA.address0; - const to = TEST_DATA.address1; - const change = TEST_DATA.changeAddress; - const tokens = new Tokens(); - const tokenId = TokenId.from_str('1111111111111111111111111111111111111111111111111111111111111111'); - tokens.add(new Token(tokenId, TokenAmount.from_i64(I64.from_str('1000')))); - const builder = new UnsignedTransactionBuilder() - .input(from, TxId.zero(), 0, tokens) - .dataInput(from.address, TxId.zero(), 0) - .output('100000000', to.address, tokens) - .fee('1000000') - .change(change); - return { from, to, change, builder, tokenId }; - }, signTxFlowCount).do( - function () { - const unsignedTransaction = this.builder.build(); - return this.test.device.signTx(unsignedTransaction, toNetwork(TEST_DATA.network)); - }, - function (signatures) { - const tokens = [ - { header: 'Token [1]', body: ellipsize(this.test.model, this.tokenId.to_str()) }, + authTokenFlows("can sign tx with tokens") + .init(async ({test, auth}) => { + const from = TEST_DATA.address0; + const to = TEST_DATA.address1; + const change = TEST_DATA.changeAddress; + const tokenId = '1111111111111111111111111111111111111111111111111111111111111111'; + const tokens = [{id: tokenId, amount: '1000'}]; + const {appTx, ergoTx, uInputs} = new TxBuilder() + .input(from, txId, 0, '1000000000', tokens) + .dataInput(from.address, txId, 0) + .output(to.address, '100000000', tokens) + .fee('1000000') + .change(change) + .build(); + const tokensFlow = [ + { header: 'Token [1]', body: ellipsize(test.model, tokenId) }, { header: 'Token [1] Value', body: '1000' } ]; - let flows = signTxFlows(this.test, this.auth, this.from, this.to, this.change, tokens); - expect(this.flows).to.be.deep.equal(flows); + const expectedFlows = signTxFlows(test, auth, from, to, change, tokensFlow); + return { appTx, ergoTx, input: uInputs[0], + expectedFlows, flowsCount: expectedFlows.length }; + }) + .shouldSucceed(({ergoTx, input, flows, expectedFlows}, signatures) => { + expect(flows).to.be.deep.equal(expectedFlows); expect(signatures).to.have.length(1); - const ergoBox = this.builder.ergoBuilder.inputs.get(0); - verifySignatures(this.builder.ergoTransaction, signatures, ergoBox); - } - ); + verifySignatures(ergoTx, signatures, input); + }) + .run(({test, appTx}) => test.device.signTx(appTx, toNetwork(TEST_DATA.network))); - new AuthTokenFlows("can sign tx with burned tokens", () => { - const from = TEST_DATA.address0; - const to = TEST_DATA.address1; - const change = TEST_DATA.changeAddress; - const tokens = new Tokens(); - const tokenId = TokenId.from_str('1111111111111111111111111111111111111111111111111111111111111111'); - tokens.add(new Token(tokenId, TokenAmount.from_i64(I64.from_str('1000')))); - const unsignedTransaction = new UnsignedTransactionBuilder() - .input(from, TxId.zero(), 0, tokens) - .dataInput(from.address, TxId.zero(), 0) - .output('100000000', to.address) - .fee('1000000') - .change(change) - .tokenIds([tokenId.as_bytes()]) - .build(false); - return { from, to, change, unsignedTransaction, tokenId }; - }, signTxFlowCount).do( - function () { - return this.test.device.signTx(this.unsignedTransaction, toNetwork(TEST_DATA.network)); - }, - function (signatures) { - const tokens = [ - { header: 'Token [1]', body: ellipsize(this.test.model, this.tokenId.to_str()) }, + authTokenFlows("can sign tx with burned tokens") + .init(async ({test, auth}) => { + const from = TEST_DATA.address0; + const to = TEST_DATA.address1; + const change = TEST_DATA.changeAddress; + const tokenId = '1111111111111111111111111111111111111111111111111111111111111111'; + const tokens = [{id: tokenId, amount: '1000'}]; + const {appTx, ergoTx, uInputs} = new TxBuilder() + .input(from, txId, 0, '1000000000', tokens) + .dataInput(from.address, txId, 0) + .output(to.address, '100000000') + .fee('1000000') + .change(change) + .burn(tokens) + .build(); + const tokensFlow = [ + { header: 'Token [1]', body: ellipsize(test.model, tokenId) }, { header: 'Token [1] Value', body: 'Burning: 1000' } ]; - let flows = signTxFlows(this.test, this.auth, this.from, this.to, this.change); - flows[flows.length-1].splice(4, 0, ...tokens); - expect(this.flows).to.be.deep.equal(flows); + const expectedFlows = signTxFlows(test, auth, from, to, change, null, tokensFlow); + return { appTx, ergoTx, input: uInputs[0], + expectedFlows, flowsCount: expectedFlows.length };; + }) + .shouldSucceed(({flows, expectedFlows, ergoTx, input}, signatures) => { + expect(flows).to.be.deep.equal(expectedFlows); expect(signatures).to.have.length(1); - } - ); + verifySignatures(ergoTx, signatures, input); + }) + .run(({test, appTx}) => test.device.signTx(appTx, toNetwork(TEST_DATA.network))); - new AuthTokenFlows("can sign tx with minted tokens", () => { - const from = TEST_DATA.address0; - const to = TEST_DATA.address1; - const change = TEST_DATA.changeAddress; - const tokens = new Tokens(); - const tokenId = TokenId.from_str('1111111111111111111111111111111111111111111111111111111111111111'); - tokens.add(new Token(tokenId, TokenAmount.from_i64(I64.from_str('1000')))); - const unsignedTransaction = new UnsignedTransactionBuilder() - .input(from, TxId.zero(), 0) - .dataInput(from.address, TxId.zero(), 0) - .output('100000000', to.address, tokens) - .fee('1000000') - .change(change) - .tokenIds([tokenId.as_bytes()]) - .build(false); - return { from, to, change, unsignedTransaction, tokenId }; - }, signTxFlowCount).do( - function () { - return this.test.device.signTx(this.unsignedTransaction, toNetwork(TEST_DATA.network)); - }, - function (signatures) { - const tokens = [ - { header: 'Token [1]', body: ellipsize(this.test.model, this.tokenId.to_str()) }, + authTokenFlows("can sign tx with minted tokens") + .init(async ({test, auth}) => { + const from = TEST_DATA.address0; + const to = TEST_DATA.address1; + const change = TEST_DATA.changeAddress; + const {appTx, ergoTx, uInputs} = new TxBuilder() + .input(from, txId, 0, '1000000000') + .dataInput(from.address, txId, 0) + .output(to.address, '100000000', null, '1000') + .fee('1000000') + .change(change) + .build(); + const tokenId = uInputs[0].box_id().to_str().toUpperCase(); + const tokensOutFlow = [ + { header: 'Token [1]', body: ellipsize(test.model, tokenId) }, { header: 'Token [1] Value', body: '1000' } ]; - let flows = signTxFlows(this.test, this.auth, this.from, this.to, this.change, tokens); - flows[flows.length-1].splice(4, 0, ...[ - { header: 'Token [1]', body: ellipsize(this.test.model, this.tokenId.to_str()) }, + const tokensTxFlow = [ + { header: 'Token [1]', body: ellipsize(test.model, tokenId) }, { header: 'Token [1] Value', body: 'Minting: 1000' } - ]); - expect(this.flows).to.be.deep.equal(flows); + ]; + const expectedFlows = signTxFlows(test, auth, from, to, change, tokensOutFlow, tokensTxFlow); + return { appTx, ergoTx, input: uInputs[0], + expectedFlows, flowsCount: expectedFlows.length }; + }) + .shouldSucceed(({flows, expectedFlows, ergoTx, input}, signatures) => { + expect(flows).to.be.deep.equal(expectedFlows); expect(signatures).to.have.length(1); - } - ); + verifySignatures(ergoTx, signatures, input); + }) + .run(({test, appTx}) => test.device.signTx(appTx, toNetwork(TEST_DATA.network))); - new AuthTokenFlows("can sign tx with few token ids", () => { - const from = TEST_DATA.address0; - const to = TEST_DATA.address1; - const change = TEST_DATA.changeAddress; - const tokens = new Tokens(); - const tokenId = TokenId.from_str('1111111111111111111111111111111111111111111111111111111111111111'); - tokens.add(new Token(tokenId, TokenAmount.from_i64(I64.from_str('1000')))); - const tokens2 = new Tokens(); - const tokenId2 = TokenId.from_str('0000000000000000000000000000000000000000000000000000000000000000'); - tokens2.add(new Token(tokenId2, TokenAmount.from_i64(I64.from_str('1000')))); - const unsignedTransaction = new UnsignedTransactionBuilder() - .input(from, TxId.zero(), 0, tokens) - .dataInput(from.address, TxId.zero(), 0) - .output('100000000', to.address, tokens2) - .fee('1000000') - .change(change) - .tokenIds([ - tokenId.as_bytes(), - tokenId2.as_bytes() - ]) - .build(false); - return { from, to, change, unsignedTransaction, tokenId, tokenId2 }; - }, signTxFlowCount).do( - function () { - return this.test.device.signTx(this.unsignedTransaction, toNetwork(TEST_DATA.network)); - }, - function (signatures) { - const tokens = [ - { header: 'Token [1]', body: ellipsize(this.test.model, this.tokenId2.to_str()) }, - { header: 'Token [1] Value', body: '1000' } + authTokenFlows("can sign tx with few token ids") + .init(async ({test, auth}) => { + const from = TEST_DATA.address0; + const to = TEST_DATA.address1; + const change = TEST_DATA.changeAddress; + const iTokenId = '1111111111111111111111111111111111111111111111111111111111111111'; + const iTokens = [{id: iTokenId, amount: '1234'}]; + const {appTx, ergoTx, uInputs} = new TxBuilder() + .input(from, txId, 0, '1000000000', iTokens) + .dataInput(from.address, txId, 0) + .output(to.address, '100000000', null, '5678') + .fee('1000000') + .change(change) + .burn(iTokens) + .build(); + const oTokenId = uInputs[0].box_id().to_str().toUpperCase(); + const tokensOutFlow = [ + { header: 'Token [1]', body: ellipsize(test.model, oTokenId) }, + { header: 'Token [1] Value', body: '5678' } + ]; + const tokensTxFlow = [ + { header: 'Token [1]', body: ellipsize(test.model, oTokenId) }, + { header: 'Token [1] Value', body: 'Minting: 5678' }, + { header: 'Token [2]', body: ellipsize(test.model, iTokenId) }, + { header: 'Token [2] Value', body: 'Burning: 1234' } ]; - let flows = signTxFlows(this.test, this.auth, this.from, this.to, this.change, tokens); - flows[flows.length-1].splice(4, 0, ...[ - { header: 'Token [1]', body: ellipsize(this.test.model, this.tokenId.to_str()) }, - { header: 'Token [1] Value', body: 'Burning: 1000' }, - { header: 'Token [2]', body: ellipsize(this.test.model, this.tokenId2.to_str()) }, - { header: 'Token [2] Value', body: 'Minting: 1000' } - ]); - expect(this.flows).to.be.deep.equal(flows); + const expectedFlows = signTxFlows(test, auth, from, to, change, tokensOutFlow, tokensTxFlow); + return { appTx, ergoTx, input: uInputs[0], + expectedFlows, flowsCount: expectedFlows.length }; + }) + .shouldSucceed(({flows, expectedFlows, ergoTx, input}, signatures) => { + expect(flows).to.be.deep.equal(expectedFlows); expect(signatures).to.have.length(1); - } - ); + verifySignatures(ergoTx, signatures, input); + }) + .run(({test, appTx}) => test.device.signTx(appTx, toNetwork(TEST_DATA.network))); }); });