UNPKG

@oyl/sdk

Version:
249 lines 10.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.actualFee = exports.send = exports.findCollectible = exports.createPsbt = void 0; const tslib_1 = require("tslib"); const btc_1 = require("../btc/btc"); const bitcoin = tslib_1.__importStar(require("bitcoinjs-lib")); const utils_1 = require("../shared/utils"); const errors_1 = require("../errors"); const utils_2 = require("../shared/utils"); const createPsbt = async ({ gatheredUtxos, account, inscriptionId, provider, inscriptionAddress, toAddress, feeRate, fee, }) => { try { const originalGatheredUtxos = gatheredUtxos; const minFee = (0, btc_1.minimumFee)({ taprootInputCount: 1, 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 { txId, voutIndex, data } = await (0, exports.findCollectible)({ address: inscriptionAddress, provider, inscriptionId, }); if ((0, utils_2.getAddressType)(inscriptionAddress) === 0) { const previousTxHex = await provider.esplora.getTxHex(txId); psbt.addInput({ hash: txId, index: parseInt(voutIndex), nonWitnessUtxo: Buffer.from(previousTxHex, 'hex'), }); } if ((0, utils_2.getAddressType)(inscriptionAddress) === 2) { const redeemScript = bitcoin.script.compile([ bitcoin.opcodes.OP_0, bitcoin.crypto.hash160(Buffer.from(account.nestedSegwit.pubkey, 'hex')), ]); psbt.addInput({ hash: txId, index: parseInt(voutIndex), redeemScript: redeemScript, witnessUtxo: { value: data.value, script: bitcoin.script.compile([ bitcoin.opcodes.OP_HASH160, bitcoin.crypto.hash160(redeemScript), bitcoin.opcodes.OP_EQUAL, ]), }, }); } if ((0, utils_2.getAddressType)(inscriptionAddress) === 1 || (0, utils_2.getAddressType)(inscriptionAddress) === 3) { psbt.addInput({ hash: txId, index: parseInt(voutIndex), witnessUtxo: { script: Buffer.from(data.scriptpubkey, 'hex'), value: data.value, }, }); } psbt.addOutput({ address: toAddress, value: data.value, }); gatheredUtxos = (0, utils_1.findXAmountOfSats)(originalGatheredUtxos.utxos, Number(finalFee)); 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; gatheredUtxos = (0, utils_1.findXAmountOfSats)(originalGatheredUtxos.utxos, Number(finalFee)); } 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 formattedPsbtTx = await (0, utils_1.formatInputsToSign)({ _psbt: psbt, senderPublicKey: account.taproot.pubkey, network: provider.network, }); return { psbt: formattedPsbtTx.toBase64() }; } catch (error) { throw new errors_1.OylTransactionError(error); } }; exports.createPsbt = createPsbt; const findCollectible = async ({ address, provider, inscriptionId, }) => { const collectibleData = await provider.ord.getInscriptionById(inscriptionId); if (collectibleData.address !== address) { throw new Error('Inscription does not belong to the address given'); } const inscriptionTxId = collectibleData.satpoint.split(':')[0]; const inscriptionTxVOutIndex = collectibleData.satpoint.split(':')[1]; const inscriptionUtxoDetails = await provider.esplora.getTxInfo(inscriptionTxId); const inscriptionUtxoData = inscriptionUtxoDetails.vout[inscriptionTxVOutIndex]; const outputId = `${inscriptionTxId}:${inscriptionTxVOutIndex}`; const [inscriptionsOnOutput, isSpentArray] = await Promise.all([ provider.ord.getTxOutput(outputId), provider.esplora.getTxOutspends(inscriptionTxId) ]); const isSpent = isSpentArray[inscriptionTxVOutIndex]; //NOTE: The inscriptionsOnOutput.runes array check is only for sandshrew v1 if (inscriptionsOnOutput.inscriptions.length > 1 || Array.isArray(inscriptionsOnOutput.runes) ? Number(inscriptionsOnOutput.runes.length) > 0 : Object.keys(inscriptionsOnOutput.runes).length > 0) { throw new Error('Unable to send from UTXO with multiple inscriptions. Split UTXO before sending.'); } if (isSpent.spent) { throw new Error('Inscription is missing'); } return { txId: inscriptionTxId, voutIndex: inscriptionTxVOutIndex, data: inscriptionUtxoData, }; }; exports.findCollectible = findCollectible; const send = async ({ gatheredUtxos, toAddress, inscriptionId, inscriptionAddress, feeRate, account, provider, signer, fee, }) => { if (!inscriptionAddress) { inscriptionAddress = account.taproot.address; } if (!fee) { fee = (await (0, exports.actualFee)({ gatheredUtxos, account, inscriptionId, provider, inscriptionAddress, toAddress, feeRate, signer, })).fee; } const { psbt: finalPsbt } = await (0, exports.createPsbt)({ gatheredUtxos, account, inscriptionId, provider, toAddress, inscriptionAddress: inscriptionAddress, feeRate, fee, }); const { signedPsbt } = await signer.signAllInputs({ rawPsbt: finalPsbt, finalize: true, }); const result = await provider.pushPsbt({ psbtBase64: signedPsbt, }); return result; }; exports.send = send; const actualFee = async ({ gatheredUtxos, account, inscriptionId, provider, inscriptionAddress = account.taproot.address, toAddress, feeRate, signer, }) => { const { psbt } = await (0, exports.createPsbt)({ gatheredUtxos, account, inscriptionId, provider, inscriptionAddress, toAddress, feeRate, }); const { signedPsbt } = await signer.signAllInputs({ rawPsbt: psbt, finalize: true, }); let rawPsbt = bitcoin.Psbt.fromBase64(signedPsbt, { network: account.network, }); const signedHexPsbt = rawPsbt.extractTransaction().toHex(); const vsize = (await provider.sandshrew.bitcoindRpc.testMemPoolAccept([signedHexPsbt]))[0].vsize; const correctFee = vsize * feeRate; const { psbt: finalPsbt } = await (0, exports.createPsbt)({ gatheredUtxos, account, inscriptionId, provider, inscriptionAddress, toAddress, feeRate, fee: correctFee, }); const { signedPsbt: signedAll } = await signer.signAllInputs({ rawPsbt: finalPsbt, finalize: true, }); let finalRawPsbt = bitcoin.Psbt.fromBase64(signedAll, { network: account.network, }); const finalSignedHexPsbt = finalRawPsbt.extractTransaction().toHex(); const finalVsize = (await provider.sandshrew.bitcoindRpc.testMemPoolAccept([finalSignedHexPsbt]))[0].vsize; const finalFee = finalVsize * feeRate; return { fee: finalFee }; }; exports.actualFee = actualFee; //# sourceMappingURL=collectible.js.map