UNPKG

@oyl/sdk

Version:
647 lines 25.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createTransactReveal = exports.execute = exports.executePsbt = exports.actualExecuteFee = exports.actualTransactRevealFee = exports.findAlkaneUtxos = exports.deployReveal = exports.createDeployRevealPsbt = exports.deployCommit = exports.createDeployCommitPsbt = exports.createExecutePsbt = exports.encodeProtostone = void 0; const tslib_1 = require("tslib"); const btc_1 = require("../btc"); const bitcoin = tslib_1.__importStar(require("bitcoinjs-lib")); const index_1 = require("alkanes/lib/index"); const utils_1 = require("../shared/utils"); const psbt_1 = require("../psbt"); const errors_1 = require("../errors"); const utils_2 = require("../shared/utils"); const bip371_1 = require("bitcoinjs-lib/src/psbt/bip371"); const bip341_1 = require("bitcoinjs-lib/src/payments/bip341"); const contract_1 = require("./contract"); const encodeProtostone = ({ protocolTag = 1n, edicts = [], pointer = 0, refundPointer = 0, calldata, }) => { return (0, index_1.encodeRunestoneProtostone)({ protostones: [ index_1.ProtoStone.message({ protocolTag, edicts, pointer, refundPointer, calldata: (0, index_1.encipher)(calldata), }), ], }).encodedRunestone; }; exports.encodeProtostone = encodeProtostone; const createExecutePsbt = async ({ frontendFee, feeAddress, alkaneUtxos, gatheredUtxos, account, protostone, provider, feeRate, fee = 0, }) => { try { const originalGatheredUtxos = gatheredUtxos; const minTxSize = (0, btc_1.minimumFee)({ taprootInputCount: 2, nonTaprootInputCount: 0, outputCount: 2, }); let calculatedFee = Math.max(minTxSize * feeRate, 250); let finalFee = fee === 0 ? calculatedFee : fee; gatheredUtxos = (0, utils_1.findXAmountOfSats)(originalGatheredUtxos.utxos, Number(finalFee) + 546 + (frontendFee || 0)); let psbt = new bitcoin.Psbt({ network: provider.network }); if (alkaneUtxos) { for await (const utxo of alkaneUtxos.alkaneUtxos) { if ((0, utils_2.getAddressType)(utxo.address) === 0) { const previousTxHex = await provider.esplora.getTxHex(utxo.txId); psbt.addInput({ hash: utxo.txId, index: parseInt(utxo.txIndex), nonWitnessUtxo: Buffer.from(previousTxHex, 'hex'), }); } if ((0, utils_2.getAddressType)(utxo.address) === 2) { const redeemScript = bitcoin.script.compile([ bitcoin.opcodes.OP_0, bitcoin.crypto.hash160(Buffer.from(account.nestedSegwit.pubkey, 'hex')), ]); psbt.addInput({ hash: utxo.txId, index: parseInt(utxo.txIndex), redeemScript: redeemScript, witnessUtxo: { value: utxo.satoshis, script: bitcoin.script.compile([ bitcoin.opcodes.OP_HASH160, bitcoin.crypto.hash160(redeemScript), bitcoin.opcodes.OP_EQUAL, ]), }, }); } if ((0, utils_2.getAddressType)(utxo.address) === 1 || (0, utils_2.getAddressType)(utxo.address) === 3) { psbt.addInput({ hash: utxo.txId, index: parseInt(utxo.txIndex), witnessUtxo: { value: utxo.satoshis, script: Buffer.from(utxo.script, 'hex'), }, }); } } } if (fee === 0 && 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; if (gatheredUtxos.totalAmount < finalFee) { throw new errors_1.OylTransactionError(Error('Insufficient Balance')); } } 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'), }, }); } } psbt.addOutput({ address: account.taproot.address, value: 546, }); const output = { script: protostone, value: 0 }; psbt.addOutput(output); const changeAmount = gatheredUtxos.totalAmount + (alkaneUtxos?.totalSatoshis || 0) - finalFee - 546 - (frontendFee || 0); psbt.addOutput({ address: account[account.spendStrategy.changeAddress].address, value: changeAmount, }); if (frontendFee && feeAddress) { psbt.addOutput({ address: feeAddress, value: frontendFee, }); } const formattedPsbtTx = await (0, utils_1.formatInputsToSign)({ _psbt: psbt, senderPublicKey: account.taproot.pubkey, network: provider.network, }); return { psbt: formattedPsbtTx.toBase64(), psbtHex: formattedPsbtTx.toHex(), }; } catch (error) { throw new errors_1.OylTransactionError(error); } }; exports.createExecutePsbt = createExecutePsbt; const createDeployCommitPsbt = async ({ payload, gatheredUtxos, tweakedPublicKey, account, provider, feeRate, fee, }) => { try { const originalGatheredUtxos = gatheredUtxos; const minFee = (0, btc_1.minimumFee)({ taprootInputCount: 2, nonTaprootInputCount: 0, outputCount: 2, }); const calculatedFee = minFee * feeRate < 250 ? 250 : minFee * feeRate; let finalFee = fee ? fee : calculatedFee; let psbt = new bitcoin.Psbt({ network: provider.network }); const script = Buffer.from((0, index_1.p2tr_ord_reveal)((0, bip371_1.toXOnly)(Buffer.from(tweakedPublicKey, 'hex')), [payload]) .script); const inscriberInfo = bitcoin.payments.p2tr({ internalPubkey: (0, bip371_1.toXOnly)(Buffer.from(tweakedPublicKey, 'hex')), scriptTree: { output: script, }, network: provider.network, }); const wasmDeploySize = (0, utils_1.getVSize)(Buffer.from(payload.body)) * feeRate; gatheredUtxos = (0, utils_1.findXAmountOfSats)(originalGatheredUtxos.utxos, wasmDeploySize + Number(utils_1.inscriptionSats) + finalFee * 2); 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; if (gatheredUtxos.totalAmount < finalFee) { gatheredUtxos = (0, utils_1.findXAmountOfSats)(originalGatheredUtxos.utxos, wasmDeploySize + Number(utils_1.inscriptionSats) + finalFee * 2); } } 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'), }, }); } } if (gatheredUtxos.totalAmount < finalFee * 2 + utils_1.inscriptionSats + wasmDeploySize) { throw new errors_1.OylTransactionError(Error('Insufficient Balance')); } psbt.addOutput({ value: finalFee + wasmDeploySize + 546, address: inscriberInfo.address, }); const changeAmount = gatheredUtxos.totalAmount - (finalFee * 2 + wasmDeploySize + utils_1.inscriptionSats); psbt.addOutput({ address: account[account.spendStrategy.changeAddress].address, value: changeAmount, }); const formattedPsbtTx = await (0, utils_1.formatInputsToSign)({ _psbt: psbt, senderPublicKey: account.taproot.pubkey, network: provider.network, }); return { psbt: formattedPsbtTx.toBase64(), script }; } catch (error) { throw new errors_1.OylTransactionError(error); } }; exports.createDeployCommitPsbt = createDeployCommitPsbt; const deployCommit = async ({ payload, gatheredUtxos, account, provider, feeRate, signer, }) => { const tweakedTaprootKeyPair = (0, utils_1.tweakSigner)(signer.taprootKeyPair, { network: provider.network, }); const tweakedPublicKey = tweakedTaprootKeyPair.publicKey.toString('hex'); const { fee: commitFee } = await (0, contract_1.actualDeployCommitFee)({ payload, gatheredUtxos, tweakedPublicKey, account, provider, feeRate, }); const { psbt: finalPsbt, script } = await (0, exports.createDeployCommitPsbt)({ payload, gatheredUtxos, tweakedPublicKey, account, provider, feeRate, fee: commitFee, }); const { signedPsbt } = await signer.signAllInputs({ rawPsbt: finalPsbt, finalize: true, }); const result = await provider.pushPsbt({ psbtBase64: signedPsbt, }); return { ...result, script: script.toString('hex') }; }; exports.deployCommit = deployCommit; const createDeployRevealPsbt = async ({ protostone, receiverAddress, script, feeRate, tweakedPublicKey, 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 < 250 ? 250 : 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 errors_1.OylTransactionError(new Error('Error getting vin #0 value')); } const p2pk_redeem = { output: script }; const { output, witness } = bitcoin.payments.p2tr({ internalPubkey: (0, bip371_1.toXOnly)(Buffer.from(tweakedPublicKey, 'hex')), 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, }); psbt.addOutput({ value: 0, script: protostone, }); if (revealTxChange > 546) { psbt.addOutput({ value: revealTxChange, address: receiverAddress, }); } return { psbt: psbt.toBase64(), fee: revealTxChange, }; } catch (error) { throw new errors_1.OylTransactionError(error); } }; exports.createDeployRevealPsbt = createDeployRevealPsbt; const deployReveal = async ({ protostone, commitTxId, script, account, provider, feeRate, signer, }) => { const tweakedTaprootKeyPair = (0, utils_1.tweakSigner)(signer.taprootKeyPair, { network: provider.network, }); const tweakedPublicKey = tweakedTaprootKeyPair.publicKey.toString('hex'); const { fee } = await (0, exports.actualTransactRevealFee)({ protostone, tweakedPublicKey, receiverAddress: account.taproot.address, commitTxId, script: Buffer.from(script, 'hex'), provider, feeRate, }); const { psbt: finalRevealPsbt } = await (0, exports.createTransactReveal)({ protostone, tweakedPublicKey, receiverAddress: account.taproot.address, commitTxId, script: Buffer.from(script, 'hex'), provider, feeRate, fee, }); let finalReveal = bitcoin.Psbt.fromBase64(finalRevealPsbt, { network: provider.network, }); finalReveal.signInput(0, tweakedTaprootKeyPair); finalReveal.finalizeInput(0); const finalSignedPsbt = finalReveal.toBase64(); const revealResult = await provider.pushPsbt({ psbtBase64: finalSignedPsbt, }); return revealResult; }; exports.deployReveal = deployReveal; const findAlkaneUtxos = async ({ address, greatestToLeast, provider, alkaneId, targetNumberOfAlkanes, }) => { const res = await provider.alkanes.getAlkanesByAddress({ address: address, protocolTag: '1', }); const matchingRunesWithOutpoints = res.flatMap((outpoint) => outpoint.runes .filter((value) => Number(value.rune.id.block) === Number(alkaneId.block) && Number(value.rune.id.tx) === Number(alkaneId.tx)) .map((rune) => ({ rune, outpoint }))); const sortedRunesWithOutpoints = matchingRunesWithOutpoints.sort((a, b) => greatestToLeast ? Number(b.rune.balance) - Number(a.rune.balance) : Number(a.rune.balance) - Number(b.rune.balance)); let totalSatoshis = 0; let totalBalanceBeingSent = 0; const alkaneUtxos = []; for (const alkane of sortedRunesWithOutpoints) { if (totalBalanceBeingSent < targetNumberOfAlkanes && Number(alkane.rune.balance) > 0) { const satoshis = Number(alkane.outpoint.output.value); alkaneUtxos.push({ txId: alkane.outpoint.outpoint.txid, txIndex: alkane.outpoint.outpoint.vout, script: alkane.outpoint.output.script, address, amountOfAlkanes: alkane.rune.balance, satoshis, ...alkane.rune.rune, }); totalSatoshis += satoshis; totalBalanceBeingSent += Number(alkane.rune.balance) / (alkane.rune.rune.divisibility == 1 ? 1 : 10 ** alkane.rune.rune.divisibility); } } if (totalBalanceBeingSent < targetNumberOfAlkanes) { throw new errors_1.OylTransactionError(Error('Insuffiecient balance of alkanes.')); } return { alkaneUtxos, totalSatoshis, totalBalanceBeingSent }; }; exports.findAlkaneUtxos = findAlkaneUtxos; const actualTransactRevealFee = async ({ protostone, tweakedPublicKey, commitTxId, receiverAddress, script, provider, feeRate, }) => { if (!feeRate) { feeRate = (await provider.esplora.getFeeEstimates())['1']; } const { psbt } = await (0, exports.createTransactReveal)({ protostone, commitTxId, receiverAddress, script, tweakedPublicKey, provider, feeRate, }); const { fee: estimatedFee } = await (0, psbt_1.getEstimatedFee)({ feeRate, psbt, provider, }); const { psbt: finalPsbt } = await (0, exports.createTransactReveal)({ protostone, commitTxId, receiverAddress, script, tweakedPublicKey, provider, feeRate, fee: estimatedFee, }); const { fee: finalFee, vsize } = await (0, psbt_1.getEstimatedFee)({ feeRate, psbt: finalPsbt, provider, }); return { fee: finalFee, vsize }; }; exports.actualTransactRevealFee = actualTransactRevealFee; const actualExecuteFee = async ({ gatheredUtxos, account, protostone, provider, feeRate, alkaneUtxos, frontendFee, feeAddress, }) => { if (!feeRate) { feeRate = (await provider.esplora.getFeeEstimates())['1']; } const { psbt } = await (0, exports.createExecutePsbt)({ frontendFee, feeAddress, gatheredUtxos, account, protostone, provider, feeRate, alkaneUtxos, }); const { fee: estimatedFee } = await (0, psbt_1.getEstimatedFee)({ feeRate, psbt, provider, }); const { psbt: finalPsbt } = await (0, exports.createExecutePsbt)({ frontendFee, feeAddress, gatheredUtxos, account, protostone, provider, feeRate, alkaneUtxos, fee: estimatedFee, }); const { fee: finalFee, vsize } = await (0, psbt_1.getEstimatedFee)({ feeRate, psbt: finalPsbt, provider, }); return { fee: finalFee, vsize }; }; exports.actualExecuteFee = actualExecuteFee; const executePsbt = async ({ alkaneUtxos, gatheredUtxos, account, protostone, provider, feeRate, frontendFee, feeAddress, }) => { const { fee } = await (0, exports.actualExecuteFee)({ frontendFee, feeAddress, alkaneUtxos, gatheredUtxos, account, protostone, provider, feeRate, }); const { psbt: finalPsbt } = await (0, exports.createExecutePsbt)({ frontendFee, feeAddress, alkaneUtxos, gatheredUtxos, account, protostone, provider, feeRate, fee, }); return { psbt: finalPsbt, fee }; }; exports.executePsbt = executePsbt; const execute = async ({ alkaneUtxos, gatheredUtxos, account, protostone, provider, feeRate, signer, frontendFee, feeAddress, }) => { const { fee } = await (0, exports.actualExecuteFee)({ frontendFee, feeAddress, alkaneUtxos, gatheredUtxos, account, protostone, provider, feeRate, }); const { psbt: finalPsbt } = await (0, exports.createExecutePsbt)({ frontendFee, feeAddress, alkaneUtxos, gatheredUtxos, account, protostone, provider, feeRate, fee, }); const { signedPsbt } = await signer.signAllInputs({ rawPsbt: finalPsbt, finalize: true, }); const pushResult = await provider.pushPsbt({ psbtBase64: signedPsbt, }); return pushResult; }; exports.execute = execute; const createTransactReveal = async ({ protostone, receiverAddress, script, feeRate, tweakedPublicKey, 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 < 250 ? 250 : 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 errors_1.OylTransactionError(new Error('Error getting vin #0 value')); } const p2pk_redeem = { output: script }; const { output, witness } = bitcoin.payments.p2tr({ internalPubkey: (0, bip371_1.toXOnly)(Buffer.from(tweakedPublicKey, 'hex')), 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, }); psbt.addOutput({ value: 0, script: protostone, }); if (revealTxChange > 546) { psbt.addOutput({ value: revealTxChange, address: receiverAddress, }); } return { psbt: psbt.toBase64(), fee: revealTxChange, }; } catch (error) { throw new errors_1.OylTransactionError(error); } }; exports.createTransactReveal = createTransactReveal; //# sourceMappingURL=alkanes.js.map