UNPKG

opnet

Version:

The perfect library for building Bitcoin-based applications.

1,361 lines 302 kB
import { a as __toESM, r as __exportAll } from "./rolldown-runtime.js"; import { A as bitcoin, C as AddressVerificator, D as fromHex, E as toBase64, M as regtest, N as testnet, O as toHex, S as AddressTypes, T as fromBase64, _ as BinaryWriter, a as ABICoder, b as AddressMap, c as isAbiTuple, d as process$1, f as P2MR_MS, g as Logger, h as TransactionFactory, i as NetEvent, j as opnetTestnet, k as fromBech32, l as ABIDataTypes, m as ChallengeSolution, n as Long, o as abiTypeToSelectorString, p as P2TR_MS, r as pLimit, s as isAbiStruct, t as BigNumber, u as init_dist, v as BinaryReader, w as decompile, x as Address, y as BufferHelper } from "./vendors.js"; import { t as require_protobuf_min } from "./protobuf.js"; //#region src/_version.ts var version = "1.8.13"; //#endregion //#region src/interfaces/opnet/OPNetTransactionTypes.ts var OPNetTransactionTypes = /* @__PURE__ */ function(OPNetTransactionTypes) { OPNetTransactionTypes["Generic"] = "Generic"; OPNetTransactionTypes["Deployment"] = "Deployment"; OPNetTransactionTypes["Interaction"] = "Interaction"; return OPNetTransactionTypes; }({}); //#endregion //#region src/transactions/metadata/TransactionInput.ts /** * Transaction input * @category ITransactions */ var TransactionInput = class { originalTransactionId; outputTransactionIndex; scriptSignature; sequenceId; transactionInWitness = []; constructor(data) { this.originalTransactionId = data.originalTransactionId; this.outputTransactionIndex = data.outputTransactionIndex; this.scriptSignature = data.scriptSignature; this.sequenceId = data.sequenceId; this.transactionInWitness = data.transactionInWitness || []; } }; //#endregion //#region src/transactions/metadata/TransactionOutput.ts /** * Transaction output * @category Transactions */ var TransactionOutput = class { value; index; scriptPubKey; script; constructor(data) { this.value = this.convertValue(data.value); this.index = data.index; this.scriptPubKey = data.scriptPubKey; this.script = decompile(fromHex(this.scriptPubKey.hex)); } convertValue(value) { return BigInt(value); } }; //#endregion //#region src/cache/LRUCaching.ts var LRUCache = class { cache; maxSize; constructor(maxSize) { this.cache = /* @__PURE__ */ new Map(); this.maxSize = maxSize; } get(key) { const value = this.cache.get(key); if (value !== void 0) { this.cache.delete(key); this.cache.set(key, value); } return value; } set(key, value) { if (this.cache.has(key)) this.cache.delete(key); else if (this.cache.size >= this.maxSize) { const firstKey = this.cache.keys().next().value; if (firstKey !== void 0) this.cache.delete(firstKey); } this.cache.set(key, value); } }; //#endregion //#region src/cache/P2OPCache.ts var P2OP_CACHE_MAX_SIZE = 5e3; var p2opCache = new LRUCache(P2OP_CACHE_MAX_SIZE); var addressCache = new LRUCache(P2OP_CACHE_MAX_SIZE); var getP2op = (rawAddress, network) => { const cacheKey = `${network.bip32}:${network.pubKeyHash}:${network.bech32}:${rawAddress}`; let cached = p2opCache.get(cacheKey); if (cached === void 0) { let addr = addressCache.get(rawAddress); if (addr === void 0) { addr = Address.fromString(rawAddress); addressCache.set(rawAddress, addr); } cached = addr.p2op(network); p2opCache.set(cacheKey, cached); } return cached; }; //#endregion //#region src/utils/RevertDecoder.ts /** * Utility functions for decoding revert data. */ var ERROR_SELECTOR_BYTES = Uint8Array.from([ 99, 115, 157, 92 ]); function areBytesEqual(a, b) { if (a.length !== b.length) return false; for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false; return true; } function startsWithErrorSelector(revertDataBytes) { return revertDataBytes.length >= 4 && areBytesEqual(revertDataBytes.subarray(0, 4), ERROR_SELECTOR_BYTES); } function bytesToHexString(byteArray) { return Array.from(byteArray, function(byte) { return ("0" + (byte & 255).toString(16)).slice(-2); }).join(""); } /** * Decode revert data into a human-readable string. * @param revertDataBytes - The raw revert data bytes. * @returns The decoded revert message. */ function decodeRevertData(revertDataBytes) { if (startsWithErrorSelector(revertDataBytes)) { const decoder = new TextDecoder(); const buf = revertDataBytes.subarray(8); return decoder.decode(buf); } else return `Unknown Revert: 0x${bytesToHexString(revertDataBytes)}`; } //#endregion //#region src/transactions/metadata/TransactionReceipt.ts /** * Transaction receipt * @category Transactions */ var TransactionReceipt = class { /** * @description The receipt of the transaction. */ receipt; /** * @description The receipt proofs of the transaction. */ receiptProofs; /** * @description The events of the transaction. */ events; rawEvents = {}; /** * @description If the transaction was reverted, this field will contain the revert message. */ rawRevert; revert; /** * @description Whether the transaction failed (reverted) or not. */ failed = false; gasUsed; specialGasUsed; constructor(receipt, network) { this.receipt = receipt.receipt ? fromBase64(receipt.receipt) : void 0; this.receiptProofs = receipt.receiptProofs || []; this.events = receipt.events ? this.parseEvents(receipt.events, network) : {}; this.rawRevert = receipt.revert ? fromBase64(receipt.revert) : void 0; this.revert = this.rawRevert ? decodeRevertData(this.rawRevert) : void 0; this.failed = receipt.revert !== void 0 && receipt.revert !== null; this.gasUsed = BigInt(receipt.gasUsed || "0x00") || 0n; this.specialGasUsed = BigInt(receipt.specialGasUsed || "0x00") || 0n; } /** * @description Parse transaction events. * @param events - The events to parse. * @param network - The network to use. * @private */ parseEvents(events, network) { const parsedEvents = {}; if (!Array.isArray(events)) for (const [key, value] of Object.entries(events)) { const caP2op = getP2op(key, network); const v = value.map((event) => { return this.decodeEvent(event); }); parsedEvents[caP2op] = v; this.rawEvents[key] = v; } else for (const event of events) { const parsedEvent = this.decodeEvent(event); const contractAddress = event.contractAddress; const caP2op = getP2op(contractAddress, network); if (!parsedEvents[caP2op]) parsedEvents[caP2op] = []; parsedEvents[caP2op].push(parsedEvent); if (!this.rawEvents[contractAddress]) this.rawEvents[contractAddress] = []; this.rawEvents[contractAddress].push(parsedEvent); } return parsedEvents; } decodeEvent(event) { let eventData; if (typeof event.data === "string") eventData = fromBase64(event.data); else eventData = event.data; return new NetEvent(event.type, eventData); } }; //#endregion //#region src/transactions/Transaction.ts /** * @description This class is used to provide a base transaction. * @class Transaction * @implements {ITransactionBase<T>} * @template T * @category Transactions * @abstract */ var TransactionBase = class extends TransactionReceipt { /** * @description The transaction ID (hash). */ id; /** * @description The transaction "hash". */ hash; /** * @description The index of the transaction in the block. */ index; /** * @description Returns the amount of satoshi that were burned in the transaction. */ burnedBitcoin; /** * @description The priority fee of the transaction. */ priorityFee; /** * @description The maximum amount of gas that can be spent by the transaction. */ maxGasSat; /** * @description The inputs of the transaction. */ inputs; /** * @description The outputs of the transaction. */ outputs; /** * @description The type of the transaction. */ OPNetType; /** * @description The amount of gas used by the transaction. */ gasUsed; /** * @description Special gas used by the transaction. */ specialGasUsed; /** * @description The proof of work challenge. */ pow; /** * @description The block number in which the transaction was included. */ blockNumber; constructor(transaction, network) { super({ receipt: transaction.receipt, receiptProofs: transaction.receiptProofs, events: transaction.events, revert: transaction.revert, gasUsed: transaction.gasUsed, specialGasUsed: transaction.specialGasUsed }, network); this.id = transaction.id; this.hash = transaction.hash; this.index = transaction.index; if (transaction.blockNumber) this.blockNumber = BigInt(transaction.blockNumber); this.burnedBitcoin = BigInt(transaction.burnedBitcoin) || 0n; this.priorityFee = BigInt(transaction.priorityFee) || 0n; this.inputs = transaction.inputs.map((input) => new TransactionInput(input)); this.outputs = transaction.outputs.map((output) => new TransactionOutput(output)); this.OPNetType = transaction.OPNetType; this.gasUsed = BigInt(transaction.gasUsed || "0x00") || 0n; this.specialGasUsed = BigInt(transaction.specialGasUsed || "0x00") || 0n; if (transaction.pow) this.pow = this.decodeProofOfWorkChallenge(transaction.pow); this.maxGasSat = this.burnedBitcoin + (this.pow?.reward || 0n) - this.priorityFee; } decodeProofOfWorkChallenge(challenge) { return { preimage: fromBase64(challenge.preimage), reward: BigInt(challenge.reward) || 0n, difficulty: BigInt(challenge.difficulty || "0"), version: challenge.version || 0 }; } }; //#endregion //#region src/transactions/decoders/DeploymentTransaction.ts /** * @desc This class is used to provide a deployment transaction. Properties could be null if reverted. * @class DeploymentTransaction * @category Transactions */ var DeploymentTransaction = class extends TransactionBase { contractAddress; contractPublicKey; bytecode; wasCompressed; deployerPubKey; deployerHashedPublicKey; deployerAddress; contractSeed; contractSaltHash; from; constructor(transaction, network) { super(transaction, network); if (!transaction.deployerAddress && (transaction.revert === null || transaction.revert === void 0)) throw new Error("Deployer address is missing"); try { this.from = new Address(fromBase64(transaction.from), fromBase64(transaction.fromLegacy)); this.contractAddress = transaction.contractAddress; this.contractPublicKey = new Address(fromBase64(transaction.contractPublicKey)); this.bytecode = fromBase64(transaction.bytecode); this.wasCompressed = transaction.wasCompressed; if (transaction.deployerPubKey) this.deployerPubKey = fromBase64(transaction.deployerPubKey); if (transaction.deployerAddress) { const deployerAddr = transaction.deployerAddress; this.deployerHashedPublicKey = fromHex(deployerAddr.startsWith("0x") ? deployerAddr.slice(2) : deployerAddr); } if (this.deployerHashedPublicKey && this.deployerPubKey) this.deployerAddress = new Address(this.deployerHashedPublicKey, this.deployerPubKey); this.contractSeed = fromBase64(transaction.contractSeed); this.contractSaltHash = fromBase64(transaction.contractSaltHash); } catch {} } }; //#endregion //#region src/transactions/decoders/GenericTransaction.ts /** * @description This class is used to create a generic transaction. * @class GenericTransaction * @extends {TransactionBase<OPNetTransactionTypes.Generic>} * @implements {IGenericTransaction} * @category Transactions */ var GenericTransaction = class extends TransactionBase { constructor(transaction, network) { super(transaction, network); } }; //#endregion //#region src/transactions/decoders/InteractionTransaction.ts /** * Interaction transaction. Properties could be null if reverted. * @category Transactions */ var InteractionTransaction = class extends TransactionBase { /** * @description The calldata of the transaction. */ calldata; /** * @description The sender's public key hash. */ senderPubKeyHash; /** * @description The contract secret. */ contractSecret; /** * @description The interaction public key. */ interactionPubKey; /** * @description Whether the transaction data was compressed. */ wasCompressed; /** * @description The from address of the transaction. (ALWAYS TAPROOT. *This address is generated from the P2TR of the pubkey of the deployer.*) */ from; /** * @description The contract address where the transaction was sent. (AKA "to"). */ contractAddress; /** * @description The contract tweaked public key. */ contractPublicKey; constructor(transaction, network) { super(transaction, network); this.contractPublicKey = new Address(fromBase64(transaction.contractPublicKey)); if (transaction.calldata) this.calldata = fromBase64(transaction.calldata); this.senderPubKeyHash = fromBase64(transaction.senderPubKeyHash); this.contractSecret = fromBase64(transaction.contractSecret); this.interactionPubKey = fromBase64(transaction.interactionPubKey); this.wasCompressed = transaction.wasCompressed || false; this.contractAddress = transaction.contractAddress; try { if (transaction.from) this.from = new Address(fromBase64(transaction.from), fromBase64(transaction.fromLegacy)); } catch {} } }; //#endregion //#region src/serialize/BigInt.ts BigInt.prototype.toJSON = function() { return this.toString(); }; //#endregion //#region src/transactions/TransactionParser.ts /** * Transaction parser * @category Transactions */ var TransactionParser = class { static parseTransactions(transactions, network) { if (!transactions) return []; const transactionArray = []; for (const transaction of transactions) { if (!transaction) throw new Error(`Something went wrong while parsing transactions`); transactionArray.push(this.parseTransaction(transaction, network)); } return transactionArray; } static parseTransaction(transaction, network) { if (!transaction) throw new Error("Transaction is required"); switch (transaction.OPNetType) { case OPNetTransactionTypes.Generic: return new GenericTransaction(transaction, network); case OPNetTransactionTypes.Interaction: return new InteractionTransaction(transaction, network); case OPNetTransactionTypes.Deployment: return new DeploymentTransaction(transaction, network); default: throw new Error("Unknown transaction type"); } } }; //#endregion //#region src/block/Block.ts /** * @description This class is used to represent a block. * @class Block * @category Block */ var Block = class { height; hash; previousBlockHash; previousBlockChecksum; bits; nonce; version; size; txCount; weight; strippedSize; time; medianTime; checksumRoot; merkleRoot; storageRoot; receiptRoot; ema; baseGas; gasUsed; checksumProofs; _rawBlock; _network; constructor(block, network) { if (!block) throw new Error("Invalid block."); this._rawBlock = block; this._network = network; this.height = BigInt(block.height.toString()); this.hash = block.hash; this.previousBlockHash = block.previousBlockHash; this.previousBlockChecksum = block.previousBlockChecksum; this.bits = block.bits; this.nonce = block.nonce; this.version = block.version; this.size = block.size; this.txCount = block.txCount; this.ema = BigInt(block.ema); this.baseGas = BigInt(block.baseGas); this.gasUsed = BigInt(block.gasUsed); this.weight = block.weight; this.strippedSize = block.strippedSize; this.time = block.time; this.medianTime = block.medianTime; this.checksumRoot = block.checksumRoot; this.merkleRoot = block.merkleRoot; this.storageRoot = block.storageRoot; this.receiptRoot = block.receiptRoot; this.checksumProofs = block.checksumProofs; } _transactions; get transactions() { if (!this._transactions) this._transactions = TransactionParser.parseTransactions(this._rawBlock.transactions, this._network); return this._transactions; } _deployments; get deployments() { if (!this._deployments) this._deployments = this._rawBlock.deployments ? this._rawBlock.deployments.map((address) => Address.fromString(address)) : []; return this._deployments; } get rawTransactions() { return this._rawBlock.transactions; } }; //#endregion //#region src/block/BlockGasParameters.ts var BlockGasParameters = class { blockNumber; gasUsed; targetGasLimit; ema; baseGas; gasPerSat; bitcoin; constructor(data) { this.blockNumber = BigInt(data.blockNumber); this.gasUsed = BigInt(data.gasUsed); this.targetGasLimit = BigInt(data.targetGasLimit); this.ema = BigInt(data.ema); this.baseGas = BigInt(data.baseGas); this.gasPerSat = BigInt(data.gasPerSat); this.bitcoin = { conservative: Number(data.bitcoin.conservative), recommended: { low: Number(data.bitcoin.recommended.low), medium: Number(data.bitcoin.recommended.medium), high: Number(data.bitcoin.recommended.high) } }; } }; //#endregion //#region src/utils/StringToBuffer.ts function stringToBuffer(str) { return fromHex(str.startsWith("0x") ? str.slice(2) : str); } function stringBase64ToBuffer(str) { return fromBase64(str); } //#endregion //#region src/block/BlockWitness.ts var BlockWitnessAPI = class { signature; timestamp; proofs; identity; publicKey; constructor(data) { this.signature = stringBase64ToBuffer(data.signature); this.timestamp = data.timestamp; this.proofs = Object.freeze(data.proofs.map((proof) => stringBase64ToBuffer(proof))); this.identity = data.identity ? stringBase64ToBuffer(data.identity) : void 0; this.publicKey = data.publicKey ? Address.fromString(data.publicKey) : void 0; } }; var BlockWitness = class { blockNumber; witnesses; constructor(data) { this.blockNumber = typeof data.blockNumber === "string" ? BigInt(data.blockNumber) : data.blockNumber; this.witnesses = Object.freeze(data.witnesses.map((witness) => new BlockWitnessAPI(witness))); } }; function parseBlockWitnesses(rawWitnesses) { return Object.freeze(rawWitnesses.map((rawWitness) => new BlockWitness(rawWitness))); } //#endregion //#region src/contracts/CallResultSerializer.ts /** * Network name enum for serialization. * @category Contracts */ var NetworkName = /* @__PURE__ */ function(NetworkName) { NetworkName["Mainnet"] = "mainnet"; NetworkName["Testnet"] = "testnet"; NetworkName["OpnetTestnet"] = "opnetTestnet"; NetworkName["Regtest"] = "regtest"; return NetworkName; }({}); /** * Version of the serialization format. * Increment when making breaking changes. */ var SERIALIZATION_VERSION = 1; /** * Serializer/Deserializer for CallResult offline signing data. * Uses binary format for efficient transfer (QR codes, files, etc.) * @category Contracts */ var CallResultSerializer = class { static FEE_PRECISION = 1e6; /** * Serializes offline data to a Uint8Array. * @param {OfflineCallResultData} data - The data to serialize. * @returns {Uint8Array} The serialized binary data. */ static serialize(data) { const writer = new BinaryWriter(); writer.writeU8(SERIALIZATION_VERSION); writer.writeU8(this.networkNameToU8(data.network)); writer.writeBytesWithLength(data.calldata); writer.writeStringWithLength(data.to); writer.writeStringWithLength(data.contractAddress); writer.writeU256(data.estimatedSatGas); writer.writeU256(data.estimatedRefundedGasInSat); writer.writeU256(data.estimatedGas ?? 0n); writer.writeU256(data.refundedGas ?? 0n); if (data.revert !== void 0) { writer.writeBoolean(true); writer.writeStringWithLength(data.revert); } else writer.writeBoolean(false); writer.writeBytesWithLength(data.result); this.writeAccessList(writer, data.accessList); if (data.bitcoinFees) { writer.writeBoolean(true); this.writeBitcoinFees(writer, data.bitcoinFees); } else writer.writeBoolean(false); this.writeChallenge(writer, data.challenge); writer.writeBytesWithLength(data.challengeOriginalPublicKey); this.writeUTXOs(writer, data.utxos); if (data.csvAddress) { writer.writeBoolean(true); writer.writeStringWithLength(data.csvAddress.address); writer.writeBytesWithLength(data.csvAddress.witnessScript); } else writer.writeBoolean(false); return writer.getBuffer(); } /** * Deserializes a Uint8Array to offline data. * @param {Uint8Array} buffer - The serialized data. * @returns {OfflineCallResultData} The deserialized data. */ static deserialize(buffer) { const reader = new BinaryReader(buffer); const version = reader.readU8(); if (version !== SERIALIZATION_VERSION) throw new Error(`Unsupported serialization version: ${version}. Expected: ${SERIALIZATION_VERSION}`); const network = this.u8ToNetworkName(reader.readU8()); const calldata = reader.readBytesWithLength(); const to = reader.readStringWithLength(); const contractAddress = reader.readStringWithLength(); const estimatedSatGas = reader.readU256(); const estimatedRefundedGasInSat = reader.readU256(); const estimatedGasRaw = reader.readU256(); const refundedGasRaw = reader.readU256(); return { calldata, to, contractAddress, estimatedSatGas, estimatedRefundedGasInSat, estimatedGas: estimatedGasRaw > 0n ? estimatedGasRaw : void 0, refundedGas: refundedGasRaw > 0n ? refundedGasRaw : void 0, revert: reader.readBoolean() ? reader.readStringWithLength() : void 0, result: reader.readBytesWithLength(), accessList: this.readAccessList(reader), bitcoinFees: reader.readBoolean() ? this.readBitcoinFees(reader) : void 0, network, challenge: this.readChallenge(reader), challengeOriginalPublicKey: reader.readBytesWithLength(), utxos: this.readUTXOs(reader), csvAddress: reader.readBoolean() ? { address: reader.readStringWithLength(), witnessScript: reader.readBytesWithLength() } : void 0 }; } static networkNameToU8(network) { switch (network) { case NetworkName.Mainnet: return 0; case NetworkName.Testnet: return 1; case NetworkName.Regtest: return 2; case NetworkName.OpnetTestnet: return 3; default: return 2; } } static u8ToNetworkName(value) { switch (value) { case 0: return NetworkName.Mainnet; case 1: return NetworkName.Testnet; case 2: return NetworkName.Regtest; case 3: return NetworkName.OpnetTestnet; default: return NetworkName.Regtest; } } static writeAccessList(writer, accessList) { const contracts = Object.keys(accessList); writer.writeU16(contracts.length); for (const contract of contracts) { writer.writeStringWithLength(contract); const slots = accessList[contract]; const slotKeys = Object.keys(slots); writer.writeU16(slotKeys.length); for (const key of slotKeys) { writer.writeStringWithLength(key); writer.writeStringWithLength(slots[key]); } } } static readAccessList(reader) { const accessList = {}; const contractCount = reader.readU16(); for (let i = 0; i < contractCount; i++) { const contract = reader.readStringWithLength(); const slotCount = reader.readU16(); accessList[contract] = {}; for (let j = 0; j < slotCount; j++) { const key = reader.readStringWithLength(); const value = reader.readStringWithLength(); accessList[contract][key] = value; } } return accessList; } static writeBitcoinFees(writer, fees) { writer.writeU64(BigInt(Math.round(fees.conservative * this.FEE_PRECISION))); writer.writeU64(BigInt(Math.round(fees.recommended.low * this.FEE_PRECISION))); writer.writeU64(BigInt(Math.round(fees.recommended.medium * this.FEE_PRECISION))); writer.writeU64(BigInt(Math.round(fees.recommended.high * this.FEE_PRECISION))); } static readBitcoinFees(reader) { return { conservative: Number(reader.readU64()) / this.FEE_PRECISION, recommended: { low: Number(reader.readU64()) / this.FEE_PRECISION, medium: Number(reader.readU64()) / this.FEE_PRECISION, high: Number(reader.readU64()) / this.FEE_PRECISION } }; } static writeChallenge(writer, challenge) { writer.writeStringWithLength(challenge.epochNumber); writer.writeStringWithLength(challenge.mldsaPublicKey); writer.writeStringWithLength(challenge.legacyPublicKey); writer.writeStringWithLength(challenge.solution); writer.writeStringWithLength(challenge.salt); writer.writeStringWithLength(challenge.graffiti); writer.writeU256(BigInt(challenge.difficulty)); writer.writeStringWithLength(challenge.verification.epochHash); writer.writeStringWithLength(challenge.verification.epochRoot); writer.writeStringWithLength(challenge.verification.targetHash); writer.writeStringWithLength(challenge.verification.targetChecksum); writer.writeStringWithLength(challenge.verification.startBlock); writer.writeStringWithLength(challenge.verification.endBlock); writer.writeU16(challenge.verification.proofs.length); for (const proof of challenge.verification.proofs) writer.writeStringWithLength(proof); if (challenge.submission) { writer.writeBoolean(true); writer.writeStringWithLength(challenge.submission.mldsaPublicKey); writer.writeStringWithLength(challenge.submission.legacyPublicKey); writer.writeStringWithLength(challenge.submission.solution); writer.writeStringWithLength(challenge.submission.graffiti || ""); writer.writeStringWithLength(challenge.submission.signature); } else writer.writeBoolean(false); } static readChallenge(reader) { const epochNumber = reader.readStringWithLength(); const mldsaPublicKey = reader.readStringWithLength(); const legacyPublicKey = reader.readStringWithLength(); const solution = reader.readStringWithLength(); const salt = reader.readStringWithLength(); const graffiti = reader.readStringWithLength(); const difficulty = Number(reader.readU256()); const epochHash = reader.readStringWithLength(); const epochRoot = reader.readStringWithLength(); const targetHash = reader.readStringWithLength(); const targetChecksum = reader.readStringWithLength(); const startBlock = reader.readStringWithLength(); const endBlock = reader.readStringWithLength(); const proofCount = reader.readU16(); const proofs = []; for (let i = 0; i < proofCount; i++) proofs.push(reader.readStringWithLength()); const submission = reader.readBoolean() ? { mldsaPublicKey: reader.readStringWithLength(), legacyPublicKey: reader.readStringWithLength(), solution: reader.readStringWithLength(), graffiti: reader.readStringWithLength() || void 0, signature: reader.readStringWithLength() } : void 0; return { epochNumber, mldsaPublicKey, legacyPublicKey, solution, salt, graffiti, difficulty, verification: { epochHash, epochRoot, targetHash, targetChecksum, startBlock, endBlock, proofs }, submission }; } static writeUTXOs(writer, utxos) { writer.writeU16(utxos.length); for (const utxo of utxos) { writer.writeStringWithLength(utxo.transactionId); writer.writeU32(utxo.outputIndex); writer.writeU64(utxo.value); writer.writeStringWithLength(utxo.scriptPubKey.hex); writer.writeStringWithLength(utxo.scriptPubKey.address || ""); writer.writeBoolean(!!utxo.isCSV); if (utxo.witnessScript) { writer.writeBoolean(true); const witnessScriptBytes = typeof utxo.witnessScript === "string" ? fromHex(utxo.witnessScript) : utxo.witnessScript; writer.writeBytesWithLength(witnessScriptBytes); } else writer.writeBoolean(false); if (utxo.redeemScript) { writer.writeBoolean(true); const redeemScriptBytes = typeof utxo.redeemScript === "string" ? fromHex(utxo.redeemScript) : utxo.redeemScript; writer.writeBytesWithLength(redeemScriptBytes); } else writer.writeBoolean(false); } } static readUTXOs(reader) { const count = reader.readU16(); const utxos = []; for (let i = 0; i < count; i++) { const transactionId = reader.readStringWithLength(); const outputIndex = reader.readU32(); const value = reader.readU64(); const hex = reader.readStringWithLength(); const addressStr = reader.readStringWithLength(); const isCSV = reader.readBoolean(); const witnessScript = reader.readBoolean() ? reader.readBytesWithLength() : void 0; const redeemScript = reader.readBoolean() ? reader.readBytesWithLength() : void 0; utxos.push({ transactionId, outputIndex, value, scriptPubKey: { hex, address: addressStr || void 0 }, isCSV, witnessScript, redeemScript }); } return utxos; } }; //#endregion //#region src/contracts/TransactionHelpper.ts var TransactionHelper = class { static estimateMiningCost(utxos, extraOutputs, opReturnLen, network, feeRate) { const vBytes = this.estimateVBytes(utxos, extraOutputs, opReturnLen, network); return BigInt(Math.ceil(vBytes * feeRate)); } static varIntLen(n) { return n < 253 ? 1 : n <= 65535 ? 3 : n <= 4294967295 ? 5 : 9; } static estimateVBytes(utxos, extraOutputs, scriptLength, network) { const INPUT_WU = { [AddressTypes.P2PKH]: 592, [AddressTypes.P2SH_OR_P2SH_P2WPKH]: 471, [AddressTypes.P2WPKH]: 271, [AddressTypes.P2TR]: 229, [AddressTypes.P2MR]: 197, [AddressTypes.P2PK]: 592, [AddressTypes.P2WSH]: 272, [AddressTypes.P2OP]: 271, [AddressTypes.P2WDA]: 417 }; const OUTPUT_BYTES = { [AddressTypes.P2PKH]: 34, [AddressTypes.P2SH_OR_P2SH_P2WPKH]: 32, [AddressTypes.P2WPKH]: 31, [AddressTypes.P2TR]: 43, [AddressTypes.P2MR]: 43, [AddressTypes.P2PK]: 34, [AddressTypes.P2OP]: 32, [AddressTypes.P2WSH]: 43, [AddressTypes.P2WDA]: 43 }; const ins = utxos.length ? utxos : new Array(3).fill(null); let weight = 0; weight += 32; if (utxos.length === 0 || utxos.some((u) => { const t = AddressVerificator.detectAddressType(u?.scriptPubKey?.address ?? "", network); return t === AddressTypes.P2WPKH || t === AddressTypes.P2SH_OR_P2SH_P2WPKH || t === AddressTypes.P2TR || t === AddressTypes.P2MR || t === AddressTypes.P2OP || t === AddressTypes.P2WSH; })) weight += 8; weight += this.varIntLen(ins.length) * 4; weight += this.varIntLen(extraOutputs.length) * 4; for (const u of ins) { const t = utxos.length === 0 ? AddressTypes.P2TR : AddressVerificator.detectAddressType(u?.scriptPubKey?.address ?? "", network) ?? AddressTypes.P2PKH; weight += INPUT_WU[t] ?? 440; } for (const o of extraOutputs) if ("address" in o) { const t = AddressVerificator.detectAddressType(o.address, network) ?? AddressTypes.P2PKH; weight += (OUTPUT_BYTES[t] ?? 40) * 4; } else if ("script" in o) { const scriptLen = o.script.length; const bytes = 8 + this.varIntLen(scriptLen) + scriptLen; weight += bytes * 4; } else weight += 136; const witnessBytes = 1 + 3 * (this.varIntLen(32) + 32); weight += witnessBytes; const stackItemScript = this.varIntLen(scriptLength) + scriptLength; weight += stackItemScript + 34; return Math.ceil(weight / 4); } }; //#endregion //#region src/contracts/CallResult.ts var factory = new TransactionFactory(); function extractPackageFailures(packageResult) { const failures = []; const results = packageResult.txResults; for (const [submittedTxid, result] of Object.entries(results)) if (result.error) failures.push(`tx ${submittedTxid} failed: ${result.error}`); if (failures.length === 0 && packageResult.packageMsg !== "success") failures.push(`package rejected: ${packageResult.packageMsg}`); return failures; } /** * Represents the result of a contract call. * @category Contracts */ var CallResult = class CallResult { result; accessList; revert; constant = false; payable = false; calldata; loadedStorage; estimatedGas; refundedGas; properties = {}; estimatedSatGas = 0n; estimatedRefundedGasInSat = 0n; events = []; to; address; fromAddress; csvAddress; #bitcoinFees; #rawEvents; #provider; #resultBase64; constructor(callResult, provider) { this.#provider = provider; this.#rawEvents = this.parseEvents(callResult.events); this.accessList = callResult.accessList; this.loadedStorage = callResult.loadedStorage; if (callResult.estimatedGas) this.estimatedGas = BigInt(callResult.estimatedGas); if (callResult.specialGas) this.refundedGas = BigInt(callResult.specialGas); const revert = typeof callResult.revert === "string" ? this.base64ToUint8Array(callResult.revert) : callResult.revert; if (revert) this.revert = CallResult.decodeRevertData(revert); if (typeof callResult.result === "string") { this.#resultBase64 = callResult.result; this.result = new BinaryReader(this.base64ToUint8Array(callResult.result)); } else if (callResult.result instanceof Uint8Array) { this.#resultBase64 = ""; this.result = new BinaryReader(callResult.result); } else { this.#resultBase64 = ""; this.result = callResult.result; } } get rawEvents() { return this.#rawEvents; } static decodeRevertData(revertDataBytes) { return decodeRevertData(revertDataBytes); } /** * Reconstructs a CallResult from offline serialized buffer. * Use this on a device to sign transactions offline. * @param {Uint8Array | string} input - The serialized offline data as Uint8Array or hex string. * @returns {CallResult} A CallResult instance ready for offline signing. * * @example * ```typescript * // Offline device: reconstruct from buffer * const buffer = fs.readFileSync('offline-tx.bin'); * const simulation = CallResult.fromOfflineBuffer(buffer); * * // Now sign offline * const signedTx = await simulation.signTransaction({ * signer: wallet.keypair, * // ... other params * }); * ``` */ static fromOfflineBuffer(input) { const buffer = typeof input === "string" ? fromHex(input) : input; const data = CallResultSerializer.deserialize(buffer); const network = CallResult.resolveNetwork(data.network); const challengeSolution = new ChallengeSolution({ ...data.challenge, legacyPublicKey: "0x" + toHex(data.challengeOriginalPublicKey) }); const offlineProvider = { network, utxoManager: { getUTXOsForAmount: () => Promise.resolve(data.utxos), spentUTXO: () => {}, clean: () => {} }, getChallenge: () => Promise.resolve(challengeSolution), sendRawTransaction: () => { return Promise.reject(/* @__PURE__ */ new Error("Cannot broadcast from offline CallResult. Export signed transaction and broadcast online.")); }, sendRawTransactionPackage: () => { return Promise.reject(/* @__PURE__ */ new Error("Cannot broadcast from offline CallResult. Export signed transaction and broadcast online.")); }, getCSV1ForAddress: () => { if (!data.csvAddress) throw new Error("CSV address not available in offline data"); return data.csvAddress; } }; const callResult = new CallResult({ result: data.result, accessList: data.accessList, events: {}, revert: void 0, estimatedGas: data.estimatedGas?.toString(), specialGas: data.refundedGas?.toString() }, offlineProvider); callResult.revert = data.revert; callResult.calldata = data.calldata; callResult.to = data.to; callResult.address = Address.fromString(data.contractAddress); callResult.estimatedSatGas = data.estimatedSatGas; callResult.estimatedRefundedGasInSat = data.estimatedRefundedGasInSat; callResult.csvAddress = data.csvAddress; if (data.bitcoinFees) callResult.setBitcoinFee(data.bitcoinFees); return callResult; } /** * Resolves a NetworkName enum to a Network object. */ static resolveNetwork(networkName) { switch (networkName) { case NetworkName.Mainnet: return bitcoin; case NetworkName.Testnet: return testnet; case NetworkName.OpnetTestnet: return opnetTestnet; case NetworkName.Regtest: return regtest; default: return regtest; } } setTo(to, address) { this.to = to; this.address = address; } setFromAddress(from) { this.fromAddress = from; this.csvAddress = this.fromAddress && this.fromAddress.originalPublicKey ? this.#provider.getCSV1ForAddress(this.fromAddress) : void 0; } /** * Signs a bitcoin interaction transaction from a simulated contract call without broadcasting. * @param {TransactionParameters} interactionParams - The parameters for the transaction. * @param {bigint} amountAddition - Additional satoshis to request when acquiring UTXOs. * @returns {Promise<SignedInteractionTransactionReceipt>} The signed transaction data and UTXO tracking info. */ async signTransaction(interactionParams, amountAddition = 0n) { if (!this.address) throw new Error("Contract address not set"); if (!this.calldata) throw new Error("Calldata not set"); if (!this.to) throw new Error("To address not set"); if (this.revert) throw new Error(`Can not send transaction! Simulation reverted: ${this.revert}`); if (this.constant) throw new Error("Cannot send a transaction on a constant (view) function. Use the returned CallResult directly."); if (this.payable) { const hasExtraInputs = interactionParams.extraInputs && interactionParams.extraInputs.length > 0; const hasExtraOutputs = interactionParams.extraOutputs && interactionParams.extraOutputs.length > 0; if (!hasExtraInputs && !hasExtraOutputs) throw new Error("Payable function requires extraInputs or extraOutputs in the transaction parameters."); } let UTXOs = interactionParams.utxos || await this.acquire(interactionParams, amountAddition); if (interactionParams.extraInputs) UTXOs = UTXOs.filter((utxo) => { return interactionParams.extraInputs?.find((input) => { return input.outputIndex === utxo.outputIndex && input.transactionId === utxo.transactionId; }) === void 0; }); if (!UTXOs || UTXOs.length === 0) throw new Error("No UTXOs found"); const priorityFee = interactionParams.priorityFee || 0n; const challenge = interactionParams.challenge || await this.#provider.getChallenge(); const sharedParams = { contract: this.address.toHex(), calldata: this.calldata, priorityFee, gasSatFee: this.bigintMax(this.estimatedSatGas, interactionParams.minGas || 0n), feeRate: interactionParams.feeRate || this.#bitcoinFees?.conservative || 10, from: interactionParams.refundTo, utxos: UTXOs, to: this.to, network: interactionParams.network, optionalInputs: interactionParams.extraInputs || [], optionalOutputs: interactionParams.extraOutputs || [], note: interactionParams.note, anchor: interactionParams.anchor || false, txVersion: interactionParams.txVersion || 2, linkMLDSAPublicKeyToAddress: interactionParams.linkMLDSAPublicKeyToAddress ?? true, revealMLDSAPublicKey: interactionParams.revealMLDSAPublicKey ?? false, subtractExtraUTXOFromAmountRequired: interactionParams.subtractExtraUTXOFromAmountRequired ?? false }; const params = interactionParams.signer !== null ? { ...sharedParams, signer: interactionParams.signer, challenge, mldsaSigner: interactionParams.mldsaSigner } : sharedParams; const transaction = await factory.signInteraction(params); const csvUTXOs = UTXOs.filter((u) => u.isCSV === true); const p2wdaUTXOs = UTXOs.filter((u) => u.witnessScript && u.isCSV !== true); const regularUTXOs = UTXOs.filter((u) => !u.witnessScript && u.isCSV !== true); const refundAddress = interactionParams.sender || interactionParams.refundTo; const p2wdaAddress = interactionParams.from?.p2wda(this.#provider.network); let refundToAddress; if (this.csvAddress && refundAddress === this.csvAddress.address) refundToAddress = this.csvAddress.address; else if (p2wdaAddress && refundAddress === p2wdaAddress.address) refundToAddress = p2wdaAddress.address; else refundToAddress = refundAddress; const utxoTracking = { csvUTXOs, p2wdaUTXOs, regularUTXOs, refundAddress, refundToAddress, csvAddress: this.csvAddress, p2wdaAddress: p2wdaAddress ? { address: p2wdaAddress.address, witnessScript: p2wdaAddress.witnessScript } : void 0, isP2WDA: interactionParams.p2wda || false }; return { fundingTransactionRaw: transaction.fundingTransaction, interactionTransactionRaw: transaction.interactionTransaction, nextUTXOs: transaction.nextUTXOs, estimatedFees: transaction.estimatedFees, challengeSolution: transaction.challenge, interactionAddress: transaction.interactionAddress, fundingUTXOs: transaction.fundingUTXOs, fundingInputUtxos: transaction.fundingInputUtxos, compiledTargetScript: transaction.compiledTargetScript, utxoTracking }; } /** * Broadcasts a pre-signed interaction transaction. * Uses sendRawTransactionPackage for atomic broadcast when a funding tx is present, * falls back to sendRawTransaction for P2WDA (interaction-only) transactions. * @param {SignedInteractionTransactionReceipt} signedTx - The signed transaction data. * @returns {Promise<InteractionTransactionReceipt>} The transaction receipt with broadcast results. */ async sendPresignedTransaction(signedTx) { if (signedTx.utxoTracking.isP2WDA || !signedTx.fundingTransactionRaw) { const tx = await this.#provider.sendRawTransaction(signedTx.interactionTransactionRaw, false); if (!tx || tx.error) throw new Error(`Error sending transaction: ${tx?.error || "Unknown error"}`); if (!tx.result) throw new Error("No transaction ID returned"); if (!tx.success) throw new Error(`Error sending transaction: ${tx.result || "Unknown error"}`); this.#processUTXOTracking(signedTx); return { interactionAddress: signedTx.interactionAddress, transactionId: tx.result, peerAcknowledgements: tx.peers || 0, newUTXOs: signedTx.nextUTXOs, estimatedFees: signedTx.estimatedFees, challengeSolution: signedTx.challengeSolution, rawTransaction: signedTx.interactionTransactionRaw, fundingUTXOs: signedTx.fundingUTXOs, fundingInputUtxos: signedTx.fundingInputUtxos, compiledTargetScript: signedTx.compiledTargetScript }; } const result = await this.#provider.sendRawTransactionPackage([signedTx.fundingTransactionRaw, signedTx.interactionTransactionRaw], true); if (!result.success) throw new Error(`Error sending transaction package: ${result.error || "Unknown error"}`); if (result.packageResult) { const failures = extractPackageFailures(result.packageResult); if (failures.length > 0) throw new Error(`Transaction package failed:\n${failures.join("\n")}`); } const interactionSeqResult = result.sequentialResults?.[1]; if (interactionSeqResult && !interactionSeqResult.success) throw new Error(`Interaction transaction failed: ${interactionSeqResult.error || "Unknown error"}`); const interactionTxId = interactionSeqResult?.txid || signedTx.interactionTransactionRaw; const peers = interactionSeqResult?.peers || 0; this.#processUTXOTracking(signedTx); return { interactionAddress: signedTx.interactionAddress, transactionId: interactionTxId, peerAcknowledgements: peers, newUTXOs: signedTx.nextUTXOs, estimatedFees: signedTx.estimatedFees, challengeSolution: signedTx.challengeSolution, rawTransaction: signedTx.interactionTransactionRaw, fundingUTXOs: signedTx.fundingUTXOs, fundingInputUtxos: signedTx.fundingInputUtxos, compiledTargetScript: signedTx.compiledTargetScript }; } /** * Signs and broadcasts a bitcoin interaction transaction from a simulated contract call. * @param {TransactionParameters} interactionParams - The parameters for the transaction. * @param {bigint} amountAddition - Additional satoshis to request when acquiring UTXOs. * @returns {Promise<InteractionTransactionReceipt>} The transaction receipt with broadcast results. */ async sendTransaction(interactionParams, amountAddition = 0n) { try { const signedTx = await this.signTransaction(interactionParams, amountAddition); return await this.sendPresignedTransaction(signedTx); } catch (e) { if (e.message.includes("Insufficient funds to pay the fees") && amountAddition === 0n) return await this.sendTransaction(interactionParams, 200000n); this.#provider.utxoManager.clean(); throw e; } } /** * Set the gas estimation values. * @param {bigint} estimatedGas - The estimated gas in satoshis. * @param {bigint} refundedGas - The refunded gas in satoshis. */ setGasEstimation(estimatedGas, refundedGas) { this.estimatedSatGas = estimatedGas; this.estimatedRefundedGasInSat = refundedGas; } /** * Set the Bitcoin fee rates. * @param {BitcoinFees} fees - The Bitcoin fee rates. */ setBitcoinFee(fees) { this.#bitcoinFees = fees; } /** * Set the decoded contract output properties. * @param {DecodedOutput} decoded - The decoded output. */ setDecoded(decoded) { this.properties = Object.freeze(decoded.obj); } /** * Set the contract events. * @param {U} events - The contract events. */ setEvents(events) { this.events = events; } /** * Set the calldata for the transaction. * @param {Uint8Array} calldata - The calldata. */ setCalldata(calldata) { this.calldata = calldata; } /** * Serializes this CallResult to a Uint8Array. * Call this on an online device after simulation, then transfer the result * to an offline device for signing. * * @param {string} refundAddress - The address to fetch UTXOs from (your p2tr address). * @param {bigint} amount - The amount of satoshis needed for the transaction. * @returns {Promise<Uint8Array>} Serialized buffer ready for offline signing. * * @example * ```typescript * // Online device: prepare for offline signing * const simulation = await contract.transfer(recipient, amount); * const offlineBuffer = await simulation.toOfflineBuffer(wallet.p2tr, 50000n); * * // Save to file or encode as base64 for QR code * fs.writeFileSync('offline-tx.bin', offlineBuffer); * // Or: const qrData = offlineBuffer.toString('base64'); * ``` */ async toOfflineBuffer(refundAddress, amount) { if (!this.calldata) throw new Error("Calldata not set"); if (!this.to) throw new Error("Contract address not set"); if (!this.address) throw new Error("Contract Address object not set"); if (this.revert) throw new Error(`Cannot serialize reverted simulation: ${this.revert}`); const utxos = await this.#provider.utxoManager.getUTXOsForAmount({ address: refundAddress, amount: amount + this.estimatedSatGas + 10000n, throwErrors: true }); const challengeSolution = await this.#provider.getChallenge(); const networkName = this.#getNetworkName(); return CallResultSerializer.serialize({ calldata: this.calldata, to: this.to, contractAddress: this.address.toHex(), estimatedSatGas: this.estimatedSatGas, estimatedRefundedGasInSat: this.estimatedRefundedGasInSat, revert: this.revert, result: fromBase64(this.#resultBase64), accessList: this.accessList, bitcoinFees: this.#bitcoinFees, network: networkName, estimatedGas: this.estimatedGas, refundedGas: this.refundedGas, challenge: challengeSolution.toRaw(), challengeOriginalPublicKey: challengeSolution.publicKey.originalPublicKeyBuffer(), utxos, csvAddress: this.csvAddress }); } /** * Gets the NetworkName enum from the provider's network. * @returns {NetworkName} The network name enum value. */ #getNetworkName() { const network = this.#provider.network; if (network === bitcoin) return NetworkName.Mainnet; if (network === testnet) return NetworkName.Testnet; if (network === opnetTestnet) return NetworkName.OpnetTestnet; if (network === regtest) return NetworkName.Regtest; return NetworkName.Regtest; } /** * Clone a UTXO and attach a witness script. * @param {UTXO} utxo - The UTXO to clone. * @param {Uint8Array} witnessScript - The witness script to attach. * @returns {UTXO} The cloned UTXO with witness script. */ #cloneUTXOWithWitnessScript(utxo, witnessScript) { const clone = Object.assign(Object.create(Object.getPrototypeOf(utxo)), utxo); clone.witnessScript = witnessScript; return clone; } /** * Process UTXO tracking after transaction broadcast. * @param {SignedInteractionTransactionReceipt} signedTx - The signed transaction receipt. */ #processUTXOTracking(signedTx) { const { csvUTXOs, p2wdaUTXOs, regularUTXOs, refundAddress, refundToAddress, csvAddress, p2wdaAddress } = signedTx.utxoTracking; if (csvAddress && csvUTXOs.length) { const finalUTXOs = signedTx.nextUTXOs.map((u) => this.#cloneUTXOWithWitnessScript(u, csvAddress.witnessScript)); this.#provider.utxoManager.spentUTXO(csvAddress.address, csvUTXOs, refundToAddress === csvAddress.address ? finalUTXOs : []); } if (p2wdaAddress && p2wdaUTXOs.length) { const finalUTXOs = signedTx.nextUTXOs.map((u) => this.#cloneUTXOWithWitnessScript(u, p2wdaAddress.witnessScript)); this.#provider.utxoManager.spentUTXO(p2wdaAddress.address, p2wdaUTXOs, refundToAddress === p2wdaAddress.address ? finalUTXOs : []); } if (regularUTXOs.length) this.#provider.utxoManager.spentUTXO(refundAddress, regularUTXOs, refundToAddress === refundAddress ? signedTx.nextUTXOs : []); if (csvAddress && refundToAddress === csvAddress.address && !csvUTXOs.length) { const finalUTXOs = signedTx.nextUTXOs.map((u) => this.#cloneUTXOWithWitnessScript(u, csvAddress.witnessScript)); this.#provider.utxoManager.spentUTXO(csvAddress.address, [], finalUTXOs); } else if (p2wdaAddress && refundToAddress === p2wdaAddress.address && !p2wdaUTXOs.length) { const finalUTXOs = signedTx.nextUTXOs.map((u) => this.#cloneUTXOWithWitnessScript(u, p2wdaAddress.witnessScript)); this.#provider.utxoManager.spentUTXO(p2wdaAddress.address, [], finalUTXOs); } else if (refundToAddress === refundAddress && !regularUTXOs.length) { if (!(csvAddress && refundToAddress === csvAddress.address || p2wdaAddress && refundToAddress === p2wdaAddress.address)) this.#provider.utxoManager.spentUTXO(refundAddress, [], signedTx.nextUTXOs); } } max(a, b) { return a > b ? a : b; } ensureUTXOsAvailable(utxos) { if (!utxos || utxos.length === 0) throw new Error("Wallet optimization required. No UTXOs available. You may need to split your wallet UTXOs so at least one non-extra-input UTXO is available for the funding transaction."); } computeRequiredAmount(gasFee, priority, amountAddition, totalOuts, extraInputValue, miningCost = 0n, maximumAllowedSatToSpend = 0n) { const gross = this.max(gasFee + priority + amountAddition + totalOuts + miningCost, maximumAllowedSatToSpend); return gross >