UNPKG

@atomicport/bitcoin

Version:

Support Cross-Chain-Swap with HTLC on any blockchains

153 lines (152 loc) 5.23 kB
import axios from 'axios'; import mempoolJS from '@mempool/mempool.js'; import varuint from 'varuint-bitcoin'; import { crypto, networks, Psbt, script } from 'bitcoinjs-lib'; import { randomBytes, createHash } from 'crypto'; import ECPairFactory from 'ecpair'; import * as ecc from 'tiny-secp256k1'; /** * bitcoin 系のコインのインターフェース */ export default class Bitcoin { mempool; network; baseUrl; constructor(network) { this.network = network; const networkStr = network === networks.bitcoin ? 'bitcoin' : 'testnet'; this.mempool = mempoolJS({ hostname: 'mempool.space', network: networkStr }).bitcoin; this.baseUrl = `https://mempool.space/${networkStr}`; } createHashPair() { const s = randomBytes(32); const p1 = createHash('sha256').update(s).digest(); const p2 = createHash('sha256').update(p1).digest(); return { proof: s.toString('hex'), secret: p2.toString('hex'), }; } async getCurrentBlockHeight() { return await this.mempool.blocks.getBlocksTipHeight(); } async postTransaction(txhex) { const endpoint = `${this.baseUrl}/api/tx`; return new Promise((resolve, reject) => { axios .post(endpoint, txhex) .then((res) => { resolve(res.data); }) .catch((error) => { reject(error); }); }); } async getInputData(txid, contractAddress) { const txInfo = await this.mempool.transactions.getTx({ txid }); let value = 0; let index = 0; for (let i = 0; i < txInfo.vout.length; i++) { if (txInfo.vout[i].scriptpubkey_address == contractAddress) { value = txInfo.vout[i].value; index = i; } } return { value, index }; } async getUtxos(address) { const utxosData = await this.mempool.addresses.getAddressTxsUtxo({ address }); const utxos = []; for (let i = 0; i < utxosData.length; i++) { const hash = utxosData[i].txid; const index = utxosData[i].vout; const value = utxosData[i].value; utxos.push({ hash, index, value, }); } return utxos; } buildAndSignTx(sender, address, recipient, sendingSat, feeSat, utxos) { const psbt = new Psbt({ network: this.network }); let total = 0; const pubKeyHash = crypto.hash160(sender.publicKey).toString('hex'); for (let len = utxos.length, i = 0; i < len; i++) { psbt.addInput({ hash: utxos[i].hash, index: utxos[i].index, witnessUtxo: { script: Buffer.from('0014' + pubKeyHash, 'hex'), value: utxos[i].value, }, }); total += utxos[i].value; } psbt.addOutput({ address: recipient, value: sendingSat, }); const changeSat = total - sendingSat - feeSat; if (changeSat < 0) { throw new Error(`Balance is insufficient. Balance (UTXO Total): ${total} satoshi`); } psbt.addOutput({ address: address, value: changeSat, }); for (let len = utxos.length, i = 0; i < len; i++) { psbt.signInput(i, sender); psbt.validateSignaturesOfInput(i, (pubkey, msghash, signature) => { return ECPairFactory(ecc).fromPublicKey(pubkey).verify(msghash, signature); }); } psbt.finalizeAllInputs(); return psbt.extractTransaction().toHex(); } witnessStackToScriptWitness(witness) { let buffer = Buffer.allocUnsafe(0); function writeSlice(slice) { buffer = Buffer.concat([buffer, Buffer.from(slice)]); } function writeVarInt(i) { const currentLen = buffer.length; const varintLen = varuint.encodingLength(i); buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)]); varuint.encode(i, buffer, currentLen); } function writeVarSlice(slice) { writeVarInt(slice.length); writeSlice(slice); } function writeVector(vector) { writeVarInt(vector.length); vector.forEach(writeVarSlice); } writeVector(witness); return buffer; } /** * Generate HTLC Contract Script for Bitcoin */ generateSwapWitnessScript(receiverPublicKey, userRefundPublicKey, paymentHash, timelock) { return script.fromASM(` OP_HASH256 ${paymentHash} OP_EQUAL OP_IF ${receiverPublicKey.toString('hex')} OP_ELSE ${script.number.encode(timelock).toString('hex')} OP_CHECKLOCKTIMEVERIFY OP_DROP ${userRefundPublicKey.toString('hex')} OP_ENDIF OP_CHECKSIG ` .trim() .replace(/\s+/g, ' ')); } }