aladinnetwork-blockstack
Version:
The Aladin Javascript library for authentication, identity, and storage.
1,188 lines (1,061 loc) • 75.3 kB
text/typescript
import test from 'tape'
import FetchMock from 'fetch-mock'
import { Transaction, TransactionBuilder, networks, address as bjsAddress, TxOutput } from 'bitcoinjs-lib'
import BN from 'bn.js'
import { network, InsightClient, BitcoindAPI } from '../../../src/network'
import {
estimateTXBytes, addUTXOsToFund, sumOutputValues,
hash160, hash128, decodeB40
} from '../../../src/operations/utils'
import { transactions, safety, config } from '../../../src'
const testAddresses = [
{
skHex: '85b33fdfa5efeca980806c6ad3c8a55d67a850bd987237e7d49c967566346fbd01',
address: '1br553PVnK6F5nyBtb4ju1owwBKdsep5c'
},
{
skHex: '744196d67ed78fe39009c71fbfd53e6ecca98353fbfe81ccba21b0703a69be9c01',
address: '16xVjkJ3nY62B9t9q3N9wY6hx1duAfwRZR'
},
{
address: '1HEjCcUjZXtbiDnCYviHLVZvSQsSZoDRFa',
skHex: '12f90d1b9e34d8df56f0dc6754a97ab4a2eb962918c281b1b552162438e313c001'
},
{
address: '16TaQJi78o4A3nKDSzswqZiX3bhecNuNBQ',
skHex: '58f7b29ee4a9a8b05855591b8a5405a0647c74c0a539515173adb9a32c964a9a01'
},
{
address: '15eNSvgT3UFvHSonajxFswnmHFifJPE5LB',
skHex: 'f5360140d18c6a34fbd2c45b98c1857c3fdad5454350249688a90efe936d475101'
},
{
address: '1Lt8ajRt7i8ajkQsYQZbk3ULCVTsSn2TNV',
skHex: '6eaed28d7f26f57fac925283aa0fe49c031028212863219f1c0141e4b0de2b2d01'
},
{
address: '1GvM4xksXrQsq4xPRck11toRLXVq9UYj2B',
skHex: '4c103c5c3de544c90f18a3ed29aaeebd33feedb1bb4f026df24aa3eddae826aa01'
}
]
function networkTests() {
test('insight-client', (t) => {
t.plan(5)
const mynet = new InsightClient('https://utxo.tester.com')
FetchMock.restore()
FetchMock.get('https://bitcoinfees.earn.com/api/v1/fees/recommended', { fastestFee: 1000 })
const txhashFound = 'txhash-found'
const blockHash = 'block-hash'
const txhashNotFound = 'txhash-not-found'
FetchMock.get(`https://utxo.tester.com/tx/${txhashNotFound}`,
{
body: JSON.stringify(
{
message: 'error fetching transaction details',
error: '-5: No information available about transaction'
}
),
status: 400
})
FetchMock.get(`https://utxo.tester.com/tx/${txhashFound}`,
{ blockHash })
FetchMock.get(`https://utxo.tester.com/block/${blockHash}`,
{ height: 300 })
FetchMock.get(`https://utxo.tester.com/addr/${testAddresses[0].address}/utxo`,
[{
value: 1,
satoshis: 1e8,
confirmations: 2,
txid: 'bar',
vout: 10
}])
FetchMock.get('https://utxo.tester.com/status',
{ blocks: 500 })
FetchMock.post('https://utxo.tester.com/tx/send',
{ body: 'true', status: 202 })
mynet.broadcastTransaction('test-transaction-text')
.then((response) => { t.ok(response, 'Should broadcast successfully') })
mynet.getBlockHeight()
.then((response) => { t.equal(response, 500, 'Should return block height') })
mynet.getTransactionInfo(txhashNotFound)
.then(() => t.ok(false, 'Should not return txinfo for not-found transaction.'))
.catch(() => t.ok(true, 'Should throw exception for not-found transaction.'))
mynet.getTransactionInfo(txhashFound)
.then(txInfo => t.equal(txInfo.block_height, 300, 'Should return txinfo.block_height'))
.catch(() => t.ok(false, 'Should not throw exception for a found transaction.'))
mynet.getNetworkedUTXOs(testAddresses[0].address)
.then((utxos) => {
t.deepEqual(utxos, [{
value: 1e8, confirmations: 2, tx_hash: 'bar', tx_output_n: 10
}])
})
})
test('bitcoind-client', (t) => {
t.plan(2)
const mynet = new BitcoindAPI('https://utxo.tester.com', { username: 'foo', password: 'bar' })
FetchMock.restore()
// @ts-ignore
FetchMock.postOnce({
name: 'Broadcast',
matcher: (url, opts) => (url === 'https://utxo.tester.com'
&& opts
&& opts.body.indexOf('importaddress') > 0),
response: {
body: {},
status: 200
}
})
// @ts-ignore
FetchMock.post({
name: 'Broadcast',
matcher: (url, opts) => (url === 'https://utxo.tester.com'
&& opts
&& opts.body.indexOf('listunspent') > 0),
response: {
body: JSON.stringify({ result: [] }),
status: 200
}
})
mynet.getNetworkedUTXOs(testAddresses[0].address)
.then((utxos) => {
t.deepEqual(utxos, [])
})
.catch((err) => {
console.log(err)
t.fail()
})
.then(() => mynet.getNetworkedUTXOs(testAddresses[0].address))
.then((utxos) => {
t.deepEqual(utxos, [])
})
.catch((err) => {
console.log(err)
t.fail()
})
})
}
function utilsTests() {
test('estimateTXBytes', (t) => {
t.plan(2)
const txHex = '010000000288e68977fab8038af07746e5d687652a44aa15f532509c202749d'
+ 'bad8a418733000000006b483045022100813ef3534b5030b544e5a5bd1db93f85dc89e2'
+ 'a565197a14784edff5564bd65b022008005213c6aa4c7ebe06cfd86bdaf3e662ae58371'
+ '896a0a841e81106fbe1507401210236b07942707a86ab666bb300b58d295d988ce9c3a3'
+ '38a0e08380dd98732fd4faffffffff3ba3edfd7a7b12b27ac72c3e67768f617fc81bc38'
+ '88a51323a9fb8aa4b1e5e4a000000006b483045022100d0c9b1594137186a1dc6c0b3a6'
+ 'cbe08399b57e2b8c953584f2ce20bef5642eb902206b9c88b8d2d311db26601acf3068d'
+ 'd118649ead4a1f93d029a52c0c61cb2cd2901210236b07942707a86ab666bb300b58d29'
+ '5d988ce9c3a338a0e08380dd98732fd4faffffffff030000000000000000296a2769643'
+ 'f363da95bc8d5203d1c07bd87c564a1e6395826cfdfe87cfd31ffa2a3b8101e3e93096f'
+ '2b7c150000000000001976a91441577ec99314a293acbc17d8152137cf4862f7f188ace'
+ '8030000000000001976a9142ebe7b4729185f68c7185c3c6af60fad1b6eeebf88ac00000000'
const tx = Transaction.fromHex(txHex)
tx.ins.forEach((x) => {
x.script = null
})
const actualLength = txHex.length / 2
const estimatedLength = estimateTXBytes(tx, 0, 0)
const tx2 = new TransactionBuilder()
tx2.addOutput(tx.outs[0].script, 0)
const estimatedLength2 = estimateTXBytes(tx2, 2, 2)
t.ok(estimatedLength >= actualLength - 5 && estimatedLength <= actualLength + 5,
`TX size estimate is roughly accurate? (estimated: ${estimatedLength},
actual: ${actualLength})`)
t.ok(estimatedLength2 >= actualLength - 5 && estimatedLength2 <= actualLength + 5,
`TX size estimate is roughly accurate? (estimated: ${estimatedLength2},
actual: ${actualLength})`)
})
test('encoding routines', (t) => {
t.plan(5)
t.equal(hash160(Buffer.from(
'99999566ahjhqwuywqehpzlzlzlzl09189128921jkjlqjosq'
)).toString('hex'),
'7ea1fa0f2003c31b015a72af9f4a5f104b5c2840')
t.equal(hash160(Buffer.from('1234')).toString('hex'),
'fd7a0d80999bedd76c9a0828057817fc6049a507')
t.equal(hash128(Buffer.from('999')).toString('hex'),
'83cf8b609de60036a8277bd0e9613575')
t.equal(hash128(Buffer.from('99999566ahjhqwuywqehpzlzlzlzl09189128921jkjlqjosqaaa'))
.toString('hex'),
'740ae7f18c939cf5e7c189a2c77a012f')
t.equal(decodeB40('0123456789abcdefghijklmnopqrstuvwxyz-_.+0123456789abcdefghi'
+ 'jklmnopqrstuvwxyz-_.+0123456789abcdefghijklmnopqrstuvwxyz-_'
+ '.+0123456789abcdefghijklmnopqrstuvwxyz-_.+0123456789abcdefg'
+ 'hijklmnopqrstuvwxyz-_.+'),
'384a516059e707615a1992d3101f6f346df3326d03ea7b673e3754078895db48da2d0'
+ 'fcb1bd89d618b0863bd8bac6db43a2d9cff5cc307310922d3cb8cf9c159d31c6a9c91'
+ '03197263a4e88f52d1b77dfc610e1b8dc9616ba6c2d0a1b792f0d73784c698c69f34a'
+ 'e5e7900753627a3ac87529035fb1a6cba7ce2e1df590941cf30a44557')
})
test('not enough UTXOs to fund', (t) => {
t.plan(1)
const txB = new TransactionBuilder()
txB.addOutput(testAddresses[0].address, 10000)
txB.addOutput(testAddresses[1].address, 0)
const utxos = [{
value: 50000,
tx_hash: '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b',
tx_output_n: 0
}]
t.throws(() => addUTXOsToFund(txB, utxos, 60000, 10),
/^NotEnoughFundsError: Not enough UTXOs to fund./,
'Errors when not enough value to fund')
})
test('addUTXOsToFundSingleUTXO', (t) => {
t.plan(2)
const txB = new TransactionBuilder()
txB.addOutput(testAddresses[0].address, 10000)
txB.addOutput(testAddresses[1].address, 0)
const utxos = [{
value: 50000,
tx_hash: '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b',
tx_output_n: 0
}]
const change = addUTXOsToFund(txB, utxos, 10000, 10)
t.equal(change, 38520) // gots to pay the fee!
t.equal((<any>txB).__TX.ins[0].hash.toString('hex'),
Buffer.from(Buffer.from(utxos[0].tx_hash, 'hex').reverse()).toString('hex'))
})
test('addUTXOsToFundTwoUTXOs', (t) => {
t.plan(3)
const txB = new TransactionBuilder()
txB.addOutput(testAddresses[0].address, 10000)
txB.addOutput(testAddresses[1].address, 0)
const utxos = [{
value: 50000,
tx_hash: '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b',
tx_output_n: 0
},
{
value: 10000,
tx_hash: '3387418aaddb4927209c5032f515aa442a6587d6e54677f08a03b8fa7789e688',
tx_output_n: 0
}]
const change = addUTXOsToFund(txB, utxos, 55000, 10)
t.ok(change <= 5000, `${(<any>txB).__TX.outs[1].value} should be less than 5k`)
t.equal((<any>txB).__TX.ins[0].hash.toString('hex'),
Buffer.from(Buffer.from(utxos[0].tx_hash, 'hex').reverse()).toString('hex'))
t.equal((<any>txB).__TX.ins[1].hash.toString('hex'),
Buffer.from(Buffer.from(utxos[1].tx_hash, 'hex').reverse()).toString('hex'))
})
test('modifiedTXSets', (t) => {
t.plan(11)
const txStarterHex = '01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323'
+ 'a9fb8aa4b1e5e4a000000006a473044022050176492b92c79ba'
+ '23fb815e62a7778ccb45a50ca11b8dabdbadc1828e6ba34002200ce770'
+ '82a072eba8d3ce49e6a316e6173c1f97d955064574fe620cc25002eadb'
+ '01210236b07942707a86ab666bb300b58d295d988ce9c3a338a0e08380'
+ 'dd98732fd4faffffffff030000000000000000296a2769643f363da95b'
+ 'c8d5203d1c07bd87c564a1e6395826cfdfe87cfd31ffa2a3b8101e3e93'
+ '096f2be02c0000000000001976a91441577ec99314a293acbc17d81521'
+ '37cf4862f7f188ac39050000000000001976a9142ebe7b4729185f68c7'
+ '185c3c6af60fad1b6eeebf88ac00000000'
const txStarter = Transaction.fromHex(txStarterHex)
const txHash = '22a024f16944d2f568de4a613566fcfab53b86d37f1903668d399f9a366883de'
t.equal(Buffer.from(txStarter.getHash().reverse()).toString('hex'), txHash)
const usedTXHash = '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b'
const utxoValues = [287825, 287825]
const utxoSet1 = [{
value: utxoValues[0],
tx_hash_big_endian: usedTXHash,
tx_output_n: 0
},
{
value: utxoValues[1],
tx_hash_big_endian:
'3387418aaddb4927209c5032f515aa442a6587d6e54677f08a03b8fa7789e688',
tx_output_n: 0
}]
const utxoSet2 = []
config.network.modifyUTXOSetFrom(txStarterHex)
const testAddress1 = '16xVjkJ3nY62B9t9q3N9wY6hx1duAfwRZR'
const testAddress2 = '15GAGiT2j2F1EzZrvjk3B8vBCfwVEzQaZx'
FetchMock.restore()
FetchMock.get('https://bitcoinfees.earn.com/api/v1/fees/recommended', { fastestFee: 1000 })
FetchMock.get(`https://blockchain.info/unspent?format=json&active=${testAddress1}&cors=true`,
{ unspent_outputs: utxoSet1 })
FetchMock.get(`https://blockchain.info/unspent?format=json&active=${testAddress2}&cors=true`,
{ unspent_outputs: utxoSet2 })
Promise.all([config.network.getUTXOs(testAddress1),
config.network.getUTXOs(testAddress2)])
.then(([utxos1, utxos2]) => {
t.equal(utxos1.length, 2)
t.equal(utxos2.length, 1)
t.ok(utxos1.find(x => x.tx_hash === txHash && x.value === 11488),
'UTXO set should include the new transaction\'s outputs')
t.ok(utxos2.find(x => x.tx_hash === txHash && x.value === 1337),
'UTXO set should include the new transaction\'s outputs')
t.ok(!utxos1.find(x => x.tx_hash === usedTXHash),
'UTXO set shouldn\'t include the transaction\'s spent input')
})
.then(() => {
config.network.resetUTXOs(testAddress1)
config.network.resetUTXOs(testAddress2)
return Promise.all([config.network.getUTXOs(testAddress1),
config.network.getUTXOs(testAddress2)])
})
.then(([utxos1, utxos2]) => {
t.equal(utxos1.length, 2)
t.equal(utxos2.length, 0)
t.ok(!utxos1.find(x => x.tx_hash === txHash && x.value === 11488),
'UTXO set should not include the new transaction\'s outputs after reset')
t.ok(!utxos2.find(x => x.tx_hash === txHash && x.value === 1337),
'UTXO set should not include the new transaction\'s outputs after reset')
t.ok(utxos1.find(x => x.tx_hash === usedTXHash),
'UTXO set should include the transaction\'s input after reset')
})
})
}
function transactionTests() {
const utxoValues = [288000, 287825, 287825]
const namespaceUtxoValues = [288000, 287825, 287825]
const tokenUtxoValues = [288000, 287825, 287825]
const BURN_AMT = 6500
const NAMESPACE_PRICE = { units: 'STACKS', amount: '6400000000' }
const BURN_ADDR = '15GAGiT2j2F1EzZrvjk3B8vBCfwVEzQaZx'
const NAMESPACE_BURN_ADDR = '1111111111111111111114oLvT2'
const utxoSet = [
{
value: utxoValues[0],
tx_hash_big_endian: '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b',
tx_output_n: 0
},
{
value: utxoValues[1],
tx_hash_big_endian: '3387418aaddb4927209c5032f515aa442a6587d6e54677f08a03b8fa7789e688',
tx_output_n: 0
},
{
value: utxoValues[2],
tx_hash_big_endian: 'ffffffffffdb4927209c5032f515aa442a6587d6e54677f08a03b8fa7789e688',
tx_output_n: 2
}
]
const utxoSet2 = [
{
value: 5500,
tx_hash_big_endian: 'ffffffffaab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdedffff',
tx_output_n: 0
}
]
const namespaceUtxoSet = [
{
value: namespaceUtxoValues[0],
tx_hash_big_endian: '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33c',
tx_output_n: 0
},
{
value: namespaceUtxoValues[1],
tx_hash_big_endian: '3387418aaddb4927209c5032f515aa442a6587d6e54677f08a03b8fa7789e689',
tx_output_n: 0
},
{
value: namespaceUtxoValues[2],
tx_hash_big_endian: 'ffffffffffdb4927209c5032f515aa442a6587d6e54677f08a03b8fa7789e689',
tx_output_n: 2
}
]
const namespaceUtxoSet2 = [
{
value: 654321,
tx_hash_big_endian: 'ffffffffaab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdee0000',
tx_output_n: 0
}
]
const tokenUtxoSet = [
{
value: tokenUtxoValues[0],
tx_hash_big_endian: '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33c',
tx_output_n: 0
},
{
value: tokenUtxoValues[1],
tx_hash_big_endian: '3387418aaddb4927209c5032f515aa442a6587d6e54677f08a03b8fa7789e689',
tx_output_n: 0
},
{
value: tokenUtxoValues[2],
tx_hash_big_endian: 'ffffffffffdb4927209c5032f515aa442a6587d6e54677f08a03b8fa7789e689',
tx_output_n: 2
}
]
const tokenUtxoSet2 = [
{
value: 1654321,
tx_hash_big_endian: 'fefefefeaab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdee0000',
tx_output_n: 0
}
]
function setupMocks() {
FetchMock.restore()
FetchMock.get('https://bitcoinfees.earn.com/api/v1/fees/recommended', { fastestFee: 1000 })
FetchMock.get(`https://blockchain.info/unspent?format=json&active=${testAddresses[1].address}&cors=true`,
{ unspent_outputs: utxoSet })
FetchMock.get(`https://blockchain.info/unspent?format=json&active=${testAddresses[0].address}&cors=true`,
{ unspent_outputs: utxoSet2 })
FetchMock.get(`https://blockchain.info/unspent?format=json&active=${testAddresses[2].address}&cors=true`,
{ unspent_outputs: namespaceUtxoSet })
FetchMock.get(`https://blockchain.info/unspent?format=json&active=${testAddresses[3].address}&cors=true`,
{ unspent_outputs: namespaceUtxoSet2 })
FetchMock.get(`https://blockchain.info/unspent?format=json&active=${testAddresses[4].address}&cors=true`,
{ unspent_outputs: tokenUtxoSet })
FetchMock.get(`https://blockchain.info/unspent?format=json&active=${testAddresses[5].address}&cors=true`,
{ unspent_outputs: tokenUtxoSet2 })
FetchMock.get('https://core.blockstack.org/v2/prices/names/foo.test',
{ name_price: { units: 'BTC', amount: String(BURN_AMT) } })
FetchMock.get('https://core.blockstack.org/v2/prices/names/bar.test2',
{ name_price: { units: 'STACKS', amount: String(BURN_AMT) } })
FetchMock.get('https://core.blockstack.org/v2/prices/namespaces/hello',
NAMESPACE_PRICE)
FetchMock.get('https://core.blockstack.org/v1/namespaces/test',
{ version: 2, address: BURN_ADDR, reveal_block: 600 })
FetchMock.get('https://core.blockstack.org/v1/namespaces/test2',
{ version: 3, address: BURN_ADDR, reveal_block: 600 })
FetchMock.get('https://core.blockstack.org/v1/blockchains/bitcoin/consensus',
{ consensus_hash: 'dfe87cfd31ffa2a3b8101e3e93096f2b' })
FetchMock.get('https://blockchain.info/latestblock?cors=true',
{ height: 601 })
}
function getInputVals(inputTXArgument, utxoSets = utxoSet) {
const utxosAll = utxoSets.concat()
return inputTXArgument.ins.reduce((agg, x) => {
const inputTX = utxosAll.find(
y => Buffer.from(Buffer.from(y.tx_hash_big_endian, 'hex')
.reverse()).compare(x.hash) === 0
)
if (inputTX) {
return agg + inputTX.value
} else {
return agg
}
}, 0)
}
test('address coercion', (t) => {
t.plan(8)
const stashed = config.network.layer1
try {
const singleSigAddressMain = '1EJh2y3xKUwFjJ8v29a2NRruPJ71neozEE'
const singleSigAddressTest = 'mtpeL28w8WNWWQcXjiYQCM5EFHhijXMF62'
const multiSigAddressTest = '2N6GHvciC9M4ze5QXpW2jZxjf5trnPFsyqZ'
const multiSigAddressMain = '3Ei5rsnAXtZeSHmz9NQrx1kPsYecbfdKiy'
config.network.layer1 = networks.testnet
t.equal(config.network.coerceAddress(singleSigAddressMain), singleSigAddressTest)
t.equal(config.network.coerceAddress(singleSigAddressTest), singleSigAddressTest)
t.equal(config.network.coerceAddress(multiSigAddressMain), multiSigAddressTest)
t.equal(config.network.coerceAddress(multiSigAddressTest), multiSigAddressTest)
config.network.layer1 = networks.bitcoin
t.equal(config.network.coerceAddress(singleSigAddressMain), singleSigAddressMain)
t.equal(config.network.coerceAddress(singleSigAddressTest), singleSigAddressMain)
t.equal(config.network.coerceAddress(multiSigAddressMain), multiSigAddressMain)
t.equal(config.network.coerceAddress(multiSigAddressTest), multiSigAddressMain)
} finally {
config.network.layer1 = stashed
}
})
test('build incomplete', (t) => {
setupMocks()
t.plan(2)
const getAddress = () => Promise.resolve(testAddresses[2].address)
const signTransaction = () => Promise.resolve()
const nullSigner = { getAddress, signTransaction }
return transactions.makeNamespacePreorder('hello',
testAddresses[3].address,
<any>nullSigner)
.then(() => {
t.fail('Should have failed to build unsigned TX.')
})
.catch(() => {
t.pass('Should have failed to build unsigned TX.')
})
.then(() => transactions.makeNamespacePreorder('hello',
testAddresses[3].address,
<any>nullSigner,
true))
.then((txhex) => {
t.ok(txhex, 'Should have built incomplete TX when buildIncomplete = true')
})
.catch((err) => {
t.fail(`Should have built incomplete TX when buildIncomplete = true: Error: ${err}`)
})
})
test('build and fund namespace preorder', (t) => {
t.plan(6)
setupMocks()
Promise.all(
[transactions.estimateNamespacePreorder('hello',
testAddresses[3].address,
testAddresses[2].address, 2),
transactions.makeNamespacePreorder('hello',
testAddresses[3].address,
testAddresses[2].skHex)]
)
.then(([estimatedCost, hexTX]) => {
t.ok(hexTX)
const tx = Transaction.fromHex(hexTX)
const txLen = hexTX.length / 2
const outputVals = sumOutputValues(tx)
const inputVals = getInputVals(tx, namespaceUtxoSet)
const fee = inputVals - outputVals
const burnAddress = bjsAddress.fromOutputScript(tx.outs[2].script, networks.bitcoin)
const change = (tx.outs[1] as TxOutput).value
t.equal(inputVals - change,
estimatedCost - 5500, 'Estimated cost should be +DUST_MINIMUM of actual.')
t.equal(burnAddress, NAMESPACE_BURN_ADDR, `Burn address should be ${NAMESPACE_BURN_ADDR}`)
t.equal((tx.outs[2] as TxOutput).value, 5500, 'Output should have paid +DUST_MINIMUM for namespace')
t.equal(tx.ins.length, 2, 'Should use 2 utxos for the payer')
t.ok(Math.floor(fee / txLen) > 990 && Math.floor(fee / txLen) < 1010,
`Paid fee of ${fee} for tx of length ${txLen} should equal 1k satoshi/byte`)
})
.catch((err) => { console.log(err.stack); throw err })
})
test('build and fund namespace reveal', (t) => {
t.plan(4)
setupMocks()
const ns = new transactions.AladinNamespace('hello')
ns.setVersion(3)
ns.setLifetime(52595)
ns.setCoeff(4)
ns.setBase(4)
ns.setBuckets([6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
ns.setNonalphaDiscount(10)
ns.setNoVowelDiscount(10)
Promise.all(
[transactions.estimateNamespaceReveal(ns,
testAddresses[3].address,
testAddresses[2].address),
transactions.makeNamespaceReveal(ns,
testAddresses[3].address,
testAddresses[2].skHex)]
)
.then(([estimatedCost, hexTX]) => {
const tx = Transaction.fromHex(hexTX)
const txLen = hexTX.length / 2
const outputVals = sumOutputValues(tx)
const inputVals = getInputVals(tx, namespaceUtxoSet)
const fee = inputVals - outputVals
// change address is the 3rd output usually...
const change = (tx.outs[2] as TxOutput).value
t.equal(bjsAddress.fromOutputScript(tx.outs[2].script, networks.bitcoin), testAddresses[2].address,
'Payer change should be third output')
t.equal(inputVals - change, estimatedCost, 'Estimated cost should match actual.')
t.equal(tx.ins.length, 1, 'Should use 1 utxo for the payer')
t.ok(Math.floor(fee / txLen) > 990 && Math.floor(fee / txLen) < 1010,
`Paid fee of ${fee} for tx of length ${txLen} should equal 1k satoshi/byte`)
})
.catch((err) => { console.log(err.stack); throw err })
})
test('build and fund name import', (t) => {
t.plan(4)
setupMocks()
Promise.all(
[transactions.estimateNameImport(
'import.hello', '151nahdGD9Dxd7xpwPeBECn5iEi4Thb7Rv',
'cabdbc18ece9ffb6a7378faa4ac4ce58dcaaf575'
),
transactions.makeNameImport(
'import.hello', '151nahdGD9Dxd7xpwPeBECn5iEi4Thb7Rv',
'cabdbc18ece9ffb6a7378faa4ac4ce58dcaaf575', testAddresses[3].skHex
)]
)
.then(([estimatedCost, hexTX]) => {
t.ok(hexTX)
const tx = Transaction.fromHex(hexTX)
const txLen = hexTX.length / 2
const outputVals = sumOutputValues(tx)
const inputVals = getInputVals(tx, namespaceUtxoSet2)
const fee = inputVals - outputVals
const change = (tx.outs[3] as TxOutput).value
t.equal(inputVals - change,
estimatedCost, 'Estimated cost should match actual.')
t.equal(tx.ins.length, 1, 'Should use 1 utxo for the payer')
t.ok(Math.floor(fee / txLen) > 990 && Math.floor(fee / txLen) < 1010,
`Paid fee of ${fee} for tx of length ${txLen} should equal 1k satoshi/byte`)
})
.catch((err) => { console.log(err.stack); throw err })
})
test('build and fund namespace ready', (t) => {
t.plan(4)
setupMocks()
Promise.all(
[transactions.estimateNamespaceReady('hello'),
transactions.makeNamespaceReady('hello',
testAddresses[3].skHex)]
)
.then(([estimatedCost, hexTX]) => {
t.ok(hexTX)
const tx = Transaction.fromHex(hexTX)
const txLen = hexTX.length / 2
const outputVals = sumOutputValues(tx)
const inputVals = getInputVals(tx, namespaceUtxoSet2)
const fee = inputVals - outputVals
const change = (tx.outs[1] as TxOutput).value
t.equal(inputVals - change,
estimatedCost, 'Estimated cost should match actual.')
t.equal(tx.ins.length, 1, 'Should use 1 utxo for the payer')
t.ok(Math.floor(fee / txLen) > 990 && Math.floor(fee / txLen) < 1010,
`Paid fee of ${fee} for tx of length ${txLen} should equal 1k satoshi/byte`)
})
.catch((err) => { console.log(err.stack); throw err })
})
test('build and fund announce', (t) => {
t.plan(4)
setupMocks()
Promise.all(
[transactions.estimateAnnounce('53bb740c47435a51b07ecf0b9e086a2ad3c12c1d'),
transactions.makeAnnounce(
'53bb740c47435a51b07ecf0b9e086a2ad3c12c1d', testAddresses[3].skHex
)]
)
.then(([estimatedCost, hexTX]) => {
t.ok(hexTX)
const tx = Transaction.fromHex(hexTX)
const txLen = hexTX.length / 2
const outputVals = sumOutputValues(tx)
const inputVals = getInputVals(tx, namespaceUtxoSet2)
const fee = inputVals - outputVals
const change = (tx.outs[1] as TxOutput).value
t.equal(inputVals - change,
estimatedCost, 'Estimated cost should match actual.')
t.equal(tx.ins.length, 1, 'Should use 1 utxo for the payer')
t.ok(Math.floor(fee / txLen) > 990 && Math.floor(fee / txLen) < 1010,
`Paid fee of ${fee} for tx of length ${txLen} should equal 1k satoshi/byte`)
})
.catch((err) => { console.log(err.stack); throw err })
})
test('build and fund token transfer', (t) => {
t.plan(6)
setupMocks()
Promise.all([
transactions.estimateTokenTransfer(testAddresses[1].address,
'STACKS',
new BN('123'),
'hello world!', 2),
transactions.makeTokenTransfer(testAddresses[1].address,
'STACKS',
new BN('123'),
'hello world!',
testAddresses[4].skHex)])
.then(([estimatedCost, hexTX]) => {
t.ok(hexTX)
const tx = Transaction.fromHex(hexTX)
const txLen = hexTX.length / 2
const outputVals = sumOutputValues(tx)
const inputVals = getInputVals(tx, tokenUtxoSet)
const fee = inputVals - outputVals
const change = (tx.outs[2] as TxOutput).value
const recipientAddr = bjsAddress.fromOutputScript(tx.outs[1].script, networks.bitcoin)
console.log(outputVals)
console.log(inputVals)
console.log(fee)
console.log(change)
console.log(recipientAddr)
t.equal(inputVals - change, estimatedCost, 'Estimated cost should be equal')
t.equal(recipientAddr, testAddresses[1].address, 'Recipient address is correct')
t.equal((tx.outs[1] as TxOutput).value, 5500, 'Recipient address should have +DUST_MINIMUM')
t.equal(tx.ins.length, 2, 'Should use 2 utxos from the payer')
t.ok(Math.floor(fee / txLen) > 990 && Math.floor(fee / txLen) < 1010,
`Paid fee of ${fee} for tx length ${txLen} should equal 1K satoshi/byte`)
})
})
test('build and fund token transfer with separate funder', (t) => {
t.plan(6)
setupMocks()
Promise.all([
transactions.estimateTokenTransfer(testAddresses[1].address,
'STACKS',
new BN('123'),
'hello world!', 2, 2),
transactions.makeTokenTransfer(testAddresses[1].address,
'STACKS',
new BN('123'),
'hello world!',
testAddresses[4].skHex,
testAddresses[5].skHex)])
.then(([estimatedCost, hexTX]) => {
t.ok(hexTX)
const tx = Transaction.fromHex(hexTX)
const txLen = hexTX.length / 2
const outputVals = sumOutputValues(tx)
const inputVals = getInputVals(tx, tokenUtxoSet) + getInputVals(tx, tokenUtxoSet2)
const fee = inputVals - outputVals
const change = (tx.outs[3] as TxOutput).value
const recipientAddr = bjsAddress.fromOutputScript(tx.outs[1].script, networks.bitcoin)
const funderInputVals = getInputVals(tx, tokenUtxoSet2)
t.equal(funderInputVals - change, estimatedCost, 'Estimated cost should be equal')
t.equal(recipientAddr, testAddresses[1].address, 'Recipient address is correct')
t.equal((tx.outs[1] as TxOutput).value, 5500, 'Recipient address should have +DUST_MINIMUM')
t.equal(tx.ins.length, 2, 'Should use 2 utxos from (token-payer, btc-payer)')
t.ok(Math.floor(fee / txLen) > 990 && Math.floor(fee / txLen) < 1010,
`Paid fee of ${fee} for tx length ${txLen} should equal 1K satoshi/byte`)
})
.catch((err) => {
console.log(err)
t.fail(err)
})
})
test('build and fund preorder', (t) => {
t.plan(6)
setupMocks()
Promise.all(
[transactions.estimatePreorder('foo.test',
testAddresses[0].address,
testAddresses[1].address),
transactions.makePreorder('foo.test',
testAddresses[0].address,
testAddresses[1].skHex)]
)
.then(([estimatedCost, hexTX]) => {
t.ok(hexTX)
const tx = Transaction.fromHex(hexTX)
const txLen = hexTX.length / 2
const outputVals = sumOutputValues(tx)
const inputVals = getInputVals(tx)
const fee = inputVals - outputVals
const burnAddress = bjsAddress.fromOutputScript(tx.outs[2].script, networks.bitcoin)
const change = (tx.outs[1] as TxOutput).value
t.equal(inputVals - change,
estimatedCost - 5500, 'Estimated cost should be +DUST_MINIMUM of actual.')
t.equal(burnAddress, BURN_ADDR, `Burn address should be ${BURN_ADDR}`)
t.equal((tx.outs[2] as TxOutput).value, BURN_AMT, `Output should have funded name price ${BURN_AMT}`)
t.equal(tx.ins.length, 1, 'Should use 1 utxo for the payer')
t.ok(Math.floor(fee / txLen) > 990 && Math.floor(fee / txLen) < 1010,
`Paid fee of ${fee} for tx of length ${txLen} should equal 1k satoshi/byte`)
})
.catch((err) => { console.log(err.stack); throw err })
})
test('build and fund preorder with stacks', (t) => {
t.plan(6)
setupMocks()
Promise.all(
[transactions.estimatePreorder('bar.test2',
testAddresses[0].address,
testAddresses[1].address,
2),
transactions.makePreorder('bar.test2',
testAddresses[0].address,
testAddresses[1].skHex)]
)
.then(([estimatedCost, hexTX]) => {
t.ok(hexTX)
const tx = Transaction.fromHex(hexTX)
const txLen = hexTX.length / 2
const outputVals = sumOutputValues(tx)
const inputVals = getInputVals(tx)
const fee = inputVals - outputVals
const burnAddress = bjsAddress.fromOutputScript(tx.outs[2].script, networks.bitcoin)
const change = (tx.outs[1] as TxOutput).value
t.equal(inputVals - change,
estimatedCost - 5500, 'Estimated cost should be +DUST_MINIMUM of actual.')
t.equal(burnAddress, NAMESPACE_BURN_ADDR, `Burn address should be ${NAMESPACE_BURN_ADDR}`)
t.equal((tx.outs[2] as TxOutput).value, 5500, 'Output should not have burned more than +DUST_MINIMUM')
t.equal(tx.ins.length, 2, 'Should use 2 utxos for the payer')
t.ok(Math.floor(fee / txLen) > 990 && Math.floor(fee / txLen) < 1010,
`Paid fee of ${fee} for tx of length ${txLen} should equal 1k satoshi/byte`)
})
.catch((err) => { console.log(err.stack); throw err })
})
test('build and fund register', (t) => {
t.plan(4)
setupMocks()
Promise.all(
[transactions.estimateRegister('foo.test',
testAddresses[0].address,
testAddresses[1].address, true, 2),
transactions.makeRegister('foo.test',
testAddresses[0].address,
testAddresses[1].skHex, 'hello world')]
)
.then(([estimatedCost, hexTX]) => {
const tx = Transaction.fromHex(hexTX)
const txLen = hexTX.length / 2
const outputVals = sumOutputValues(tx)
const inputVals = getInputVals(tx)
const fee = inputVals - outputVals
// change address is the 3rd output usually...
const change = (tx.outs[2] as TxOutput).value
t.equal(bjsAddress.fromOutputScript(tx.outs[2].script, networks.bitcoin), testAddresses[1].address,
'Payer change should be third output')
t.equal(inputVals - change, estimatedCost, 'Estimated cost should match actual.')
t.equal(tx.ins.length, 2, 'Should use both payer utxos')
t.equal(Math.floor(fee / txLen), 1000,
`Paid fee of ${fee} for tx of length ${txLen} should equal 1k satoshi/byte`)
})
.catch((err) => { console.log(err.stack); throw err })
})
test('build and fund update', (t) => {
t.plan(5)
setupMocks()
Promise.all(
[transactions.estimateUpdate('foo.test',
testAddresses[0].address,
testAddresses[1].address,
3),
transactions.makeUpdate('foo.test',
testAddresses[0].skHex,
testAddresses[1].skHex,
'hello world')]
)
.then(([estimatedCost, hexTX]) => {
const tx = Transaction.fromHex(hexTX)
const txLen = hexTX.length / 2
const outputVals = sumOutputValues(tx)
const inputVals = getInputVals(tx)
const fee = inputVals - outputVals
// payer change address is the 3rd output...
const changeOut = tx.outs[2]
const ownerChange = tx.outs[1]
const change = (changeOut as TxOutput).value
t.equal(bjsAddress.fromOutputScript(changeOut.script, networks.bitcoin), testAddresses[1].address,
'Owner change should be second output')
t.equal(bjsAddress.fromOutputScript(ownerChange.script, networks.bitcoin), testAddresses[0].address,
'Payer change should be third output')
t.equal(inputVals - change, estimatedCost, 'Estimated cost should match actual.')
t.equal(tx.ins.length, 4, 'Should use all payer utxos and one owner utxo')
t.ok(Math.floor(fee / txLen) > 990 && Math.floor(fee / txLen) < 1010,
`Paid fee of ${fee} for tx of length ${txLen} should roughly equal 1k satoshi/byte`)
})
.catch((err) => { console.log(err.stack); throw err })
})
test('build and fund transfer', (t) => {
t.plan(6)
setupMocks()
Promise.all(
[transactions.estimateTransfer('foo.test',
testAddresses[2].address,
testAddresses[0].address,
testAddresses[1].address,
3),
transactions.makeTransfer('foo.test',
testAddresses[2].address,
testAddresses[0].skHex,
testAddresses[1].skHex)]
)
.then(([estimatedCost, hexTX]) => {
const tx = Transaction.fromHex(hexTX)
const txLen = hexTX.length / 2
const outputVals = sumOutputValues(tx)
const inputVals = getInputVals(tx)
const fee = inputVals - outputVals
// payer change address is the 4th output...
const changeOut = tx.outs[3]
// old owner change address is the 3rd output
const ownerChange = tx.outs[2]
const change = (changeOut as TxOutput).value
t.equal(bjsAddress.fromOutputScript(tx.outs[1].script, networks.bitcoin), testAddresses[2].address,
'New owner should be second output')
t.equal(bjsAddress.fromOutputScript(ownerChange.script, networks.bitcoin), testAddresses[0].address,
'Prior owner should be third output')
t.equal(bjsAddress.fromOutputScript(changeOut.script, networks.bitcoin), testAddresses[1].address,
'Payer change should be fourth output')
t.equal(inputVals - change, estimatedCost, 'Estimated cost should match actual.')
t.equal(tx.ins.length, 4, 'Should use both payer utxos and one owner utxo')
t.ok(Math.floor(fee / txLen) > 990 && Math.floor(fee / txLen) < 1010,
`Paid fee of ${fee} for tx of length ${txLen} should roughly equal 1k satoshi/byte`)
})
.catch((err) => { console.log(err.stack); throw err })
})
test('build and fund revoke', (t) => {
t.plan(4)
setupMocks()
Promise.all(
[transactions.estimateRevoke('foo.test',
testAddresses[0].address,
testAddresses[1].address, 2),
transactions.makeRevoke('foo.test',
testAddresses[0].skHex,
testAddresses[1].skHex)]
)
.then(([estimatedCost, hexTX]) => {
const tx = Transaction.fromHex(hexTX)
const txLen = hexTX.length / 2
const outputVals = sumOutputValues(tx)
const inputVals = getInputVals(tx)
const fee = inputVals - outputVals
// change address is the 3rd output usually...
const change = (tx.outs[2] as TxOutput).value
t.equal(bjsAddress.fromOutputScript(tx.outs[2].script, networks.bitcoin), testAddresses[1].address,
'Payer change should be third output')
t.equal(inputVals - change, estimatedCost, 'Estimated cost should match actual.')
t.equal(tx.ins.length, 3, 'Should use both payer utxos')
t.ok(Math.floor(fee / txLen) > 990 && Math.floor(fee / txLen) < 1010,
`Paid fee of ${fee} for tx of length ${txLen} should equal 1k satoshi/byte`)
})
.catch((err) => { console.log(err.stack); throw err })
})
test('fund bitcoin spends', (t) => {
t.plan(14)
setupMocks()
const TEST1_AMOUNT = 250000
const TEST2_AMOUNT = 80000
const TEST3_AMOUNT = 288000 + 287825 + 287825
const TEST4_AMOUNT = 288000 + 287825 + 287825 + 1
transactions.makeBitcoinSpend(testAddresses[2].address,
testAddresses[1].skHex,
TEST1_AMOUNT)
.then((hexTX) => {
const tx = Transaction.fromHex(hexTX)
const txLen = hexTX.length / 2
const outputVals = sumOutputValues(tx)
const inputVals = getInputVals(tx)
const fee = inputVals - outputVals
t.equal(tx.ins.length, 1, 'Should use 1 input')
t.equal(tx.outs.length, 2, 'Should have a change output')
const changeOut = tx.outs[1]
t.equal(bjsAddress.fromOutputScript(changeOut.script, networks.bitcoin), testAddresses[1].address,
'Must be correct change address')
t.ok(Math.abs(1000 * txLen - fee) <= 2000,
`Fee should be roughly correct: Actual fee: ${fee}, expected: ${1000 * txLen}`)
t.equal(inputVals - (changeOut as TxOutput).value, TEST1_AMOUNT, 'Should fund correct amount')
})
.then(() => transactions.makeBitcoinSpend(testAddresses[2].address,
testAddresses[1].skHex,
TEST2_AMOUNT))
.then(() => t.fail('Should reject with InvalidAmountError if not enough coin to fund fees.'))
.catch(err => t.equal(
err.name, 'InvalidAmountError',
'Should reject with InvalidAmountError if not enough coin to fund fees.'
))
.then(() => transactions.makeBitcoinSpend(testAddresses[2].address,
testAddresses[1].skHex,
TEST3_AMOUNT))
.then((hexTX) => {
const tx = Transaction.fromHex(hexTX)
const txLen = hexTX.length / 2
const outputVals = sumOutputValues(tx)
const inputVals = getInputVals(tx)
const fee = inputVals - outputVals
t.equal(tx.ins.length, 3, 'Should use 3 inputs')
t.equal(tx.outs.length, 1, 'Should not have a change output')
t.ok(Math.abs(1000 * txLen - fee) <= 2000, 'Fee should be roughly correct.')
t.equal(outputVals + fee, TEST3_AMOUNT, 'Should fund correct amount')
})
.then(() => transactions.makeBitcoinSpend(testAddresses[2].address,
testAddresses[1].skHex,
TEST4_AMOUNT))
.then((hexTX) => {
const tx = Transaction.fromHex(hexTX)
const txLen = hexTX.length / 2
const outputVals = sumOutputValues(tx)
const inputVals = getInputVals(tx)
const fee = inputVals - outputVals
t.equal(tx.ins.length, 3, 'Should use 3 inputs')
t.equal(tx.outs.length, 1, 'Should not have a change output')
t.ok(Math.abs(1000 * txLen - fee) <= 2000, 'Fee should be roughly correct.')
t.equal(outputVals + fee, TEST3_AMOUNT, 'Should fund maximum amount')
})
})
test('build and fund renewal', (t) => {
t.plan(7)
setupMocks()
Promise.all(
[transactions.estimateRenewal('foo.test',
testAddresses[2].address,
testAddresses[0].address,
testAddresses[1].address,
true,
3),
transactions.makeRenewal('foo.test',
testAddresses[2].address,
testAddresses[0].skHex,
testAddresses[1].skHex,
'hello world')]
)
.then(([estimatedCost, hexTX]) => {
const tx = Transaction.fromHex(hexTX)
const txLen = hexTX.length / 2
const outputVals = sumOutputValues(tx)
const inputVals = getInputVals(tx)
const fee = inputVals - outputVals
// payer change address is the 5th output...
const changeOut = tx.outs[4]
// old owner change address is the 3rd output
const ownerChange = tx.outs[2]
const change = (changeOut as TxOutput).value
t.equal(bjsAddress.fromOutputScript(tx.outs[1].script, networks.bitcoin), testAddresses[2].address,
'New owner should be second output')
t.equal(bjsAddress.fromOutputScript(ownerChange.script, networks.bitcoin), testAddresses[0].address,
'Prior owner should be third output')
t.equal(bjsAddress.fromOutputScript(tx.outs[3].script, networks.bitcoin), BURN_ADDR,
'Burn address should be fourth output')
t.equal(bjsAddress.fromOutputScript(changeOut.script, networks.bitcoin), testAddresses[1].address,
'Payer change should be fifth output')
t.equal(inputVals - change, estimatedCost, 'Estimated cost should be accurate.')
t.equal(tx.ins.length, 4, 'Should use both payer utxos and one owner utxo')
t.ok(Math.floor(fee / txLen) > 990 && Math.floor(fee / txLen) < 1010,
`Paid fee of ${fee} for tx of length ${txLen} should roughly equal 1k satoshi/byte`)
})
.catch((err) => { console.log(err.stack); throw err })
})
test('build and fund renewal with stacks', (t) => {
t.plan(8)
setupMocks()
Promise.all(
[transactions.estimateRenewal('bar.test2',
testAddresses[2].address,
testAddresses[0].address,
testAddresses[1].address,
true,
3),
transactions.makeRenewal('bar.test2',
testAddresses[2].address,
testAddresses[0].skHex,
testAddresses[1].skHex,
'hello world')]
)
.then(([estimatedCost, hexTX]) => {
const tx = Transaction.fromHex(hexTX)
const txLen = hexTX.length / 2
const outputVals = sumOutputValues(tx)
const inputVals = getInputVals(tx)
const fee = inputVals - outputVals
// payer change address is the 5th output...
const changeOut = tx.outs[4]
// old owner change address is the 3rd output
const ownerChange = tx.outs[2]
const change = (changeOut as TxOutput).value
t.equal(bjsAddress.fromOutputScript(tx.outs[1].script, networks.bitcoin), testAddresses[2].address,
'New owner should be second output')
t.equal(bjsAddress.fromOutputScript(ownerChange.script, networks.bitcoin), testAddresses[0].address,
'Prior owner should be third output')
t.equal(bjsAddress.fromOutputScript(tx.outs[3].script, networks.bitcoin), NAMESPACE_BURN_ADDR,
'Burn address should be fourth output, and it should be 11111....')
t.equal(bjsAddress.fromOutputScript(changeOut.script, networks.bitcoin), testAddresses[1].address,
'Payer change should be fifth output')
t.equal(inputVals - change, estimatedCost, 'Estimated cost should be accurate.')
t.equal(tx.ins.length, 4, 'Should use both payer utxos and one owner utxo')
t.equal((tx.outs[3] as TxOutput).value, 5500, 'Output should not have burned more than +DUST_MINIMUM')
t.ok(Math.floor(fee / txLen) > 990 && Math.floor(fee / txLen) < 1010,
`Paid fee of ${fee} for tx of length ${txLen} should roughly equal 1k satoshi/byte`)
})
.catch((err) => { console.log(err.stack); throw err })
})
test('use alternative magic bytes', (t) => {
t.plan(24)
setupMocks()
const ns = new transactions.AladinNamespace('hello')
ns.setVersion(3)
ns.setLifetime(52595)
ns.setCoeff(4)
ns.setBase(4)
ns.setBuckets([6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
ns.setNonalphaDiscount(10)
ns.setNoVowelDiscount(10)
Promise.resolve().then(() => {
network.defaults.MAINNET_DEFAULT.MAGIC_BYTES = 'di'
return Promise.all([
transactions.makeNamespacePreorder('hello',
testAddresses[3].address,
testAddresses[2].skHex),
transactions.makeNamespaceReveal(ns,
testAddresses[3].address,
testAddresses[2].skHex),
transactions.makeNameImport(
'import.hello', '151nahdGD9Dxd7xpwPeBECn5iEi4Thb7Rv',
'cabdbc18ece9ffb6a7378faa4ac4ce58dcaaf575', testAddresses[3].skHex
),
transactions.makeNamespaceReady('hello',
testAddresses[3].skHex),
transactions.makePreorder('foo.test',
testAddresses[0].address,
testAddresses[1].skHex),
transactions.makeRegister('foo.