UNPKG

@okxweb3/coin-bitcoin

Version:

@okxweb3/coin-bitcoin is a Bitcoin SDK for building Web3 wallets and applications. It supports BTC, BSV, DOGE, LTC, and TBTC, enabling private key management, transaction signing, address generation, and inscriptions like BRC-20, Runes, CAT, and Atomicals

213 lines 9.99 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.calcVsizeTransfer = exports.transfer = exports.estimateFee = void 0; const common_1 = require("../common"); const scrypt_ts_1 = require("scrypt-ts"); const utils_1 = require("../utils"); const cat_smartcontracts_1 = require("@cat-protocol/cat-smartcontracts"); const functions_1 = require("./functions"); const merge_1 = require("./merge"); async function estimateFee(param) { param.data.estimateFee = true; return await transfer(param); } exports.estimateFee = estimateFee; async function transfer(param) { const txParams = param.data; const ecKey = !txParams.estimateFee ? new utils_1.EcKeyService({ privateKey: param.privateKey }) : (0, utils_1.getDummyEcKey)(); let metadata = (0, utils_1.tokenInfoParse)(txParams.tokenMetadata, common_1.SupportedNetwork.fractalMainnet); const minterP2TR = (0, utils_1.toP2tr)(metadata.minterAddr); const { p2tr: tokenP2TR, tapScript: tokenTapScript } = (0, utils_1.getTokenContractP2TR)(minterP2TR); let verifyScript = txParams.verifyScript || false; let receiver; let amount; try { receiver = common_1.btc.Address.fromString(txParams.toAddress); } catch (error) { throw new Error(`Invalid receiver address: ${txParams.toAddress}`); } if (receiver.type !== 'taproot') { throw new Error(`Invalid address type: ${receiver.type}`); } try { amount = BigInt(txParams.tokenAmount); } catch (error) { throw new Error(`Invalid token amount: ${txParams.tokenAmount}`); } if (amount <= 0n) { throw new Error('Invalid token amount'); } const feeUtxos = (0, utils_1.feeUtxoParse)(ecKey, param.data.feeInputs); let feeUtxo = feeUtxos[0]; let mergeTx; if (feeUtxos.length > 1) { ({ feeUtxo, mergeTx } = (0, merge_1.mergeFee)(ecKey, feeUtxos, txParams.feeRate)); } let tokens = (0, utils_1.tokenUtxoParse)(txParams.tokens); const commitResult = (0, functions_1.createGuardContract)(ecKey, feeUtxo, txParams.feeRate, tokens, tokenP2TR, txParams.changeAddress); if (commitResult === null) { return null; } const { commitTx, contact: guardContract, guardTapScript } = commitResult; const newState = cat_smartcontracts_1.ProtocolState.getEmptyState(); const receiverTokenState = cat_smartcontracts_1.CAT20Proto.create(amount, (0, utils_1.toTokenAddress)(receiver)); newState.updateDataList(0, cat_smartcontracts_1.CAT20Proto.toByteString(receiverTokenState)); const tokenInputAmount = tokens.reduce((acc, t) => acc + t.state.data.amount, 0n); const changeTokenInputAmount = tokenInputAmount - amount; let changeTokenState = null; if (changeTokenInputAmount > 0n) { const tokenChangeAddress = ecKey.getTokenAddress(); changeTokenState = cat_smartcontracts_1.CAT20Proto.create(changeTokenInputAmount, tokenChangeAddress); newState.updateDataList(1, cat_smartcontracts_1.CAT20Proto.toByteString(changeTokenState)); } const newFeeUtxo = { txId: commitTx.id, outputIndex: 2, script: commitTx.outputs[2].script.toHex(), satoshis: commitTx.outputs[2].satoshis, }; const inputUtxos = [ ...tokens.map((t) => t.utxo), guardContract.utxo, newFeeUtxo, ]; if (inputUtxos.length > cat_smartcontracts_1.MAX_INPUT) { throw new Error('Too many inputs, max 4 token inputs'); } const revealTx = new common_1.btc.Transaction() .from(inputUtxos) .addOutput(new common_1.btc.Transaction.Output({ satoshis: 0, script: (0, utils_1.toStateScript)(newState), })) .addOutput(new common_1.btc.Transaction.Output({ satoshis: common_1.Postage.TOKEN_POSTAGE, script: tokenP2TR, })) .feePerByte(txParams.feeRate); if (changeTokenState) { revealTx.addOutput(new common_1.btc.Transaction.Output({ satoshis: common_1.Postage.TOKEN_POSTAGE, script: tokenP2TR, })); } const satoshiChangeScript = common_1.btc.Script.fromAddress(txParams.changeAddress); revealTx.addOutput(new common_1.btc.Transaction.Output({ satoshis: 0, script: satoshiChangeScript, })); let tokenTxs = []; if (txParams.tokenPrevTxs.length !== tokens.length) { throw new Error('Invalid tokenPrevTxs length'); } for (let i = 0; i < tokens.length; i++) { const prevTx = txParams.tokenPrevTxs[i].prevTx; const prevPrevTx = txParams.tokenPrevTxs[i].prevPrevTx; const res = (0, utils_1.validatePrevTx)(metadata, prevTx, prevPrevTx, common_1.SupportedNetwork.fractalMainnet); if (res === null) { throw new Error('prevTx does not match prevPrevTx'); } tokenTxs.push(res); } const guardCommitTxHeader = (0, cat_smartcontracts_1.getTxHeaderCheck)(commitTx, guardContract.utxo.outputIndex); const guardInputIndex = tokens.length; const guardInfo = { outputIndex: (0, utils_1.toTxOutpoint)(guardContract.utxo.txId, guardContract.utxo.outputIndex).outputIndex, inputIndexVal: BigInt(guardInputIndex), tx: guardCommitTxHeader.tx, guardState: guardContract.state.data, }; let { vsize } = await (0, exports.calcVsizeTransfer)(tokens, guardContract, revealTx, guardInfo, tokenTxs, tokenTapScript, guardTapScript, newState, receiverTokenState, changeTokenState, satoshiChangeScript, minterP2TR); const satoshiChangeOutputIndex = changeTokenState === null ? 2 : 3; let satoshiChangeAmount = revealTx.inputAmount - vsize * txParams.feeRate - common_1.Postage.TOKEN_POSTAGE - (changeTokenState === null ? 0 : common_1.Postage.TOKEN_POSTAGE); let fee = vsize * txParams.feeRate; if (satoshiChangeAmount <= common_1.CHANGE_MIN_POSTAGE) { satoshiChangeAmount = 0; revealTx.removeOutput(satoshiChangeOutputIndex); const { vsize: newVsize, fee: newFee } = await (0, exports.calcVsizeTransfer)(tokens, guardContract, revealTx, guardInfo, tokenTxs, tokenTapScript, guardTapScript, newState, receiverTokenState, changeTokenState, satoshiChangeScript, minterP2TR); vsize = newVsize; fee = newFee; } else { revealTx.outputs[satoshiChangeOutputIndex].satoshis = satoshiChangeAmount; } if (txParams.estimateFee) { if (mergeTx) { return { mergeTx: (0, utils_1.getFee)(mergeTx), commitTx: (0, utils_1.getFee)(commitTx), revealTx: fee }; } return { commitTx: (0, utils_1.getFee)(commitTx), revealTx: fee }; } let changeInfo = null; if (satoshiChangeAmount > BigInt(0)) { changeInfo = { script: (0, scrypt_ts_1.toByteString)(satoshiChangeScript.toHex()), satoshis: (0, scrypt_ts_1.int2ByteString)(BigInt(satoshiChangeAmount), BigInt(8)), }; } const txCtxs = (0, cat_smartcontracts_1.getTxCtxMulti)(revealTx, tokens.map((_, i) => i).concat([tokens.length]), [ ...new Array(tokens.length).fill(Buffer.from(tokenTapScript, 'hex')), Buffer.from(guardTapScript, 'hex'), ]); for (let i = 0; i < tokens.length; i++) { const res = await (0, functions_1.unlockToken)(ecKey, tokens[i], i, tokenTxs[i].prevTx, tokenTxs[i].prevTokenInputIndex, tokenTxs[i].prevPrevTx, guardInfo, revealTx, minterP2TR, txCtxs[i], verifyScript); if (!res) { return null; } } const res = await (0, functions_1.unlockGuard)(guardContract, guardInfo, guardInputIndex, newState, revealTx, receiverTokenState, changeTokenState, changeInfo, txCtxs[guardInputIndex], verifyScript); if (!res) { return null; } ecKey.signTx(revealTx); if (mergeTx) { return { mergeTx: mergeTx.uncheckedSerialize(), commitTx: commitTx.uncheckedSerialize(), revealTx: revealTx.uncheckedSerialize(), }; } return { commitTx: commitTx.uncheckedSerialize(), revealTx: revealTx.uncheckedSerialize(), }; } exports.transfer = transfer; const calcVsizeTransfer = async (tokens, guardContract, revealTx, guardInfo, tokenTxs, tokenTapScript, guardTapScript, newState, receiverTokenState, changeTokenState, satoshisChangeScript, minterP2TR) => { const fakeEcKey = (0, utils_1.getDummyEcKey)(); const fakeFeeScript = common_1.btc.Script.fromAddress(fakeEcKey.getAddress()); const feeUtxoIndex = revealTx.inputs.length - 1; const originalFeeScript = revealTx.inputs[feeUtxoIndex].output.script; revealTx.inputs[feeUtxoIndex].output.setScript(fakeFeeScript); const txCtxs = (0, cat_smartcontracts_1.getTxCtxMulti)(revealTx, tokens.map((_, i) => i).concat([tokens.length]), [ ...new Array(tokens.length).fill(Buffer.from(tokenTapScript, 'hex')), Buffer.from(guardTapScript, 'hex'), ]); const guardInputIndex = tokens.length; const changeInfo = { script: satoshisChangeScript.toHex(), satoshis: (0, scrypt_ts_1.int2ByteString)(0n, 8n), }; for (let i = 0; i < tokens.length; i++) { await (0, functions_1.unlockToken)(fakeEcKey, tokens[i], i, tokenTxs[i].prevTx, tokenTxs[i].prevTokenInputIndex, tokenTxs[i].prevPrevTx, guardInfo, revealTx, minterP2TR, txCtxs[i], false); } await (0, functions_1.unlockGuard)(guardContract, guardInfo, guardInputIndex, newState, revealTx, receiverTokenState, changeTokenState, changeInfo, txCtxs[guardInputIndex], false); fakeEcKey.signTx(revealTx); const vsize = revealTx.vsize; const fee = (0, utils_1.getFee)(revealTx); (0, utils_1.resetTx)(revealTx); revealTx.inputs[feeUtxoIndex].output.setScript(originalFeeScript); return { vsize, fee }; }; exports.calcVsizeTransfer = calcVsizeTransfer; //# sourceMappingURL=transfer.js.map