@atomicport/bitcoin
Version:
Support Cross-Chain-Swap with HTLC on any blockchains
155 lines (154 loc) • 7.13 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BitcoinHtlc = void 0;
const bip65_1 = __importDefault(require("bip65"));
const Bitcoin_1 = __importDefault(require("./Bitcoin"));
const bitcoinjs_lib_1 = require("bitcoinjs-lib");
/**
* HTLC operations on the Bitcoin.
*/
class BitcoinHtlc extends Bitcoin_1.default {
constructor(network) {
super(network);
}
/**
* Issue HTLC and obtain the key at the time of issue
*/
async lock(sender, receiver, secret, amount, options) {
// set option paramater
const fee = options?.fee || 1800;
const lockHeight = options?.lockHeight || 2;
const blockHeight = await this.getCurrentBlockHeight();
const timelock = bip65_1.default.encode({ blocks: blockHeight + lockHeight });
// generate contract
const witnessScript = this.generateSwapWitnessScript(receiver.publicKey, sender.publicKey, secret, timelock);
const p2wsh = bitcoinjs_lib_1.payments.p2wsh({
redeem: { output: witnessScript, network: this.network },
network: this.network,
});
// get addresses
const senderAddress = bitcoinjs_lib_1.payments.p2wpkh({ pubkey: sender.publicKey, network: this.network }).address;
if (senderAddress == undefined || p2wsh.address == undefined) {
throw new Error('senderAddress or contractAddress is undefined');
}
// get balance
const utxos = await this.getUtxos(senderAddress);
if (!utxos || utxos.length <= 0) {
throw new Error(`There was no UTXO currently available at the specified address ${senderAddress}.`);
}
// create transaction & announce
const txHex = this.buildAndSignTx(sender, senderAddress, p2wsh.address, amount, fee, utxos);
const hash = await this.postTransaction(txHex);
return {
hash,
contractAddress: p2wsh.address,
witnessScript: witnessScript.toString('hex'),
};
}
async withdraw(hash, contractAddress, witnessScript, receiver, proof, option) {
// set option paramater
const fee = option?.fee || 1800;
const witnessUtxoValue = await this.getInputData(hash, contractAddress);
const p2wpkh = bitcoinjs_lib_1.payments.p2wpkh({ pubkey: receiver.publicKey, network: this.network });
if (p2wpkh.address === undefined)
throw new Error(`recieverAddress is undefined`);
// transaction process
const transaction = new bitcoinjs_lib_1.Psbt({ network: this.network })
.addInput({
hash,
index: witnessUtxoValue.index,
sequence: 0xfffffffe,
witnessScript: Buffer.from(witnessScript, 'hex'),
witnessUtxo: {
script: Buffer.from('0020' + bitcoinjs_lib_1.crypto.sha256(Buffer.from(witnessScript, 'hex')).toString('hex'), 'hex'),
value: witnessUtxoValue.value,
},
})
.addOutput({
address: p2wpkh.address,
value: witnessUtxoValue.value - fee,
})
.signInput(0, receiver)
.finalizeInput(0, (inputIndex, input, tapLeafHashToFinalize) => {
const decompiled = bitcoinjs_lib_1.script.decompile(tapLeafHashToFinalize);
if (!decompiled || decompiled[0] !== bitcoinjs_lib_1.opcodes.OP_HASH256) {
throw new Error(`Can not finalize input #${inputIndex}`);
}
const witnessStackClaimBranch = bitcoinjs_lib_1.payments.p2wsh({
redeem: {
input: bitcoinjs_lib_1.script.compile([input.partialSig[0].signature, Buffer.from(proof, 'hex')]),
output: Buffer.from(witnessScript, 'hex'),
},
});
return {
finalScriptSig: undefined,
finalScriptWitness: this.witnessStackToScriptWitness(witnessStackClaimBranch.witness),
};
})
.extractTransaction();
console.log(`transaction id: ${transaction.getId()}`);
await new Promise((ok) => {
setTimeout(() => {
ok('');
}, 10000);
});
return await this.postTransaction(transaction.toHex());
}
/**
* Called by the sender if there was no withdraw AND the time lock has
* expired. This will refund the contract amount.
* @returns transaction hash
*/
async refund(hash, contractAddress, witnessScript, sender, option) {
// set option paramater
const fee = option?.fee || 1800;
const decompiled = bitcoinjs_lib_1.script.decompile(Buffer.from(witnessScript, 'hex'));
const witnessUtxoValue = await this.getInputData(hash, contractAddress);
const p2wpkh = bitcoinjs_lib_1.payments.p2wpkh({ pubkey: sender.publicKey, network: this.network });
if (decompiled == null || decompiled[6] == null)
throw new Error("script hasn't lock time");
if (p2wpkh.address === undefined)
throw new Error(`recieverAddress is undefined`);
const timelock = bip65_1.default.encode({ blocks: bitcoinjs_lib_1.script.number.decode(decompiled[6]) });
// transaction process
const transaction = new bitcoinjs_lib_1.Psbt({ network: this.network })
.setLocktime(timelock)
.addInput({
hash,
index: witnessUtxoValue.index,
sequence: 0xfffffffe,
witnessScript: Buffer.from(witnessScript, 'hex'),
witnessUtxo: {
script: Buffer.from('0020' + bitcoinjs_lib_1.crypto.sha256(Buffer.from(witnessScript, 'hex')).toString('hex'), 'hex'),
value: witnessUtxoValue.value,
},
})
.addOutput({
address: p2wpkh.address,
value: witnessUtxoValue.value - fee,
})
.signInput(0, sender)
.finalizeInput(0, (inputIndex, input, tapLeafHashToFinalize) => {
const decompiled = bitcoinjs_lib_1.script.decompile(tapLeafHashToFinalize);
if (!decompiled || decompiled[0] !== bitcoinjs_lib_1.opcodes.OP_HASH256) {
throw new Error(`Can not finalize input #${inputIndex}`);
}
const witnessStackRefundBranch = bitcoinjs_lib_1.payments.p2wsh({
redeem: {
input: bitcoinjs_lib_1.script.compile([input.partialSig[0].signature, Buffer.from('', 'hex')]),
output: Buffer.from(witnessScript, 'hex'),
},
});
return {
finalScriptSig: undefined,
finalScriptWitness: this.witnessStackToScriptWitness(witnessStackRefundBranch.witness),
};
})
.extractTransaction();
return await this.postTransaction(transaction.toHex());
}
}
exports.BitcoinHtlc = BitcoinHtlc;