buildLockProof
Turns indexer responses for a confirmed lockup funding transaction into the BondL1LockupOutput that register-for-bond takes for one L1 lockup output. Pure computation: the caller fetches the inputs, this makes no network call.
Usage
import { buildLockProof } from '@stacks/bitcoin-staking';
import type { RegisterMetadata } from '@stacks/bitcoin-staking';
declare const esplora: string; // base URL of an Esplora-compatible API
declare const txid: string; // funding transaction ID
declare const blockHash: string; // block that confirmed it
declare const meta: RegisterMetadata; // from buildRegisterMetadata
const get = (path: string) => fetch(`${esplora}${path}`);
const output = buildLockProof({
txHex: await (await get(`/tx/${txid}/hex`)).text(),
header: await (await get(`/block/${blockHash}/header`)).text(),
merkleProof: await (await get(`/tx/${txid}/merkle-proof`)).json(),
txCount: (await (await get(`/block/${blockHash}`)).json()).tx_count,
unlockHeight: meta.unlockHeight,
lockScript: meta.lockScript,
});
// pass to buildRegisterForBond as lockup: { kind: 'btc', outputs: [output], unlockBytes: meta.unlockBytes }Notes
The witness is stripped from
txHex, so the storedtxis the legacy serialization that hashes to the txid. Segwit serialization fromGET /tx/:txid/hexis accepted.Each
merkleProof.merklehash is reversed from display order to the internal little-endian order pox-5 folds over.The lockup output is found by comparing each output's
scriptPubKeywith the expected P2WSH script (see ExpectedScriptInput), the same equalityregister-for-bondasserts.amountis read from that output, in sats. It throws if no output matches, if several match andoutputIndexis not set, or ifoutputIndexnames an output that does not match.Before returning, it checks the proof:
headeris 80 bytes,posis withintxCount, the branch has exactlyceil(log2(txCount))siblings of 32 bytes each and at most 14, and the branch folds from the txid to the header's merkle root. It also throws if the legacy transaction is over 100,000 bytes, the size pox-5 accepts.It does not check the following, which
register-for-bondchecks on chain invalidate-l1-lockup(L2031-L2113):unlockHeightbelow the bond's minimum fails withERR_INVALID_UNLOCK_HEIGHT (u52).unlockHeightdifferent from the height the script commits to fails withERR_INVALID_LOCKUP_SCRIPT (u42)because pox-5 rebuilds the expected script from it.A header that is not the Bitcoin block at
merkleProof.block_heightfails withERR_INVALID_BTC_HEADER (u40).
register-for-bondtakes up to 10 lockup outputs. When one transaction pays the lockup script more than once, call this once per output withoutputIndexset. pox-5 rejects the same outpoint twice withERR_DUPLICATE_LOCKUP_OUTPOINT (u46).Without an Esplora
/merkle-proofendpoint, use buildLockProofFromBlock.
Signature
Returns
BondL1LockupOutput
A BondL1LockupOutput with tx, header and leafHashes as bytes, amount as a bigint in sats, and unlockBurnHeight set from unlockHeight.
Parameters
input.txHex (required)
Type:
string
The raw funding transaction as hex, from GET /tx/:txid/hex. Segwit serialization is accepted.
input.header (required)
Type:
Uint8Array | string
The 80-byte header of the block that confirmed the transaction, as bytes or hex, from GET /block/:hash/header.
input.merkleProof (required)
Type:
EsploraMerkleProof
The indexer's merkle proof, from GET /tx/:txid/merkle-proof. See EsploraMerkleProof.
input.txCount (required)
Type:
number
The number of transactions in the block, the tx_count field of GET /block/:hash.
input.unlockHeight (required)
Type:
IntegerType
The unlock height the lockup script commits to, the same value used to build it.
input.outputIndex (optional)
Type:
number
Which output to prove when the transaction pays the lockup script more than once. Omit when exactly one output matches.
input.outputScript or input.lockScript (required, exactly one)
Type:
Uint8Array | string
The P2WSH scriptPubKey or the witness script of the lockup. See ExpectedScriptInput.
Was this helpful?