atomic-swap/atomic-swap.js

209 lines
7.5 KiB
JavaScript
Raw Normal View History

2025-01-30 14:25:13 +11:00
'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
// });