'use strict'; const assert = require('bsert'); const Network = require('hsd/lib/protocol/network'); const FullNode = require('hsd/lib/node/fullnode'); const MTX = require('hsd/lib/primitives/mtx'); const Address = require('hsd/lib/primitives/address'); const Output = require('hsd/lib/primitives/output'); const Script = require('hsd/lib/script/script'); const rules = require('hsd/lib/covenants/rules'); const {types} = rules; const {Resource} = require('hsd/lib/dns/resource'); // const WalletClient = require('hsd/lib/client/wallet'); const { NodeClient, WalletClient } = require('hs-client'); const network = Network.get('main'); const minimist = require('minimist'); const args = minimist(process.argv.slice(2)); //! TODO this needs to hook into existing node // const node = new FullNode({ // memory: true, // network: 'main' // }); const nclient = new NodeClient({ url: args.hsdIP || 'localhost', apiKey: args.apiKey || '', ssl: false, host: args.hsdIP || 'localhost', port: 12037 }); const wclient = new WalletClient({ url: args.hsdIP || 'localhost', apiKey: args.apiKey || '', ssl: false, host: args.hsdIP || 'localhost', port: 12039, }); nclient.open(); wclient.open(); // const {wdb} = wclient.require('walletdb'); const price = args.price/1000000 || 1.234567; // 1.234567 HNS const domainAddress = args.domainAddress || 'hs1qsyy6eujpt2wv0caa0d26tvwejx77zdks2pj8nz'; const buyerAddress = args.buyer || 'hs1qcq8nzmlus8z5qqn6wvls9r0vg98tr6d6ga3fau'; const sellerAddress = args.seller || 'hs1q962sf24659wm6ev26gshrfwg8ruhve4c34f0ur'; const ns = args.nameInfo || {"name": "woodburn1","nameHash": "c93640a7772f786391c1c3ff2e99e4aaa442b6f2cf1fc6ec1790f2ea818dec08","state": "CLOSED","height": 175454,"renewal": 252162,"owner": {"hash": "337edb6e00aaf602a218d35d499b088872d2e74b9463066da845edccbe797c27","index": 4},"value": 1000000,"highest": 1000000,"data": "0001036e733108776f6f646275726e0001036e7332c006060204546573740548656c6c6f","transfer": 0,"revoked": 0,"claimed": 0,"renewals": 4,"registered": true,"expired": false,"weak": false,"stats": {"renewalPeriodStart": 252162,"renewalPeriodEnd": 357282,"blocksUntilExpire": 95192,"daysUntilExpire": 661.06}}; const walletId = args.walletId || 'hot'; const name = ns.name; const nameHash = ns.nameHash; // Alice constructs an incomplete transaction. // input 0 and output 1 are committed by Alice's SINGLEREVERSE signature. // output 0 can be added by either party since it's construction is // dictated completely by consensus rules (it isn't signed yet). // // input 0: TRANSFER UTXO --> output 0: FINALIZE covenant // (null) --- output 1: payment to Alice const owner = ns.owner; wclient.execute('selectwallet', [walletId]); const coin = wclient.getCoin(owner.hash, owner.index); const output0 = new Output(); output0.value = coin.value; output0.address = buyerAddress; output0.covenant.setFinalize( nameHash, ns.height, Buffer.from(name, 'ascii'), 0, // flags, may be required if name was CLAIMed ns.claimed, ns.renewals, wdb.getRenewalBlock() ); const output1 = new Output(); output1.address = sellerAddress; output1.value = price; const mtx = new MTX(); mtx.addCoin(coin); mtx.outputs.push(output0); mtx.outputs.push(output1); // Sign const rings = wclient.deriveInputs(mtx); assert(rings.length === 1); const signed = mtx.sign( rings, Script.hashType.SINGLEREVERSE | Script.hashType.ANYONECANPAY ); assert(signed === 1); assert(mtx.verify()); blob = mtx.encode().toString('hex'); console.log(blob); // COMPLETE TX // it('should complete transaction', async () => { // // Bob receives the hex string as a blob and decodes. // const mtx = MTX.decode(Buffer.from(blob, 'hex')); // // Bob should verify all the data in the MTX to ensure everything is valid, // // but this is the minimum. // const input0 = mtx.input(0).clone(); // copy input with Alice's signature // const coinEntry = await nclient.chain.db.readCoin(input0.prevout); // assert(coinEntry); // ensures that coin exists and is still unspent // const coin = coinEntry.toCoin(input0.prevout); // assert(coin.covenant.type === types.TRANSFER); // const addr = new Address({ // version: coin.covenant.items[2].readInt8(), // hash: coin.covenant.items[3] // }); // assert.deepStrictEqual(addr, bobReceive); // transfer is to Bob's address // // Fund the TX. // // The hsd wallet is not designed to handle partially-signed TXs // // or coins from outside the wallet, so a little hacking is needed. // const changeAddress = await bob.changeAddress(); // const rate = await wdb.estimateFee(); // const coins = await bob.getSmartCoins(); // // Add the external coin to the coin selector so we don't fail assertions // coins.push(coin); // await mtx.fund(coins, {changeAddress, rate}); // // The funding mechanism starts by wiping out existing inputs // // which for us includes Alice's signature. Replace it from our backup. // mtx.inputs[0].inject(input0); // // Rearrange outputs. // // Since we added a change output, the SINGELREVERSE is now broken: // // // // input 0: TRANSFER UTXO --> output 0: FINALIZE covenant // // input 1: Bob's funds --- output 1: payment to Alice // // (null) --- output 2: change to Bob // const outputs = mtx.outputs.slice(); // mtx.outputs = [outputs[0], outputs[2], outputs[1]]; // // Prepare to wait for mempool acceptance (race condition) // const waiter = new Promise((resolve, reject) => { // node.mempool.once('tx', resolve); // }); // // Sign & Broadcast // // Bob uses SIGHASHALL. The final TX looks like this: // // // // input 0: TRANSFER UTXO --> output 0: FINALIZE covenant // // input 1: Bob's funds --- output 1: change to Bob // // (null) --- output 2: payment to Alice // const tx = await bob.sendMTX(mtx); // bobFee = tx.getFee(mtx.view); // assert(tx.verify(mtx.view)); // // Wait for mempool and check // await waiter; // assert(node.mempool.hasEntry(tx.hash())); // // Confirm // await mineBlocks(1); // }); // it('should verify that name has been swapped', async () => { // const aliceNewBalance = await alice.getBalance(); // const bobNewBalance = await bob.getBalance(); // // Alice got the monies // // Note: This test works right now because the value of the name // // Alice won in the auction is ZERO (she had the only bid) // // See https://github.com/handshake-org/hsd/pull/464 for explanation // // Currently hsd wallet does not account for FINALIZE correctly // assert.strictEqual( // aliceNewBalance.confirmed, // aliceOriginalBalance.confirmed + price // ); // assert.strictEqual( // bobNewBalance.confirmed, // bobOriginalBalance.confirmed - price - bobFee // ); // // Bob got the name // const ns = await node.getNameStatus(nameHash); // const owner = ns.owner; // let coin = await alice.getCoin(owner.hash, owner.index); // assert(!coin); // coin = await bob.getCoin(owner.hash, owner.index); // assert(coin); // const resource = Resource.fromJSON({ // records: [{type: 'TXT', txt: ['Thanks Alice! --Bob']}] // }); // await bob.sendUpdate(name, resource); // await mineBlocks(network.names.treeInterval); // const actual = await node.chain.db.getNameState(nameHash); // assert.bufferEqual(resource.encode(), actual.data); // // The End // });