@trivechain/triveasset-protocol
Version:
TriveAsset Protocol provides the definition, encode and decode functions that recognize a TriveAsset Encoding
520 lines (491 loc) • 18.7 kB
JavaScript
/* eslint-env mocha */
const TransactionBuilder = require('../index').TransactionBuilder
const TA = require('../index').Transaction
const transactionBuilder = new TransactionBuilder({ network: 'testnet' })
const assert = require('assert')
const clone = require('clone')
const bitcoinjs = require('bitcoinjs-lib')
const Transaction = bitcoinjs.Transaction
const script = bitcoinjs.script
const _ = require('lodash')
const consumer = function (buff) {
let curr = 0
return function consume(len) {
return buff.slice(curr, (curr += len))
}
}
const toBuffer = function (val) {
val = val.toString(16)
if (val.length % 2 === 1) {
val = '0' + val
}
return Buffer.from(val, 'hex')
}
const issueArgs = {
utxos: [
{
index: 2,
txid: '26879b80504ae1251f401ecfd3c5e50ee467d994ae0b656ef321957d5e8310e7',
blocktime: 1584795444000,
blockheight: 564183,
value: 999976677,
used: false,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 cc9e2fae9f7c83254c79e0fc5fff047fc04fbea7 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a914cc9e2fae9f7c83254c79e0fc5fff047fc04fbea788ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['tRaXPu5bovE5bXx7fgZ5ANsmACdra86PPm'],
},
assets: [],
},
],
issueAddress: 'tAgiixzqW2bAjWTqNyn3kSNhBNY6ozCFDm',
amount: 3600,
fee: 5000,
}
describe('builder.buildIssueTransaction(args)', function () {
it('throws: Must have "utxos"', function (done) {
const args = clone(issueArgs)
delete args.utxos
assert.throws(function () {
transactionBuilder.buildIssueTransaction(args)
}, /Must have "utxos"/)
done()
})
it('throws: Must have "issueAddress"', function (done) {
const args = clone(issueArgs)
delete args.issueAddress
assert.throws(function () {
transactionBuilder.buildIssueTransaction(args)
}, /Must have "issueAddress"/)
done()
})
it('throws: Must have "amount"', function (done) {
const args = clone(issueArgs)
delete args.amount
assert.throws(function () {
transactionBuilder.buildIssueTransaction(args)
}, /Must have "amount"/)
done()
})
it('returns valid response with default values', function (done) {
const result = transactionBuilder.buildIssueTransaction(issueArgs)
assert(result.txHex)
const tx = Transaction.fromHex(result.txHex)
assert.strictEqual(tx.ins.length, 1)
assert.strictEqual(tx.outs.length, 3) // OP_RETURN + 2 changes
assert(result.assetId)
assert.deepEqual(result.coloredOutputIndexes, [2])
const sumValueInputs = issueArgs.utxos[0].value
const sumValueOutputs = _.sumBy(tx.outs, function (output) {
return output.value
})
assert.strictEqual(sumValueInputs - sumValueOutputs, issueArgs.fee)
const opReturnScriptBuffer = script.decompile(tx.outs[0].script)[1]
const taTransaction = TA.fromHex(opReturnScriptBuffer)
assert.strictEqual(taTransaction.type, 'issuance')
assert.strictEqual(taTransaction.amount, issueArgs.amount)
// default values
assert.strictEqual(taTransaction.lockStatus, true)
assert.strictEqual(taTransaction.divisibility, 0)
assert.strictEqual(taTransaction.aggregationPolicy, 'aggregatable')
done()
})
it('flags.injectPreviousOutput === true: return previous output hex in inputs', function (done) {
const args = clone(issueArgs)
args.flags = { injectPreviousOutput: true }
const result = transactionBuilder.buildIssueTransaction(args)
assert(result.txHex)
const tx = Transaction.fromHex(result.txHex)
assert.strictEqual(tx.ins.length, 1)
assert.strictEqual(
tx.ins[0].script.toString('hex'),
args.utxos[0].scriptPubKey.hex
)
done()
})
it('should split change', function (done) {
const args = clone(issueArgs)
args.financeChangeAddress = false
const result = transactionBuilder.buildIssueTransaction(args)
assert(result.txHex)
const tx = Transaction.fromHex(result.txHex)
assert.strictEqual(tx.ins.length, 1)
assert.strictEqual(tx.outs.length, 2) // OP_RETURN + 1 change
assert.deepEqual(result.coloredOutputIndexes, [1])
done()
})
it('should encode ipfsHash', function (done) {
const args = clone(issueArgs)
args.ipfsHash =
'12207fd9423c0301a82e7116483cbc194d7c3818b2e11a77c5e021b2c5d04cb48852'
const result = transactionBuilder.buildIssueTransaction(args)
const tx = Transaction.fromHex(result.txHex)
const opReturnScriptBuffer = script.decompile(tx.outs[0].script)[1]
const taTransaction = TA.fromHex(opReturnScriptBuffer)
assert.strictEqual(taTransaction.ipfsHash.toString('hex'), args.ipfsHash)
done()
})
})
const sendArgs = {
utxos: [
{
index: 2,
txid: '13eb8adcf7c0c7657739df9d24d7917eb27f0e50316df75cb8d208e793134423',
blocktime: 1575532788000,
blockheight: 204359,
value: 5441,
used: false,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 cc9e2fae9f7c83254c79e0fc5fff047fc04fbea7 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a914cc9e2fae9f7c83254c79e0fc5fff047fc04fbea788ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['tRaXPu5bovE5bXx7fgZ5ANsmACdra86PPm'],
},
assets: [
{
assetId: 'La9Sa9wWN5MpAFLtRwS1jwTFsRzdRqhtpeVcya',
amount: 100000000000000,
issueTxid:
'13eb8adcf7c0c7657739df9d24d7917eb27f0e50316df75cb8d208e793134423',
divisibility: 8,
lockStatus: true,
aggregationPolicy: 'aggregatable',
},
],
},
{
index: 2,
txid: '26879b80504ae1251f401ecfd3c5e50ee467d994ae0b656ef321957d5e8310e7',
blocktime: 1584795444000,
blockheight: 564183,
value: 999976677,
used: false,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 cc9e2fae9f7c83254c79e0fc5fff047fc04fbea7 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a914cc9e2fae9f7c83254c79e0fc5fff047fc04fbea788ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['tRaXPu5bovE5bXx7fgZ5ANsmACdra86PPm'],
},
assets: [],
},
],
to: [
{
address: 'tAgiixzqW2bAjWTqNyn3kSNhBNY6ozCFDm',
amount: 20,
assetId: 'La9Sa9wWN5MpAFLtRwS1jwTFsRzdRqhtpeVcya',
},
],
fee: 5000,
}
describe('builder.buildSendTransaction(args)', function () {
it('throws: Must have "utxos"', function (done) {
const args = clone(sendArgs)
delete args.utxos
assert.throws(function () {
transactionBuilder.buildSendTransaction(args)
}, /Must have "utxos"/)
done()
})
it('throws: Must have "to"', function (done) {
const args = clone(sendArgs)
delete args.to
assert.throws(function () {
transactionBuilder.buildSendTransaction(args)
}, /Must have "to"/)
done()
})
it('returns valid response with default values', function (done) {
sendArgs.fee = 5000
const result = transactionBuilder.buildSendTransaction(sendArgs)
assert(result.txHex)
const tx = Transaction.fromHex(result.txHex)
assert.strictEqual(tx.ins.length, 2)
assert.strictEqual(tx.outs.length, 4) // transfer + OP_RETURN + 2 changes
assert.deepEqual(result.coloredOutputIndexes, [0, 3])
const sumValueInputs = sendArgs.utxos[0].value + sendArgs.utxos[1].value
const sumValueOutputs = _.sumBy(tx.outs, function (output) {
return output.value
})
assert.strictEqual(sumValueInputs - sumValueOutputs, sendArgs.fee)
const opReturnScriptBuffer = script.decompile(tx.outs[1].script)[1]
const taTransaction = TA.fromHex(opReturnScriptBuffer)
assert.strictEqual(taTransaction.type, 'transfer')
assert.strictEqual(taTransaction.payments[0].range, false)
assert.strictEqual(taTransaction.payments[0].output, 0)
assert.strictEqual(taTransaction.payments[0].input, 0)
assert.strictEqual(taTransaction.payments[0].percent, false)
assert.strictEqual(taTransaction.payments[0].amount, sendArgs.to[0].amount)
done()
})
it('returns valid response with default values', function (done) {
const addresses = [
'tQsErC77qB5X1oUbkkWeVWoCm4J7xbfays',
't8PezUK91pug8uY7M4piDRYj5QHLkyxC8J',
'tGjioXYFLLxF8caMQdwcAXiXcLBZj5Xh4Q',
'tNc7YX6hzMrd5HXfHMEPSkgRxTeFwNUfUp',
'tHLJScKjfi8c2ntrxr41WdFK9aJPMyQmV7',
'tMgRnGCE6JQKtWAYgwbQ12dhps2RpzyD6Y',
'tTnG8XvR6ps6eWBZnFPCAii47n3DgSznxE',
'tEjizw2dPnYXxfjfTWmUf6keEft5n1oak3',
'tJ3ec9RRn7MQmJ5vMCk8HNFmfRfv333gah',
'tGWeDgsHRSpmZf2BFZvThcy2b66YfFf3VA',
'tH14kK6qsm2tXGfZ6gBPyZ6sJESDPUPBvW',
'tVCTQzPURiBqWdRYyGwq2ahNLLPqwTHHzG',
'tMDjq56q5WtAURvq1SDNRiyCKism1Z1WMh',
'tKeRoxknf4MaWoYmqC5ExQgFfCKk9dNiJp',
'tNZFPR8cX97LYRVveu8tGvqnmFXhFASAjj',
'tAe7kgaY7LuUycaWdYkUjB1xRwDCAyAKtg',
't9RnjhUpBFRJ28XjTsQ6BSeUbxWWMsbGS5',
'tGzCUCvrK3LWcxgdoJbStbs3yKYNkwxvG3',
'tEsqntmm4YsriSY7JiKsKK5RaC8zBC2JFs',
'tARUgLr2NJsXhH63Jwvp1EdjxjJLpmCdQa',
't9cDKrneLkqNWJfYWQUxbweSEXA1TNivRr',
'tE4zdhKF83XYaGKE2HGkwrfDWQcfuiuWa7',
'tBLDv4JmjoTUxism3E3gr1qU53nrj41T17',
'tFm2EnfdHehaqJAJaHwnnQnz778UocQ1Re',
'tLG2RpK7bU8ouF8crY9s9fmzxQdPHLF6w3',
'tVCH32qqKYQX4UFE15zZAmUXnQhqs5kW4i',
'tNrbLTwjanrvnGJYtueL2z6Y9ALnqpghEu',
'tGpxRwQsNugHP6fPNqnYm2bBGdkMQuZX1Y',
'tE5BBJizdH6tax625VH35dGXEHqknaKTjU',
'tDuwND9abYovDHbdTT7HP1eHPsAfxofSPj',
]
const args = clone(sendArgs)
args.ipfsHash =
'122098ed210c6291c25ae9cd40a85aeced620ef2c4c169e0cdc2be2091ddf3a352e3'
for (const address of addresses) {
args.to.push({
address: address,
amount: 1,
assetId: 'La9Sa9wWN5MpAFLtRwS1jwTFsRzdRqhtpeVcya',
})
}
const result = transactionBuilder.buildSendTransaction(args)
assert(result.txHex)
const tx = Transaction.fromHex(result.txHex)
const opReturnScriptBuffer = script.decompile(
tx.outs[tx.outs.length - 3].script
)[1]
const taTransaction = TA.fromHex(opReturnScriptBuffer)
assert.strictEqual(taTransaction.multiSig[0].hashType, 'ipfsHash')
done()
})
it('should encode ipfsHash', function (done) {
const args = clone(sendArgs)
args.ipfsHash =
'122098ed210c6291c25ae9cd40a85aeced620ef2c4c169e0cdc2be2091ddf3a352e3'
const result = transactionBuilder.buildSendTransaction(args)
const tx = Transaction.fromHex(result.txHex)
const opReturnScriptBuffer = script.decompile(tx.outs[1].script)[1]
const taTransaction = TA.fromHex(opReturnScriptBuffer)
assert.strictEqual(taTransaction.ipfsHash.toString('hex'), args.ipfsHash)
done()
})
it('flags.injectPreviousOutput === true: return previous output hex in inputs', function (done) {
const args = clone(sendArgs)
args.flags = { injectPreviousOutput: true }
const result = transactionBuilder.buildSendTransaction(args)
assert(result.txHex)
const tx = Transaction.fromHex(result.txHex)
assert.strictEqual(tx.ins.length, 2)
assert.strictEqual(
tx.ins[0].script.toString('hex'),
args.utxos[0].scriptPubKey.hex
)
done()
})
it('should not have finance change', function (done) {
const args = clone(sendArgs)
args.utxos[1].value = 10441
args.fee = 5000
const result = transactionBuilder.buildSendTransaction(args)
assert(result.txHex)
const tx = Transaction.fromHex(result.txHex)
assert.strictEqual(tx.ins.length, 2)
assert.strictEqual(tx.outs.length, 3) // transfer + OP_RETURN + 1 change
assert.deepEqual(result.coloredOutputIndexes, [0, 2])
done()
})
it('should not have colored change', function (done) {
const args = clone(sendArgs)
args.to[0].amount = args.utxos[0].assets[0].amount
args.fee = 5000
const result = transactionBuilder.buildSendTransaction(args)
assert(result.txHex)
const tx = Transaction.fromHex(result.txHex)
assert.strictEqual(tx.ins.length, 2)
assert.strictEqual(tx.outs.length, 3) // transfer + OP_RETURN + 1 change
assert.deepEqual(result.coloredOutputIndexes, [0])
done()
})
})
const burnArgs = {
utxos: [
{
index: 2,
txid: '13eb8adcf7c0c7657739df9d24d7917eb27f0e50316df75cb8d208e793134423',
blocktime: 1575532788000,
blockheight: 204359,
value: 5441,
used: false,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 cc9e2fae9f7c83254c79e0fc5fff047fc04fbea7 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a914cc9e2fae9f7c83254c79e0fc5fff047fc04fbea788ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['tRaXPu5bovE5bXx7fgZ5ANsmACdra86PPm'],
},
assets: [
{
assetId: 'La9Sa9wWN5MpAFLtRwS1jwTFsRzdRqhtpeVcya',
amount: 100000000000000,
issueTxid:
'13eb8adcf7c0c7657739df9d24d7917eb27f0e50316df75cb8d208e793134423',
divisibility: 8,
lockStatus: true,
aggregationPolicy: 'aggregatable',
},
],
},
{
index: 2,
txid: '26879b80504ae1251f401ecfd3c5e50ee467d994ae0b656ef321957d5e8310e7',
blocktime: 1584795444000,
blockheight: 564183,
value: 999976677,
used: false,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 cc9e2fae9f7c83254c79e0fc5fff047fc04fbea7 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a914cc9e2fae9f7c83254c79e0fc5fff047fc04fbea788ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['tRaXPu5bovE5bXx7fgZ5ANsmACdra86PPm'],
},
assets: [],
},
],
burn: [
{ amount: 100000000000, assetId: 'La9Sa9wWN5MpAFLtRwS1jwTFsRzdRqhtpeVcya' },
],
fee: 5000,
}
describe('builder.buildBurnTransaction(args)', function () {
it('returns valid response when burn completely', function (done) {
const result = transactionBuilder.buildBurnTransaction(burnArgs)
assert(result.txHex)
const tx = Transaction.fromHex(result.txHex)
assert.strictEqual(tx.ins.length, 2)
assert.strictEqual(tx.outs.length, 3) // OP_RETURN + 2 changes
assert.deepEqual(result.coloredOutputIndexes, [2])
const sumValueInputs = burnArgs.utxos[0].value + burnArgs.utxos[1].value
const sumValueOutputs = _.sumBy(tx.outs, function (output) {
return output.value
})
assert.strictEqual(sumValueInputs - sumValueOutputs, burnArgs.fee)
const opReturnScriptBuffer = script.decompile(tx.outs[0].script)[1]
const taTransaction = TA.fromHex(opReturnScriptBuffer)
assert.strictEqual(taTransaction.type, 'burn')
assert.strictEqual(taTransaction.payments[0].burn, true)
assert.strictEqual(taTransaction.payments[0].input, 0)
assert.strictEqual(
taTransaction.payments[0].amount,
burnArgs.burn[0].amount
)
done()
})
it('returns valid response when burn partially', function (done) {
burnArgs.burn[0].amount = 100
const result = transactionBuilder.buildBurnTransaction(burnArgs)
assert(result.txHex)
const tx = Transaction.fromHex(result.txHex)
assert.strictEqual(tx.ins.length, 2)
assert.strictEqual(tx.outs.length, 3) // OP_RETURN + 2 changes
assert.deepEqual(result.coloredOutputIndexes, [2])
const sumValueInputs = burnArgs.utxos[0].value + burnArgs.utxos[1].value
const sumValueOutputs = _.sumBy(tx.outs, function (output) {
return output.value
})
assert.strictEqual(sumValueInputs - sumValueOutputs, burnArgs.fee)
const opReturnScriptBuffer = script.decompile(tx.outs[0].script)[1]
const taTransaction = TA.fromHex(opReturnScriptBuffer)
assert.strictEqual(taTransaction.type, 'burn')
assert.strictEqual(taTransaction.payments[0].burn, true)
assert.strictEqual(taTransaction.payments[0].input, 0)
assert.strictEqual(
taTransaction.payments[0].amount,
burnArgs.burn[0].amount
)
done()
})
it('Burn OP_CODE 0x25 - No Metadata', function (done) {
const result = transactionBuilder.buildBurnTransaction(burnArgs)
assert(result.txHex)
const tx = Transaction.fromHex(result.txHex)
assert.strictEqual(tx.ins.length, 2)
assert.strictEqual(tx.outs.length, 3) // OP_RETURN + 2 changes
assert.deepEqual(result.coloredOutputIndexes, [2])
const sumValueInputs = burnArgs.utxos[0].value + burnArgs.utxos[1].value
const sumValueOutputs = _.sumBy(tx.outs, function (output) {
return output.value
})
assert.strictEqual(sumValueInputs - sumValueOutputs, burnArgs.fee)
const opReturnScriptBuffer = script.decompile(tx.outs[0].script)[1]
const consume = consumer(opReturnScriptBuffer)
assert.deepEqual(toBuffer('5441'), consume(2))
assert.deepEqual(toBuffer('03'), consume(1)) // version
assert.deepEqual(toBuffer('25'), consume(1)) // trasnfer OP_CODE
const taTransaction = TA.fromHex(opReturnScriptBuffer)
assert.strictEqual(taTransaction.type, 'burn')
assert.strictEqual(taTransaction.payments[0].burn, true)
assert.strictEqual(taTransaction.payments[0].input, 0)
assert.strictEqual(
taTransaction.payments[0].amount,
burnArgs.burn[0].amount
)
done()
})
it('Burn OP_CODE 0x26 - Burn Instruction & Metadata in OP_RETURN', function (done) {
burnArgs.ipfsHash =
'12207fd9423c0301a82e7116483cbc194d7c3818b2e11a77c5e021b2c5d04cb48852'
const result = transactionBuilder.buildBurnTransaction(burnArgs)
assert(result.txHex)
const tx = Transaction.fromHex(result.txHex)
assert.strictEqual(tx.ins.length, 2)
assert.strictEqual(tx.outs.length, 3) // OP_RETURN + 2 changes
assert.deepEqual(result.coloredOutputIndexes, [2])
const sumValueInputs = burnArgs.utxos[0].value + burnArgs.utxos[1].value
const sumValueOutputs = _.sumBy(tx.outs, function (output) {
return output.value
})
assert.strictEqual(sumValueInputs - sumValueOutputs, burnArgs.fee)
const opReturnScriptBuffer = script.decompile(tx.outs[0].script)[1]
console.log(opReturnScriptBuffer)
const consume = consumer(opReturnScriptBuffer)
assert.deepEqual(toBuffer('5441'), consume(2))
assert.deepEqual(toBuffer('03'), consume(1)) // version
assert.deepEqual(toBuffer('26'), consume(1)) // trasnfer OP_CODE
const taTransaction = TA.fromHex(opReturnScriptBuffer)
assert.strictEqual(taTransaction.type, 'burn')
assert.strictEqual(taTransaction.payments[0].burn, true)
assert.strictEqual(taTransaction.payments[0].input, 0)
assert.strictEqual(
taTransaction.payments[0].amount,
burnArgs.burn[0].amount
)
assert.strictEqual(
taTransaction.ipfsHash.toString('hex'),
burnArgs.ipfsHash
)
done()
})
})