.md to its URL, for example docs.writz.xyz/developers/for-ai-agents.md.
Prompt to give your agent:
Facts
contracts/deployments/testnet.md, machine-readable in contracts/deployments/testnet.json. The contracts are immutable; a new version gets a new ID there.
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. Any other bitcoin-spv or writz-lend ID is superseded; do not use it. How the storage windows work: Contract reference and Storage and TTL management.
Packages
npm install @writz/sdk and cargo add writz-spv-client both work. The surface:
@writz/sdk:createWritzClient()(testnet preset),writz.verifyBitcoinTx(txid),writz.relayer.getSpvProof(txid)(throwsRelayerErrorwithcode,status,retryAfterSeconds; retries 409light_client_behind, 429 and 503 afterRetry-After),writz.spv.verifyTransaction(sorobanArgs)(throwsContractErrorwithcode,errorName),writz.lend.getProtocolState(),writz.lend.getSupplyRateBp(),writz.lend.getSupplyBalance(address),writz.lend.buildSupplyUsdc(address, stroops)andwritz.lend.buildWithdrawSupply(address, stroops)(unsigned XDR, the SDK never signs).writz-spv-client:BitcoinSpvClient,SpvVerificationResult,SPVError,find_output_value(&raw_tx, &script_pubkey) -> Option<u64>,txid(&env, &raw_tx).
Does not exist (do not generate code for it)
- Writz publishes exactly two packages:
@writz/sdkon npm andwritz-spv-clienton crates.io. There is nowritz-sdkand no other@writz/...package. verify_transactionhas noheadersargument and its result has nooutputsfield.- No Bitcoin mainnet, testnet3 or testnet4 verification. Signet only.
- No Writz contract on Stellar mainnet.
- No relayer integration route other than those listed below (the rest serve the Writz app); no API keys; no webhooks.
- External contracts cannot call
bitcoin-spv.consume_deposit(admin-registered consumers only).
bitcoin-spv interface
block_hash and every merkle_proof item in internal byte order (reverse of explorers); raw_tx without witness data, never exactly 64 bytes; min_confirmations >= 1. Inclusion only: to check amount and recipient, parse raw_tx. Store accepted txids yourself (replay protection).
Errors (Error(Contract, #N)): 2 InsufficientConfirmations, 3 ZeroMinConfirmations, 5 MerkleProofInvalid, 7 EmptyTransaction, 15 AmbiguousTransactionLength, 22 HeaderNotFound, 23 NotOnBestChain, 24 BlockNotAfterCheckpoint.
writz-lend reads and the lender side
get_supply_rate_bp is variable and reads 0 while nothing, or very little, is borrowed, so read it live and never present a fixed APY. get_supply_balance includes accrued interest. withdraw_supply fails with #13 InsufficientSupply above the lender’s balance and #11 InsufficientLiquidity above what is not lent out; supply_usdc fails with #17 Paused while the pool is paused, and the supplier needs a trustline to the test USDC (without one, simulation fails inside the token contract). Generate a typed client from the deployed ID for anything else; the full list is in Contract Reference.
Relayer
/spv-proof 200 body: { txid, rawTxNoWitness, blockHash, txIndex, merkleProof[], headers[], blockHeight, confirmations, sorobanArgs: { block_hash, merkle_proof[], tx_index, raw_tx, min_confirmations } }. Hex strings. sorobanArgs is already in internal byte order and maps one to one onto verify_transaction. Errors: 400 invalid_txid or invalid_confirmations, 404 unconfirmed, 404 tx_not_found (txid not on signet), 409 insufficient_confirmations (requested, available), 409 light_client_behind (block not yet in bitcoin-spv; calling the contract now fails with #22 or #2), 429 rate_limited (60 per minute per IP), 502 upstream_error, 503 upstream_unavailable. On 409, 429 and 503, wait retryAfterSeconds (body) or Retry-After (header) before retrying; never retry in a tight loop. CORS is open.
Signet coins for an integration come from a public Signet faucet (for example signetfaucet.com or bitcoinsignetfaucet.com); claim them by hand and never automate around a faucet’s captcha. The relayer’s /faucet/signet route is not for agents or builders: it only funds accounts created through the Writz testnet app’s social login, needs that login’s session token, and refuses every other request.
/timeline lists one event per transaction of a writz-lend position, in chain order: btc_lock and released on bitcoin, verified, borrowed, received and repaid on stellar, and a received mint on base-sepolia. at is Unix ms or null; url is the mempool.space, stellar.expert or Basescan page. A received event (a swap to a stablecoin or XLM, or a CCTP burn) is linked to the loan by a rule, not by the chain: it belongs to the owner’s most recent borrow before it, with no borrow or repay by the owner in between. complete: false with gaps means a source could not be read in full; the events listed are still real. Errors: 400 invalid_txid, 404 position_not_found (use /deposit-status before the deposit is verified), 429 rate_limited (60 per minute per IP), 503 stellar_unavailable or lending_not_configured.
/cctp/relay completes a Stellar to Base Sepolia CCTP V2 transfer so the recipient needs no wallet or gas on Base Sepolia: call POST once with the Stellar hash of the deposit_for_burn, then poll GET /cctp/relay/:burnHash every few seconds until kind is arrived (mintHash is the Base Sepolia transaction, null only while it is still being looked up) or failed (reason). Transient failures (an RPC outage, an empty gas account, the daily cap, Circle slow to confirm) are retried by the relay itself: the status then stays confirming or confirmed with retrying: true, reason and nextRetryAt; failed is final (a refused or invalid message, or 24 hours of retrying). Asking again never mints twice. Only burns from Stellar (domain 27) to Base Sepolia (domain 6) through Circle’s TokenMessengerV2, with no destination caller and no hook, are relayed. Errors: 400 invalid_burn_hash, 404 not_found (on GET, before any POST), 429 rate_limited (30 per IP per 10 minutes), 503 cctp_relay_disabled or relay_busy.
The other routes the index lists (/merkle-path, /notes, /insert-commitment, /update-leaf, /metrics/*) serve the Writz app and are not an integration surface. Yield on USDC is the writz-lend lender side, called on the contract directly (above), not through the relayer.
Verified examples
CLI (simulation; the source account is any valid G address and is not charged):@stellar/stellar-sdk 16.x, read-only):
examples/verify-bitcoin-tx.
Rust, cross-contract (soroban-sdk 26):
examples/spv-payment-check.
Checklist before you answer “it works”
get_best_tipreturns a height near the signet tip on mempool.space.- The txid is a signet transaction with enough confirmations, in a block above 327,925.
- You passed
sorobanArgsthrough unchanged (no extra byte reversal). - You simulated reads (
--send=no,simulateTransaction) and only signed real writes. - Your contract checks the output (amount and scriptPubKey) and stores the txid.