UNPKG

@oyl/sdk

Version:
481 lines 19.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.send = exports.transfer = exports.reveal = exports.commit = exports.transferEstimate = void 0; const tslib_1 = require("tslib"); const errors_1 = require("../errors"); const bitcoin = tslib_1.__importStar(require("bitcoinjs-lib")); const utils_1 = require("../shared/utils"); const btc_1 = require("../btc/btc"); const bip371_1 = require("bitcoinjs-lib/src/psbt/bip371"); const bip341_1 = require("bitcoinjs-lib/src/payments/bip341"); const utils_2 = require("../shared/utils"); const transferEstimate = async ({ gatheredUtxos, toAddress, feeRate, account, provider, fee, }) => { try { const originalGatheredUtxos = gatheredUtxos; if (!feeRate) { feeRate = (await provider.esplora.getFeeEstimates())['1']; } const psbt = new bitcoin.Psbt({ network: provider.network }); const minFee = (0, btc_1.minimumFee)({ taprootInputCount: 1, nonTaprootInputCount: 0, outputCount: 2, }); let calculatedFee = minFee * feeRate < 250 ? 250 : minFee * feeRate; let finalFee = fee ? fee : calculatedFee; gatheredUtxos = (0, utils_1.findXAmountOfSats)(originalGatheredUtxos.utxos, Number(finalFee) + 546); if (!fee && gatheredUtxos.utxos.length > 1) { const txSize = (0, btc_1.minimumFee)({ taprootInputCount: gatheredUtxos.utxos.length, nonTaprootInputCount: 0, outputCount: 2, }); finalFee = Math.max(txSize * feeRate, 250); gatheredUtxos = (0, utils_1.findXAmountOfSats)(originalGatheredUtxos.utxos, Number(finalFee) + 546); } if (gatheredUtxos.totalAmount < finalFee + 546) { throw new errors_1.OylTransactionError(Error('Insufficient Balance')); } for (let i = 0; i < gatheredUtxos.utxos.length; i++) { if ((0, utils_2.getAddressType)(gatheredUtxos.utxos[i].address) === 0) { const previousTxHex = await provider.esplora.getTxHex(gatheredUtxos.utxos[i].txId); psbt.addInput({ hash: gatheredUtxos.utxos[i].txId, index: gatheredUtxos.utxos[i].outputIndex, nonWitnessUtxo: Buffer.from(previousTxHex, 'hex'), }); } if ((0, utils_2.getAddressType)(gatheredUtxos.utxos[i].address) === 2) { const redeemScript = bitcoin.script.compile([ bitcoin.opcodes.OP_0, bitcoin.crypto.hash160(Buffer.from(account.nestedSegwit.pubkey, 'hex')), ]); psbt.addInput({ hash: gatheredUtxos.utxos[i].txId, index: gatheredUtxos.utxos[i].outputIndex, redeemScript: redeemScript, witnessUtxo: { value: gatheredUtxos.utxos[i].satoshis, script: bitcoin.script.compile([ bitcoin.opcodes.OP_HASH160, bitcoin.crypto.hash160(redeemScript), bitcoin.opcodes.OP_EQUAL, ]), }, }); } if ((0, utils_2.getAddressType)(gatheredUtxos.utxos[i].address) === 1 || (0, utils_2.getAddressType)(gatheredUtxos.utxos[i].address) === 3) { psbt.addInput({ hash: gatheredUtxos.utxos[i].txId, index: gatheredUtxos.utxos[i].outputIndex, witnessUtxo: { value: gatheredUtxos.utxos[i].satoshis, script: Buffer.from(gatheredUtxos.utxos[i].scriptPk, 'hex'), }, }); } } psbt.addOutput({ address: toAddress, value: 546, }); const changeAmount = gatheredUtxos.totalAmount - (finalFee + 546); psbt.addOutput({ address: account[account.spendStrategy.changeAddress].address, value: changeAmount, }); const updatedPsbt = await (0, utils_1.formatInputsToSign)({ _psbt: psbt, senderPublicKey: account.taproot.pubkey, network: provider.network, }); return { psbt: updatedPsbt.toBase64(), fee: finalFee }; } catch (error) { throw new errors_1.OylTransactionError(error); } }; exports.transferEstimate = transferEstimate; const commit = async ({ gatheredUtxos, ticker, amount, feeRate, account, tweakedTaprootPublicKey, provider, finalTransferFee, fee, }) => { try { const originalGatheredUtxos = gatheredUtxos; const content = `{"p":"brc-20","op":"transfer","tick":"${ticker}","amt":"${amount}"}`; if (!feeRate) { feeRate = (await provider.esplora.getFeeEstimates())['1']; } const psbt = new bitcoin.Psbt({ network: provider.network }); const commitTxSize = (0, utils_1.calculateTaprootTxSize)(1, 0, 2); const feeForCommit = commitTxSize * feeRate < 250 ? 250 : commitTxSize * feeRate; const revealTxSize = (0, utils_1.calculateTaprootTxSize)(1, 0, 2); const feeForReveal = revealTxSize * feeRate < 250 ? 250 : revealTxSize * feeRate; const baseEstimate = Number(feeForCommit) + Number(feeForReveal) + 546; let finalFee = fee ? fee + Number(feeForReveal) + 546 : baseEstimate; const script = (0, utils_1.createInscriptionScript)(tweakedTaprootPublicKey, content); const outputScript = bitcoin.script.compile(script); const inscriberInfo = bitcoin.payments.p2tr({ internalPubkey: tweakedTaprootPublicKey, scriptTree: { output: outputScript }, network: provider.network, }); psbt.addOutput({ value: Number(feeForReveal) + 546, address: inscriberInfo.address, }); gatheredUtxos = (0, utils_1.findXAmountOfSats)(originalGatheredUtxos.utxos, Number(finalFee) + Number(finalTransferFee)); if (!fee && gatheredUtxos.utxos.length > 1) { const txSize = (0, btc_1.minimumFee)({ taprootInputCount: gatheredUtxos.utxos.length, nonTaprootInputCount: 0, outputCount: 2, }); finalFee = txSize * feeRate < 250 ? 250 : txSize * feeRate + Number(feeForReveal) + 546; gatheredUtxos = (0, utils_1.findXAmountOfSats)(originalGatheredUtxos.utxos, Number(finalFee) + Number(finalTransferFee)); } if (gatheredUtxos.totalAmount < finalFee) { throw new errors_1.OylTransactionError(Error('Insufficient Balance')); } for (let i = 0; i < gatheredUtxos.utxos.length; i++) { if ((0, utils_2.getAddressType)(gatheredUtxos.utxos[i].address) === 0) { const previousTxHex = await provider.esplora.getTxHex(gatheredUtxos.utxos[i].txId); psbt.addInput({ hash: gatheredUtxos.utxos[i].txId, index: gatheredUtxos.utxos[i].outputIndex, nonWitnessUtxo: Buffer.from(previousTxHex, 'hex'), }); } if ((0, utils_2.getAddressType)(gatheredUtxos.utxos[i].address) === 2) { const redeemScript = bitcoin.script.compile([ bitcoin.opcodes.OP_0, bitcoin.crypto.hash160(Buffer.from(account.nestedSegwit.pubkey, 'hex')), ]); psbt.addInput({ hash: gatheredUtxos.utxos[i].txId, index: gatheredUtxos.utxos[i].outputIndex, redeemScript: redeemScript, witnessUtxo: { value: gatheredUtxos.utxos[i].satoshis, script: bitcoin.script.compile([ bitcoin.opcodes.OP_HASH160, bitcoin.crypto.hash160(redeemScript), bitcoin.opcodes.OP_EQUAL, ]), }, }); } if ((0, utils_2.getAddressType)(gatheredUtxos.utxos[i].address) === 1 || (0, utils_2.getAddressType)(gatheredUtxos.utxos[i].address) === 3) { psbt.addInput({ hash: gatheredUtxos.utxos[i].txId, index: gatheredUtxos.utxos[i].outputIndex, witnessUtxo: { value: gatheredUtxos.utxos[i].satoshis, script: Buffer.from(gatheredUtxos.utxos[i].scriptPk, 'hex'), }, }); } } const changeAmount = gatheredUtxos.totalAmount - finalFee; psbt.addOutput({ address: account[account.spendStrategy.changeAddress].address, value: changeAmount, }); const updatedPsbt = await (0, utils_1.formatInputsToSign)({ _psbt: psbt, senderPublicKey: account.taproot.pubkey, network: provider.network, }); return { psbt: updatedPsbt.toBase64(), fee: finalFee, script: outputScript }; } catch (error) { throw new errors_1.OylTransactionError(error); } }; exports.commit = commit; const reveal = async ({ receiverAddress, script, feeRate, tweakedTaprootKeyPair, provider, fee = 0, commitTxId, }) => { try { if (!feeRate) { feeRate = (await provider.esplora.getFeeEstimates())['1']; } const psbt = new bitcoin.Psbt({ network: provider.network }); const minFee = (0, btc_1.minimumFee)({ taprootInputCount: 1, nonTaprootInputCount: 0, outputCount: 2, }); const revealTxBaseFee = minFee * feeRate < 546 ? 546 : minFee * feeRate; const revealTxChange = fee === 0 ? 0 : Number(revealTxBaseFee) - fee; const commitTxOutput = await (0, utils_1.getOutputValueByVOutIndex)({ txId: commitTxId, vOut: 0, esploraRpc: provider.esplora, }); if (!commitTxOutput) { throw new Error('Error getting vin #0 value'); } const p2pk_redeem = { output: script }; const { output, witness } = bitcoin.payments.p2tr({ internalPubkey: (0, bip371_1.toXOnly)(tweakedTaprootKeyPair.publicKey), scriptTree: p2pk_redeem, redeem: p2pk_redeem, network: provider.network, }); psbt.addInput({ hash: commitTxId, index: 0, witnessUtxo: { value: commitTxOutput.value, script: output, }, tapLeafScript: [ { leafVersion: bip341_1.LEAF_VERSION_TAPSCRIPT, script: p2pk_redeem.output, controlBlock: witness[witness.length - 1], }, ], }); psbt.addOutput({ value: 546, address: receiverAddress, }); if (revealTxChange > 546) { psbt.addOutput({ value: revealTxChange, address: receiverAddress, }); } psbt.signInput(0, tweakedTaprootKeyPair); psbt.finalizeInput(0); return { psbt: psbt.toBase64(), psbtHex: psbt.extractTransaction().toHex(), fee: revealTxChange, }; } catch (error) { throw new errors_1.OylTransactionError(error); } }; exports.reveal = reveal; const transfer = async ({ commitChangeUtxoId, revealTxId, toAddress, feeRate, account, provider, fee, }) => { try { if (!feeRate) { feeRate = (await provider.esplora.getFeeEstimates())['1']; } const psbt = new bitcoin.Psbt({ network: provider.network }); const [utxoInfo, revealInfo] = await provider.sandshrew.multiCall([ ['esplora_tx', [commitChangeUtxoId]], ['esplora_tx', [revealTxId]], ]); const minFee = (0, btc_1.minimumFee)({ taprootInputCount: 2, nonTaprootInputCount: 0, outputCount: 2, }); let calculatedFee = minFee * feeRate < 250 ? 250 : minFee * feeRate; let finalFee = fee ? fee : calculatedFee; let totalValue = 0; psbt.addInput({ hash: revealTxId, index: 0, witnessUtxo: { script: Buffer.from(revealInfo.result.vout[0].scriptpubkey, 'hex'), value: 546, }, }); const allAddresses = [ account.taproot.address, account.nativeSegwit.address, account.nestedSegwit.address, account.legacy.address, ]; for (let i = 0; i < utxoInfo.result.vout.length; i++) { if (allAddresses.includes(utxoInfo.result.vout[i].scriptpubkey_address)) { if ((0, utils_2.getAddressType)(utxoInfo.result.vout[i].scriptpubkey_address) === 0) { const previousTxHex = await provider.esplora.getTxHex(commitChangeUtxoId); psbt.addInput({ hash: commitChangeUtxoId, index: i, nonWitnessUtxo: Buffer.from(previousTxHex, 'hex'), }); } if ((0, utils_2.getAddressType)(utxoInfo.result.vout[i].scriptpubkey_address) === 2) { const redeemScript = bitcoin.script.compile([ bitcoin.opcodes.OP_0, bitcoin.crypto.hash160(Buffer.from(account.nestedSegwit.pubkey, 'hex')), ]); psbt.addInput({ hash: commitChangeUtxoId, index: i, redeemScript: redeemScript, witnessUtxo: { value: utxoInfo.result.vout[i].value, script: bitcoin.script.compile([ bitcoin.opcodes.OP_HASH160, bitcoin.crypto.hash160(redeemScript), bitcoin.opcodes.OP_EQUAL, ]), }, }); } if ((0, utils_2.getAddressType)(utxoInfo.result.vout[i].scriptpubkey_address) === 1 || (0, utils_2.getAddressType)(utxoInfo.result.vout[i].scriptpubkey_address) === 3) { psbt.addInput({ hash: commitChangeUtxoId, index: i, witnessUtxo: { value: utxoInfo.result.vout[i].value, script: Buffer.from(utxoInfo.result.vout[i].scriptpubkey, 'hex'), }, }); } totalValue += utxoInfo.result.vout[i].value; } } psbt.addOutput({ address: toAddress, value: 546, }); psbt.addOutput({ address: account[account.spendStrategy.changeAddress].address, value: totalValue - finalFee, }); const formattedPsbt = await (0, utils_1.formatInputsToSign)({ _psbt: psbt, senderPublicKey: account.taproot.pubkey, network: provider.network, }); return { psbt: formattedPsbt.toBase64() }; } catch (error) { throw new errors_1.OylTransactionError(error); } }; exports.transfer = transfer; const send = async ({ gatheredUtxos, toAddress, ticker, amount, account, provider, feeRate, signer, }) => { let successTxIds = []; const estimate = await (0, exports.transferEstimate)({ gatheredUtxos, toAddress: toAddress, feeRate: feeRate, account: account, provider: provider, }); const tweakedTaprootKeyPair = (0, utils_1.tweakSigner)(signer.taprootKeyPair, { network: provider.network }); const tweakedTaprootPublicKey = (0, bip371_1.toXOnly)(tweakedTaprootKeyPair.publicKey); const { psbt: dryCommitPsbt } = await (0, exports.commit)({ gatheredUtxos, ticker: ticker, amount: amount, feeRate: feeRate, tweakedTaprootPublicKey, account: account, provider: provider, finalTransferFee: estimate.fee, }); const { signedPsbt: commitSigned } = await signer.signAllInputs({ rawPsbt: dryCommitPsbt, finalize: true, }); const commitFee = await (0, utils_1.getFee)({ provider, psbt: commitSigned, feeRate: feeRate, }); const { psbt: finalCommitPsbt, script } = await (0, exports.commit)({ gatheredUtxos, ticker: ticker, amount: amount, feeRate: feeRate, tweakedTaprootPublicKey, account: account, provider: provider, fee: commitFee, finalTransferFee: estimate.fee, }); const { signedPsbt: finalCommitSigned } = await signer.signAllInputs({ rawPsbt: finalCommitPsbt, finalize: true, }); const { txId: commitTxId } = await provider.pushPsbt({ psbtBase64: finalCommitSigned, }); successTxIds.push(commitTxId); const { psbt: revealPsbt } = await (0, exports.reveal)({ feeRate: feeRate, tweakedTaprootKeyPair, provider: provider, script: script, commitTxId: commitTxId, receiverAddress: account.taproot.address, }); const revealFee = await (0, utils_1.getFee)({ provider, psbt: revealPsbt, feeRate: feeRate, }); const { psbt: finalRevealPsbt } = await (0, exports.reveal)({ feeRate: feeRate, tweakedTaprootKeyPair, provider: provider, script: script, commitTxId: commitTxId, receiverAddress: account.taproot.address, fee: revealFee, }); const { txId: revealTxId } = await provider.pushPsbt({ psbtBase64: finalRevealPsbt, }); if (!revealTxId) { throw new Error('Unable to reveal inscription.'); } successTxIds.push(revealTxId); await (0, utils_1.waitForTransaction)({ txId: revealTxId, sandshrewBtcClient: provider.sandshrew, }); const { psbt: transferPsbt } = await (0, exports.transfer)({ feeRate, account, provider, revealTxId, commitChangeUtxoId: commitTxId, toAddress, }); const { signedPsbt: transferSigned } = await signer.signAllInputs({ rawPsbt: transferPsbt, finalize: true, }); const transferFee = await (0, utils_1.getFee)({ provider, psbt: transferSigned, feeRate: feeRate, }); const { psbt: finalTransferPsbt } = await (0, exports.transfer)({ feeRate, account, provider, revealTxId, commitChangeUtxoId: commitTxId, toAddress, fee: transferFee, }); const { signedPsbt: finalTransferSigned } = await signer.signAllInputs({ rawPsbt: finalTransferPsbt, finalize: true, }); const { txId: transferTxId } = await provider.pushPsbt({ psbtBase64: finalTransferSigned, }); return { txId: transferTxId, rawTxn: finalTransferSigned, sendBrc20Txids: [...successTxIds, transferTxId], }; }; exports.send = send; //# sourceMappingURL=brc20.js.map