main. Writz v1 is bitcoin-spv and writz-lend; zk-verifier and commitment-tree belong to the separate privacy edition (in progress), are deployed on testnet only, and are never called by writz-lend.
Every function below is copied from the contract source, with the first
paragraph of its doc comment; env: Env is omitted. The source is the
authority: contracts/contracts/<name>/src/lib.rs, and the generated
TypeScript bindings in packages/ and frontend/app/src/lib/contracts/.
Every contract is configured by its __constructor, which runs inside the
deploy transaction. There is no separate initialize call. To deploy and
wire up all four, use scripts/deploy/deploy_stack.mjs (see #177 for the
order and arguments).
Testnet addresses:
bitcoin-spv and writz-lend are the instances from the 2026-10-25 redeploy;
zk-verifier and commitment-tree are from the coordinated redeploy (#177,
2026-10-11). All four run the interfaces documented here. See
contracts/deployments/testnet.md.
Testnet bitcoin-spv and writz-lend were redeployed on 2026-10-25 because testnet rent rose sharply: testnet’s live state is above its target size, and the previous contracts gave every entry they created the long mainnet storage windows, so a deposit was charged about 428 XLM in fees. The new contracts take those windows as deployment parameters and run testnet-length ones (a deposit was charged about 89 XLM, measured 2026-10-25). Positions on the previous contracts were closed and their BTC released; their IDs are recorded as superseded in contracts/deployments/testnet.md. The windows are the header_ttl_ledgers and consumed_ttl_ledgers arguments of
bitcoin-spv’s __constructor and the entry_ttl_ledgers argument of
writz-lend’s (both below); what each entry keeps and when it is extended is in
Storage and TTL management.
bitcoin-spv
__constructor
Runs exactly once, atomically, as part of deployment (__constructor): no separate initialize transaction exists for anyone to front-run (GHSA-422m-f73x-fh58).
set_checkpoint
Sets the trust-root checkpoint. Admin-gated and callable exactly once: after this the chain only grows through submit_headers, so the admin cannot later rewrite which chain is trusted.
set_consumer
Registers (or removes) a lending contract allowed to consume deposit txids. Admin-only. Both lending contracts share one txid registry so the same Bitcoin output cannot back loans from both pools (GHSA-2975-ggwh-pxw5).
consume_deposit
Marks a deposit txid as consumed by a lending contract. Fails if the caller is not a registered consumer or the txid was already consumed. Called by each lending contract’s deposit after its own checks.
is_deposit_consumed
Returns whether a deposit txid has already been consumed.
set_submitter
Restricts submit_headers to submitter, or reopens it to everyone with None. Admin-gated.
set_ttls
Sets how long stored headers (with their height index) and consumed deposit markers are kept live, in ledgers. Admin-gated, same bounds as the constructor (17,280 to 3,110,400, else InvalidEntryTtl). Applies to entries written or touched after the change only; emits ttls_set.
set_admin
Rotates the admin address. Admin-gated.
get_checkpoint
Returns the checkpoint, or None if never set.
get_best_tip
Returns the tip of the most-work chain, or None before the checkpoint is set.
get_header
Returns the stored header for block_hash, if any.
get_canonical_hash
Returns the most-work chain’s block hash at height, if known.
refresh_ttl
Extends the TTL of the Config, Checkpoint and best-tip entries. Permissionless - anyone can call this to keep an inactive deployment from expiring.
submit_headers
Adds a run of contiguous block headers to the light client.
verify_transaction
Verify that a Bitcoin transaction is included in a confirmed block of the most-work chain this contract tracks.
zk-verifier
Privacy edition (in progress), testnet only. Not part of Writz v1.
__constructor
Runs exactly once, atomically, as part of deployment (__constructor). The admin is the only account that can call set_verification_key.
set_verification_key
Store or replace the verification key for a circuit.
get_verification_key
Returns the stored verification key for a circuit, or None.
refresh_ttl
Extends the TTL of the admin entry and every set verification key. Permissionless - call on a schedule so an infrequently-used deployment doesn’t silently expire and start failing proof verification with VerificationKeyNotSet.
verify_deposit
Verify a Groth16 proof for the deposit circuit.
verify_borrow_repay
Verify a Groth16 proof for the borrow/repay circuit.
verify_zero_debt
Verify a Groth16 proof for the zero-debt release circuit.
verify_liquidation
Verify a Groth16 proof for the liquidation circuit.
verify_insert
Verify a Groth16 proof for the Merkle insertion circuit.
Events
Topic fields are in bold.commitment-tree
Privacy edition (in progress), testnet only. Not part of Writz v1; writz-lend never calls it.
__constructor
Runs exactly once, atomically, as part of deployment (__constructor) - see GHSA-422m-f73x-fh58.
deposit
Register a BTC deposit with a ZK commitment.
insert_commitment
Insert a pending commitment into the Merkle tree and advance the root.
borrow
Borrow USDC against a BTC position using a ZK proof.
mark_released
Records that a fully repaid position’s BTC has been released, by spending the zero-debt leaf’s nullifier (GHSA-w4rp-v54x-2cv3, GHSA-hcjf-8vjc-2hfv). After this, borrow on that leaf fails its nullifier check.
repay
Repay USDC debt on a ZK position.
liquidate
Liquidate an undercollateralized position using a ZK proof.
supply_usdc
Lender supplies USDC to the pool to earn yield from borrower interest.
withdraw_supply
Lender withdraws USDC from the pool.
set_oracle
Updates the oracle contract address used for BTC/USD pricing. Admin only.
set_spv_contract
Updates the bitcoin-spv contract address used for deposit verification. Admin only. Changing this mid-flight affects only deposits submitted after the change - an in-flight deposit’s SPV proof was already verified against the previous contract by the time this would run.
set_zk_verifier
Updates the zk-verifier contract address used for ZK proof verification. Admin only. Changing this mid-flight affects only proofs submitted after the change.
set_max_total_borrowed
Sets the ceiling on total outstanding borrows, in USDC stroops. Admin only. Lowering it below the current total blocks new borrows without touching existing positions.
set_keeper_pubkey
Rotates the keeper’s BabyJubJub key (#212). Admin only. Positions whose last note went to the old key stay liquidatable by whoever holds it; their next borrow or repay re-encrypts to the new one.
set_borrow_timelock_margin
Sets how many blocks a borrowing position must have left before its timelock (#212). Admin only; 144 (~1 day) up to the deposit window’s own maximum.
set_max_tip_age
Sets how old bitcoin-spv’s tip may be for a borrow to rely on it (#212). Admin only; 1 to 48 hours.
set_paused
Pauses or unpauses new deposits/borrows/USDC supply. Admin only.
refresh_nullifier_ttl
Extend the TTL of a spent-nullifier entry to another 180-day window.
refresh_commitment_ttl
Extend the TTL of the Bitcoin txid → commitment dedup record.
refresh_merkle_root_ttl
Extend the TTL of the on-chain Merkle root to another 180-day window.
refresh_pool_ttl
Extend the TTL of the USDC pool accounting entry.
refresh_supply_balance_ttl
Extend the TTL of a lender’s supply balance entry.
refresh_instance_ttl
Extend the instance storage TTL to another 90-day window.
get_keeper_pubkey
The keeper’s BabyJubJub public key (x, y) every borrow and repay proof must encrypt its keeper note to (#212).
get_borrow_timelock_margin
Blocks a borrowing position must have left before its timelock (#212).
get_merkle_root
Returns the current Poseidon Merkle root of the position commitment tree.
is_nullifier_spent
Returns true if the nullifier has already been spent.
get_commitment
Returns the commitment for a Bitcoin txid, or None if not deposited.
get_next_leaf_index
Returns the index of the next empty Merkle leaf - the leaf_index the next insertion proof must target.
is_commitment_pending
Returns true if a commitment is pending Merkle tree insertion.
get_pool_state
Returns (total_supplied, total_borrowed) in USDC stroops.
get_supply_balance
Returns the USDC supply balance (in stroops) for a lender.
Events
Topic fields are in bold.writz-lend
__constructor
Runs exactly once, atomically, as part of deployment (__constructor): no separate initialize transaction exists for anyone to front-run (GHSA-422m-f73x-fh58).
deposit
Register a BTC deposit by submitting an SPV proof. user_pubkey must differ from the protocol key, else UserKeyIsProtocolKey.
supply_usdc
Lender supplies USDC to the pool, making it available for borrowing. Lenders earn supply_rate_bp() APR on their supplied amount.
withdraw_supply
Lender withdraws previously supplied USDC from the pool.
borrow
Borrow USDC against an existing BTC deposit. Every borrow, including a top-up, requires the position’s timelock height H to be at least borrow_timelock_margin_blocks (4,320 by default) above the bitcoin-spv tip, read from a tip header no older than max_tip_age_secs (6 hours by default); else TimelockTooSoon, or StaleBitcoinTip when the tip is missing or too old.
repay
Repay some or all of the USDC debt on a position.
liquidate
Liquidate an undercollateralized position. Accepted only while H is at least LIQUIDATION_CUTOFF_BLOCKS (288) above the bitcoin-spv tip, read from a fresh tip as for borrow; else LiquidationWindowClosed, or StaleBitcoinTip.
set_max_total_borrowed
Update the keeper address. Admin only.
set_keeper
keeper_heartbeat
Explicit liveness signal from the designated keeper.
set_keeper_stale_window
Updates how many seconds of keeper inactivity before liquidation opens to any caller. Admin only.
set_borrow_timelock_margin
Sets how many Bitcoin blocks a borrow must leave between the bitcoin-spv tip and the position’s timelock height. Admin only; 4,320 to 105,000, else InvalidTimelockParams: the margin can be raised, never lowered below the default.
set_max_tip_age
Sets how old the bitcoin-spv tip header may be, in seconds, for borrow and liquidate to rely on it. Admin only; 1 to 48 hours, else InvalidTimelockParams.
set_relayer
Updates the relayer address authorized to call publish_release_psbt. Admin only.
set_oracle
Updates the oracle contract address used for BTC/USD pricing. Admin only.
set_entry_ttl
Sets how long per-user entries (positions, supply balances, release PSBTs) are kept live, in ledgers. Admin only, same bounds as the constructor’s entry_ttl_ledgers (17,280 to 3,110,400, else InvalidEntryTtl). Applies to entries written or touched after the change only; emits entry_ttl_set.
set_spv_contract
Updates the bitcoin-spv contract address used for deposit verification. Admin only. Changing this mid-flight affects only deposits submitted after the change - an in-flight deposit’s SPV proof was already verified against the previous contract by the time this would run.
set_paused
Pauses or unpauses new deposits/borrows/USDC supply. Admin only.
publish_release_psbt
Publishes a co-signed Path A release PSBT for a repaid position.
get_release_psbt
Returns the relayer-published release PSBT for a position, or None if the relayer hasn’t published one yet (or the position was never fully repaid).
get_position
Returns the position for the given Bitcoin txid, or None.
get_health_ratio_bp
Returns the health ratio (in basis points) for a position.
get_borrow_timelock_margin
Blocks a borrow must leave between the bitcoin-spv tip and the position’s timelock height.
get_max_tip_age
Oldest the bitcoin-spv tip header may be, in seconds, for borrow and liquidate to rely on it.
get_borrow_rate_bp
Current annual borrow rate in basis points (e.g. 800 = 8%).
get_supply_rate_bp
Current annual supply rate in basis points.
get_protocol_state
Returns a snapshot of the global protocol state.
get_supply_balance
Returns the USDC supply balance (in stroops) for a lender.
refresh_position_ttl
Extend the TTL of a position entry to the full entry_ttl_ledgers window (180 days on mainnet).
refresh_supply_balance_ttl
Extend the TTL of a lender’s supply balance entry to the full entry_ttl_ledgers window.