Overview
- token
LEAF— a CRC-20 token on Bitcoin mainnet- decimals
- 8 · amounts are integers in atoms · 1 LEAF = 100000000 atoms
- supply
- 1000000000 LEAF, fully minted
- model
- accounts · a balance belongs to a Bitcoin address; tokens are not attached to UTXOs
- transfer
- a Bitcoin transaction:
vin[0]spends a UTXO of the sender, one OP_RETURN carries the amount, the next output is the receiver - finality
- confirmed transactions only · reorganisations undo and replay blocks
- also
AXBT, a BTC-backed token of the same ledger: read it with the same API (see AXBT)
API
Base URL https://crc20.org/api/v1 · read-only JSON · no key · CORS enabled · full spec openapi.json. Amounts are decimal strings (LEAF in atoms, BTC in satoshis): parse them with BigInt, never as JSON numbers. Only market.unit_price_sat has decimals ("1.5"). Lists return next_cursor; pass it back as ?cursor= until it is null.
Balance
/api/v1/addresses/{address}LEAF balance (confirmed, at height), AXBT balance and confirmed BTC balance in satoshis (from mempool.space, cached 30 s; btc is null when it is unavailable, which never means zero) of an address. An address with no activity answers a balance of "0", not a 404.
address- a mainnet address:
bc1q…orbc1p…(either case, answered in lower case),1…,3…
$ curl https://crc20.org/api/v1/addresses/bc1pnjm803d0erzgvvmx6ue99gunsht8vpl3kxa3k8d00pke32argr6syrvu6uHistory
/api/v1/addresses/{address}/historyLEAF operations of an address, newest first, with direction = in · out · self. Use it to detect deposits.
address- a mainnet address:
bc1q…orbc1p…(either case, answered in lower case),1…,3… limit- 1 to 100, default 50
cursor- the
next_cursorof the previous page, unchanged tickLEAF(default),AXBTorall, case-insensitive (see AXBT)
$ curl "https://crc20.org/api/v1/addresses/bc1pnjm803d0erzgvvmx6ue99gunsht8vpl3kxa3k8d00pke32argr6syrvu6u/history?limit=1"typetransfer,mint,swap(AXBT/LEAF pool:inwhen the address bought LEAF,outwhen it sold) orliquidity(a deposit into the pool,out), and the AXBT vault types with?tick=AXBT· credit onlydirection=intemplatetransfer(ordinary),market(marketplace sale, see market sales),mint,swap,liquidity,vault, ornulluntil the transaction is decoded (retry later)market- for a sale:
price_sat,platform_fee_sat,network_fee_sat,buyer_paid_sat,unit_price_sat· otherwisenull sender_balance_before- the sender's LEAF balance just before the transfer ·
nullfor every other type swap- see pool
liquidity- see pool
vault- see AXBT
block_hash- compare with your own node to detect reorganisations
Transaction
/api/v1/transactions/{txid}The accepted LEAF operations of a transaction, in operations (same fields as a history item, without direction). 404 means it moved no LEAF: unknown, unconfirmed, or confirmed but invalid. Use it to confirm a withdrawal. While the index may be behind, the lookup answers 503 instead of a 404: retry.
txid- 64 hexadecimal characters, either case
tickLEAF(default),AXBTorall, case-insensitive (see AXBT)
$ curl https://crc20.org/api/v1/transactions/1b356577165847e2e3b1cfe285a8d05f1803f0b4125584cc0914e97801089641Status
/api/v1/statusIndexer position: wait for synced = true and height ≥ your transaction's block before trusting a 404.
$ curl https://crc20.org/api/v1/statusAll operations
/api/v1/historyEvery operation, newest first, as { items, next_cursor } with the history items above (without direction). Pages never shift: to follow new activity, read the first page again until you reach an item you already have (see limits).
limit- 1 to 100, default 50
cursor- the
next_cursorof the previous page, unchanged tickLEAF(default),AXBTorall, case-insensitive (see AXBT)
$ curl "https://crc20.org/api/v1/history?tick=all&limit=100"Decoded transaction
/api/v1/transactions/{txid}/detailsAny transaction, decoded: status (accepted or not_leaf), template, the operations, and every input and output with its role (sender, payload, receiver, change, …), sighash and value; market holds the economics of a sale and notes explains anomalies (a transfer that confirmed but was not applied, a block newer than the index). Unknown txid → 404.
txid- 64 hexadecimal characters, either case
tickLEAF(default),AXBTorall, case-insensitive (see AXBT)
$ curl "https://crc20.org/api/v1/transactions/737e9ef7bde669d926976381c15c9592c5bdeabd3754027bdfa2ed7727616971/details?tick=all"Errors
Every error is { "error": { "code", "message" } }; a bad parameter is a 400 even while the indexer is down.
400invalid_address,invalid_txid,invalid_cursor,invalid_limit,invalid_query(unknown, duplicate or malformed parameter)404not_found: unknown route or token, or no accepted operation in the transaction405method_not_allowed: onlyGET,HEADandOPTIONS503unavailable: indexer unreachable or catching up, or load shedding (Retry-After: 1): retry later
AXBT
AXBT is the BTC-backed token of the PRECOP ledger: it is minted against BTC collateral locked in a vault and burned when the vault is repaid and closed. 8 decimals, 1 atom = 1 sat; no maximum supply. The LEAF routes stay unchanged: AXBT is read with one more parameter.
- balance
/addresses/{address}returnsaxbt.balance(atoms = sats), last field- operations
- add
?tick=AXBTto/history,/addresses/{address}/history,/transactions/{txid}and/details: AXBT transfers, vault operations, and the pool swaps and liquidity (which move AXBT too).?tick=alllists both tokens and the liquidity paybacks tick- every operation ends with
tick, the token of itsamount:LEAForAXBT - identity
- with
tick=all, a payback reuses its deposit's txid and one transaction can touch two vaults: identify an item byblock_height,txid,type,liquidity.actionandvault.funding_txid, not by txid alone - transfer
- the LEAF layout (see withdrawals) with
"tick":"AXBT"in the payload,amtin sats
Vault operations
| type | transaction | AXBT |
|---|---|---|
| vault_open | the vault funding: BTC collateral locked | amount = the debt, credited to to (the owner) |
| vault_repay | the repayment (route owner_repay or operator_liquidation) | amount = the debt, from from (the payer) to to (the vault's settlement account) |
| vault_close | the close: collateral released | amount = the settled debt, burned from from; "0" and from null when nothing was settled |
| vault_liquidation | the operator's recovery of an expired, unpaid vault | nothing moves: amount = the debt left unbacked |
These items carry vault: funding_txid, owner, collateral_sats, debt, status, liquidation_height, settlement_account and route (status and debt as the ledger reports the vault now; null on every other operation). In an address history, direction is null when the item is listed only because the address owns the vault.
$ curl "https://crc20.org/api/v1/addresses/bc1ppnrxdhrfu9dvpxgxeem5w5qcern689yhw5l2hfthghun0wz8sgcq5zwz9k/history?tick=AXBT&limit=1"Replaying AXBT
For the items whose tick is AXBT, credit amount to to and debit it from from (a null side is the mint or the burn; vault_liquidation moves nothing). Pool operations carry tick LEAF but move AXBT too: see pool. This reproduces axbt.balance, which stays authoritative; treat a type you do not know as non-crediting.
Statistics
Token statistics
/api/v1/stats/{tick}Holders, operations, volumes, a daily series and the top holders of a token at the index tip, computed once per block. Additive: fields may be added, existing ones keep their meaning. The example shortens top, daily and cdp.list to one item.
tickLEAForAXBT, case-insensitive
$ curl https://crc20.org/api/v1/stats/AXBTtokentype,supply,max_supply(nullfor AXBT),holders,heightoperationstotal,by_type(only the kinds that occurred:transfer,sale,mint,swap,liquidity_deposit,liquidity_payback,vault_*),last_24handlast_7d(windows ending atwindow_end),firstandlastaddresses- distinct addresses in the token's operations
volume- atoms
transferred,swapped(the token's leg in the pool),minted,burned holderscount,top(the 10 largest balances),exact(true when a replay of every operation matches them)daily- per UTC day from the first operation:
operations,volume,holdersat the end of the day poolswapsand thelastone (side,leafandaxbtatoms)market- LEAF sales:
sales,volume_sat,last_unit_price_sat,last·nullfor AXBT cdp- AXBT vaults:
open_debt,bad_debt,vaults,open_vaults,collateral_locked_sats, every vault inlist·nullfor LEAF
AXBT/LEAF pool
The ledger runs one pool, SWAP_POOL:4:AXBT:4:LEAF. Its swaps and liquidity deposits are ordinary operations of the history and transaction routes, with their own field:
- swap
typeswap;amountis the LEAF leg,fromthe owner when it sells LEAF,towhen it buys.swap:side(buyorsell),input_tick/input_atomspaid in,output_tick/output_atomsreceived,fee_atoms(in the input token, kept by the pool's liquidity)- deposit
typeliquidity,liquidity.actiondeposit:fromthe owner, debited the LEAFamount;deposit(AXBT,LEAF),lock_blocks,maturity_height,creates_pool- payback
- at maturity the pool pays the owner back with no transaction of its own:
actionpayback, at the settlement block, with the deposit's txid;tothe owner,credited(its share, fees included) andfees. Listed only with?tick=AXBTor?tick=all
Replaying balances with the pool
In the default scope a deposit is listed but not its payback, so replaying LEAF misses what liquidity providers get back. With ?tick=all, replaying the items whose tick is LEAF is exact for every address. The AXBT legs: a swap debits input_atoms when input_tick is AXBT and credits output_atoms when output_tick is AXBT; a deposit debits liquidity.deposit.AXBT; a payback credits liquidity.credited.AXBT.
Swap counts and the last swap are in statistics (pool). The explorer's pool page also reads internal routes (reserves, price, positions); they are not part of this API and may change without notice.
Limits
- usage
- free and public, provided as is: no availability guarantee
- anonymous
- 300 requests per minute per IP, bursts of 50 · every answer carries
RateLimit-Policy: 300;w=60 - over the quota
- 429
rate_limitedwithRetry-After: 1: wait, then retry - api key
- a larger quota, counted per key instead of per IP: ask for one (contact)
- unknown key
- 401
invalid_api_key
Send the key in the X-API-Key header (or as ?api_key=). Keep it on your servers, never in a web page.
$ curl -H "X-API-Key: crc20_…" "https://crc20.org/api/v1/history?limit=100"Watching many deposit addresses: poll /api/v1/history (every LEAF operation, newest first) every 30–60 s and stop at the last txid you have seen, rather than polling each address.
Deposits
- Give each user a single-key P2WPKH (
bc1q…) or key-path P2TR (bc1p…) address. Other types (1…,3…, P2WSH, multisig) can receive LEAF but can never send it again. - Poll
/addresses/{address}/history; credit items withdirection=in, deduplicated bytxid. - Wait for confirmations (6 recommended) and check
block_hashagainst your own node. Never credit from the mempool. - Keep a small BTC UTXO on every deposit address: sending LEAF out requires spending one of its own UTXOs as
vin[0].
Withdrawals
A LEAF transfer is a normal Bitcoin transaction with this layout:
| position | content | rule |
|---|---|---|
| vin[0] | UTXO of the sender | defines who sends · P2WPKH or P2TR · signed SIGHASH_ALL (or DEFAULT) |
| vin[1…] | fee UTXOs | any of your UTXOs |
| vout[0] | OP_RETURN payload · 0 sat | exactly one per transaction |
| vout[1] | receiver ≥ dust | must directly follow the payload · 330 sat (P2TR) · 294 sat (P2WPKH) |
| vout[2…] | BTC change | anywhere after the receiver |
Payload
Exactly {"p":"crc-20","op":"transfer","tick":"LEAF","amt":"<atoms>"} — this key order, no spaces, amt as a decimal string of atoms (1500 LEAF = "150000000000"), pushed in one direct push after OP_RETURN. The script for 1500 LEAF:
6a417b22 70223a22 6372632d 3230222c 226f7022 3a227472 616e7366 6572222c 22746963 6b223a22 4c454146 222c2261 6d74223a 22313530 30303030 30303030 30227dConfirm
After confirmation, /transactions/{txid} must return the expected from, to and amount. A 404 once the indexer is past that block means no LEAF moved: re-send only then.
Rulesa broken rule confirms on Bitcoin but moves no LEAF
- The receiver is always the output right after the OP_RETURN: disable output sorting (BIP69) and change randomisation.
- Never sign
vin[0]with ANYONECANPAY, NONE or SINGLE. - One CRC-20 OP_RETURN per transaction: no batching.
- Withdrawals from one address apply in block order against its balance: track in-flight amounts and confirm every txid.
- Never send LEAF to
1…,3…, P2WSH or multisig addresses.
Market sales
A marketplace sale is a LEAF transfer with a different template: the seller pre-signs two small "carrier" inputs with SINGLE|ANYONECANPAY, the buyer adds the payment, and the platform co-signs with its anchor. The API reports it as template = market. Integrators never need to build one.
| position | role | note |
|---|---|---|
| vin[0], vin[1] | seller carriers | 670 sat each, signed SINGLE|ANYONECANPAY · vin[0] makes the seller the sender |
| vin[2] | platform anchor | 1000 sat, signed SIGHASH_ALL by the platform |
| vin[3…] | buyer funding | pays the price and the fees |
| vout[0] | seller payment | price + the 1340 sat of carriers |
| vout[1] | payload | the LEAF amount |
| vout[2] | buyer receiver | receives the LEAF |
| vout[3] | platform fee | |
| vout[4] | anchor return | 1000 sat back to the platform |
| vout[5] | buyer change |
Price = vout[0] − 1340 · buyer paid = price + platform fee + network fee. These figures are in market.
Code
P2TR key-path sender: add tapInternalKey (32-byte x-only key) to vin[0], sign with keyPair.tweak(bitcoin.crypto.taggedHash("TapTweak", internalKey)) and keep SIGHASH_DEFAULT.
Soft covenants
A soft covenant is a Taproot (P2TR) address whose only spending path is one script leaf that commits to a program (its CMR), a relative time lock and an owner key. Like any address it holds a LEAF balance, receives transfers and sends them. Its internal key is the BIP341 "nothing up my sleeve" point 50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0, which nobody can sign for: there is no key path.
<CMR: 32 bytes> OP_DROP <delay> OP_CHECKSEQUENCEVERIFY OP_DROP <owner x-only key: 32 bytes> OP_CHECKSIG20 <32-byte CMR> 75 <delay push> b2 75 20 <32-byte key> ac- CMR
- Commitment Merkle Root: the 32-byte hash that identifies a compiled Simplicity program; change one bit of the program and the CMR changes. Bitcoin mainnet does not execute Simplicity, so the leaf only pushes the CMR (
20= push 32 bytes) and drops it (75= OP_DROP). The program is committed in the address but runs nowhere on chain — hence "soft". The CRC-20 indexer uses the CMR as the contract's identity at deploy and mint; it does not read it when the address sends LEAF. - delay
- a relative time lock of 1 to 65535 blocks, enforced by Bitcoin (
OP_CHECKSEQUENCEVERIFY): the UTXO spent must be at least that many blocks old. It locks UTXOs, not tokens: LEAF that has just arrived can leave with any UTXO of the address that is already old enough. - owner key
- the 32-byte x-only key whose Schnorr signature is required (
20= push 32 bytes,ac= OP_CHECKSIG). Without it the leaf would be spendable by anyone once the delay has passed. - address
- the NUMS key tweaked with this single leaf: the same owner and delay with a different CMR give a different address.
Bitcoin enforces the delay and the owner signature; these are the only protections of a transfer out of a soft covenant. To send LEAF from one, spend one of its UTXOs as vin[0] through the script path:
- transaction version 2;
nSequenceofvin[0]= the delay, as a block-based BIP68 value (bits 31 and 22 clear):0xfffffffd,0xfffffffeand0xfffffffffail;- witness exactly
[owner signature, leaf, control block], signedSIGHASH_DEFAULTorSIGHASH_ALL. Any other sighash confirms on Bitcoin but moves no LEAF.
Do not use a soft covenant as a deposit address, and do not send withdrawals to one unless the user asks for it.
Contact
Integrating LEAF or CRC-20? Reach the team: