Docs
Everything about XCP-69, in one place. The first half explains how launches work for anyone. The second half is for developers who want to read or build on the same on-chain data this site does.
Overview
XCP-69 is a fixed-parameter token launch standard built on Counterparty fairmint pools. There is no factory contract, no admin key, and no platform custody at any point: the protocol is the platform. Every action — creating a launch, minting, swapping — is a transaction you sign in your own wallet and broadcast to Bitcoin. This site is an interface over public on-chain data; if it disappeared tomorrow, every launch, refund, and pool would keep working exactly as before.
Every XCP-69 launch is identical: 100M supply, 69M public sale at 0.01 XCP per 1,000-token lot, 31M reserved for the liquidity pool, 10 XCP per-address cap, an on-chain pre-announcement before minting opens, and a 1,000-block window. There is no fine print to read because there is no fine print. The full parameter set lives on the How it works page.
Launch mechanism
A launch moves through four phases:
- Announce. Every launch confirms on-chain before its
start_block. Until that block arrives the fairminter ispendingand consensus rejects every mint — nobody, creator included, can mint early. There are no stealth launches: the full terms sit on-chain, inspectable, before the first lot can be bought. - Mint. A 1,000-block window (~7 days) from
start_block. Anyone can mint whole 1,000-token lots at 0.01 XCP per lot, up to 1,000,000 tokens (10 XCP) per address. Both the paid XCP and the minted tokens sit in escrow at the unspendable address — nobody holds anything until the launch resolves. The window length only ever delays failure: a sell-out settles the moment it fills, while a miss frees every minter's XCP within about a week. - Resolve. All-or-nothing at the 69M soft cap. The soft cap equals the entire public sale, so reaching it is selling out — there is no partial success. Sell out and the pool seeds; miss the deadline and the protocol refunds every minter automatically and destroys the escrowed supply. Resolution happens at end-of-block even on a hard-cap fill, so nobody can trade the pool in the transaction that creates it.
- Trade. All 690 raised XCP plus the 31M reserved tokens seed a TOKEN/XCP AMM pool. The LP tokens are minted directly to the unspendable address — liquidity is locked by consensus, permanently. Supply and description lock in the same block, and trading is live immediately.
Trading and pricing
Graduated tokens trade against a constant-product TOKEN/XCP pool. The price is simply the ratio of the pool's reserves; every swap moves it. A fixed 50 bps fee on each swap is paid to the pool itself — and since the LP is burned, fees deepen the locked liquidity rather than paying anyone out.
The pool opens with 690 XCP against 31M tokens: 69/31 ≈ 2.23× the mint price. Every minter is structurally in profit at open, and the pool — not later buyers — absorbs early exits.
Counterparty's DEX order is the single trading primitive: matching routes through the pool whenever the pool's price beats the order book. A market order is an order at the router's quoted output — it fills from pool and book at best price immediately. A limit order rests on the book, and the pool fills it automatically if its price ever crosses yours.
- Price
- XCP reserve ÷ token reserve. Moves with every swap; there is no order book and no market maker.
- Market cap
- Pool price × 100M total supply. A convention, not a promise — the pool could not pay it out.
- Price impact
- How much your own swap moves the price. Larger swaps against the fixed reserves get a worse average price.
- Slippage
- The difference between the quoted price and what executes, if the pool moves between your quote and your confirmation.
Graduation
On other launchpads, graduation is a threshold inside a live market: the token trades on a curve while everyone hopes it reaches the magic number. XCP-69 graduation is binary and happens before any trading exists. Sell out the 69M sale within 1,000 blocks and the launch graduates — pool seeded, LP burned, supply locked, trading live in the same block's resolution phase. Miss it and the launch never trades at all.
Refunds are not a support process. They are automatic protocol behavior: at the deadline, every minter's XCP is returned and the escrowed supply is destroyed in the same block. Failed launches move to the graveyard, where their history is preserved — the mint tape, the participant count, and the on-chain proof: a destruction record tagged "soft cap not reached".
Fees
- Creator's share of the 690 XCP raise
- 0%
- Protocol / platform share of the raise
- 0%
- Premine or mint commission to the creator
- 0
- LP tokens
- burned at the unspendable address, forever
- Swap fee after launch
- 50 bps, paid to the pool (the LP is burned, so it deepens locked liquidity)
There is no fee-split table because there are no fees to split. Every satoshi of XCP paid by minters goes into the pool. The only costs anywhere in the system are Bitcoin transaction fees, the 0.5 XCP asset-name registration fee for named assets, and the protocol's pooldeposit gas fee debited at creation — costs paid to the network, not to us or to the creator.
Risk disclosures
The standard removes the rug pull and the premine. It does not remove risk, and we won't pretend otherwise:
- The per-address cap is sybil-resistant in cost only, not in principle. The cap is per address, not per person. It raises the cost of faking a crowd of 69; it cannot prevent one.
- Refunds return XCP quantity, not fiat value. If XCP's price moves during the ~7-day window, a refund makes you whole in XCP terms only.
- The 2.23× opening premium is structural, not a price guarantee. The pool floor decays as people sell into it. Nothing stops a token from trading below mint price.
- Token media is on-chain only if the creator chooses. By default the chain permanently carries the URL of the asset-info JSON (via
lock_description) while the image and info are hosted off-chain, editable only by the asset's current on-chain owner via a wallet-signed message. If that hosting ever vanished, the token's economics — supply, pool, refunds — are untouched; only the artwork would be. Creators launching from a taproot wallet can remove the dependency entirely by inscribing the image on-chain as the permanent description.
Integration
Everything this site displays comes from public APIs. You can build your own launchpad, bot, or dashboard on the same data — nothing below requires our permission.
Network
- Chain: Counterparty on Bitcoin mainnet. XCP-69 launches are ordinary Bitcoin transactions carrying Counterparty messages.
- API base:
https://api.counterparty.io:4000/v2— or run your own counterparty-core node for trustless reads. - Protocol feature:
fairmint_pool, activated on mainnet at block 961,100 (2026-08-05). Requires core v11.2.0+.
Message format
Launches use the fairminter message (ID 90) with the pool fields pool_quantity and lp_asset set. Mints are ordinary fairmint messages in whole-lot multiples of quantity_by_price.
One integration trap worth knowing: pool_quantity and max_mint_per_address have no _normalized siblings in the API. Do all standard math in raw integer satoshi units (×10⁸) and divide by 10⁸ only for display.
Composing transactions
The compose API returns an unsigned raw Bitcoin transaction — the node never sees a key. Sign with your own wallet, broadcast, done. Add verbose=true for a PSBT and echoed params; every quantity is a raw integer.
# Compose an XCP-69 launch (unsigned tx back; sign + broadcast yourself).
# START = a future block: the pre-announcement window. The launch must
# CONFIRM before START or it opens instantly and fails conformance.
curl -G "https://api.counterparty.io:4000/v2/addresses/$ISSUER/compose/fairminter" \
--data-urlencode "asset=MYTOKEN" \
--data-urlencode "price=1000000" \
--data-urlencode "quantity_by_price=100000000000" \
--data-urlencode "hard_cap=10000000000000000" \
--data-urlencode "soft_cap=6900000000000000" \
--data-urlencode "pool_quantity=3100000000000000" \
--data-urlencode "lp_asset=$LP_NAME" \ # any unissued numeric; house style: 69…69, ≡69 (mod 97)
--data-urlencode "max_mint_per_address=100000000000000" \
--data-urlencode "max_mint_per_tx=100000000000000" \
--data-urlencode "start_block=$START" \
--data-urlencode "soft_cap_deadline_block=$((START + 1000))" \
--data-urlencode "end_block=0" \
--data-urlencode "premint_quantity=0" \
--data-urlencode "minted_asset_commission=0" \
--data-urlencode "burn_payment=false" \
--data-urlencode "lock_quantity=true" \
--data-urlencode "lock_description=true" \
--data-urlencode "divisible=true" \
--data-urlencode "description=https://…/MYTOKEN.json" \
--data-urlencode "sat_per_vbyte=$FEE_RATE" \
--data-urlencode "verbose=true"Consensus enforces the standard's coherence at parse time: soft_cap must equal hard_cap − premint − pool_quantity whenever pool_quantity > 0 — all-or-nothing is a protocol rule, not site policy. The issuer's address must hold the 0.5 XCP name-registration fee plus the pooldeposit gas fee on-ledger; both debit at confirmation, so settlement later costs nothing. Pick lp_asset with real randomness: numeric issuance is free, and a predictable name lets anyone pre-register it between broadcast and confirmation, invalidating the launch.
# Compose a mint. quantity is the TOKEN amount (raw, whole lots) —
# the XCP price is computed by consensus and debited from the minter's
# on-ledger XCP balance; nothing rides in the Bitcoin outputs.
curl -G "https://api.counterparty.io:4000/v2/addresses/$MINTER/compose/fairmint" \
--data-urlencode "asset=MYTOKEN" \
--data-urlencode "quantity=100000000000000" \
--data-urlencode "sat_per_vbyte=$FEE_RATE"
# Issuer-side XCP cost of the pool settlement (prepaid at creation):
curl "https://api.counterparty.io:4000/v2/addresses/$ISSUER/compose/pooldeposit/estimatexcpfees"Mints must be whole-lot multiples of quantity_by_price, within the per-transaction cap, and within the address's remaining allowance — a partially used cap can be topped up across multiple transactions. The minter needs the XCP on their Counterparty balance; a funded BTC wallet with no XCP will compose-fail with "insufficient XCP balance".
Conformance
Core has no on-chain standard marker, so conformance is a predicate: exact equality against the standard's fixed raw values. This is the actual function this site runs — a launch either passes it or is not XCP-69.
export const XCP69 = {
/** 100M supply */
HARD_CAP: 10_000_000_000_000_000,
/** 69M public sale — reaching it IS selling out (all-or-nothing) */
SOFT_CAP: 6_900_000_000_000_000,
/** 31M seeded into the TOKEN/XCP pool at close, LP burned */
POOL_QUANTITY: 3_100_000_000_000_000,
/** 1,000-token lots */
QUANTITY_BY_PRICE: 100_000_000_000,
/** 0.01 XCP per lot */
PRICE: 1_000_000,
/** 1M tokens = 10 XCP per address; 69M ÷ 1M = 69 participants */
MAX_MINT_PER_ADDRESS: 100_000_000_000_000,
MAX_MINT_PER_TX: 100_000_000_000_000,
/** Mint window: soft_cap_deadline_block − start_block, exactly (~7 days) */
DEADLINE_BLOCKS: 1_000,
} as const;
/** core's block_index sentinel for unconfirmed transactions */
const MEMPOOL_BLOCK_INDEX = 9_999_999;
export function isXcp69(fm: Fairminter): boolean {
return (
(fm.status === "pending" || fm.status === "open" || fm.status === "closed") &&
fm.pool_quantity === XCP69.POOL_QUANTITY &&
fm.soft_cap === XCP69.SOFT_CAP &&
fm.hard_cap === XCP69.HARD_CAP &&
fm.quantity_by_price === XCP69.QUANTITY_BY_PRICE &&
fm.price === XCP69.PRICE &&
fm.max_mint_per_address === XCP69.MAX_MINT_PER_ADDRESS &&
fm.max_mint_per_tx === XCP69.MAX_MINT_PER_TX &&
fm.premint_quantity === 0 &&
(fm.minted_asset_commission_int ?? 0) === 0 &&
fm.lock_quantity &&
fm.lock_description &&
fm.divisible &&
!fm.burn_payment &&
!fm.asset.startsWith("A") && // named assets only
// timing: scheduled start, fixed window, no end_block
fm.start_block > 0 &&
fm.end_block === 0 &&
(fm.confirmed === false ||
fm.block_index >= MEMPOOL_BLOCK_INDEX || // unconfirmed sentinel
fm.start_block > fm.block_index) && // confirmed before start
(fm.status === "closed"
// core rewrites the deadline to the fill block on early sell-out
? fm.soft_cap_deadline_block <= fm.start_block + XCP69.DEADLINE_BLOCKS
: fm.soft_cap_deadline_block === fm.start_block + XCP69.DEADLINE_BLOCKS)
);
}The commission clause is the one naive checks miss. The protocol allows a fairminter to skim up to 99% of every mint back to the creator — a premine with extra steps — and no other field catches it. XCP-69 requires it to be exactly 0.
The timing clauses are the two deliberate inequalities. Consensus does not require a future start — a launch confirming late simply opens instantly — so the pre-announcement guarantee lives here: start_block must exceed the confirmation block. Without that clause, a creator could broadcast a nominal 1,000-block sale late, confirm just before its own deadline, and run a near-instant insider mint behind thousand-block metadata. And on the fairminter row the window check relaxes to ≤ once closed because core rewrites the deadline on early sell-out — for closed launches this site restores exact equality from the immutable NEW_FAIRMINTER event (see the trap in Reading state). Everything else is exact equality on the row.
Onchain events
The full lifecycle is observable as Counterparty events via GET /v2/events/<EVENT>:
- NEW_FAIRMINTER
- a launch is created
- NEW_FAIRMINT
- someone mints (also visible in the mempool before confirmation)
- OPEN_POOL
- the launch graduated — the TOKEN/XCP pool was seeded
- POOL_MATCH
- a swap executed against the pool
- ASSET_DESTRUCTION
- escrowed supply destroyed — on a missed soft cap, tagged “soft cap not reached”
Reading state
Everything is curl-able. Launches and mints:
# All fairminters currently minting (filter with isXcp69 client-side)
curl "https://api.counterparty.io:4000/v2/fairminters?status=open&verbose=true"
# Every mint into one launch
curl "https://api.counterparty.io:4000/v2/fairminters/<TX_HASH>/fairmints"Pools, prices, and quotes:
# Pool state (reserves) — a row here means the launch graduated
curl "https://api.counterparty.io:4000/v2/pools/<ASSET>/XCP"
# Price series: one row per reserve mutation
curl "https://api.counterparty.io:4000/v2/pools/<ASSET>/XCP/price_history"
# Swap quote for a given input quantity (raw integer)
curl "https://api.counterparty.io:4000/v2/pools/<ASSET>/XCP/quote?quantity=100000000"Holders:
# Holders — the unspendable address appears holding the burned LP
curl "https://api.counterparty.io:4000/v2/assets/<ASSET>/holders"Lifecycle detection recipe. Success and failure both end at fairminter status closed, so the pool row is the disambiguator:
pending→ scheduledopen→ mintingclosed+/v2/pools/<ASSET>/XCPreturns a pool → graduatedclosedwith no pool row → refunded (corroborate with the destruction tagged "soft cap not reached")
Second integration trap: soft_cap_deadline_block is rewritten when a launch sells out early — core pulls it forward to the fill block so the pool seeds at that block's end-of-block phase. On a closed record the field is the settlement block, not the original deadline. Countdown UIs are only meaningful while status is open. The composed value survives in the append-only event history: GET /v2/transactions/<tx_hash>/events/NEW_FAIRMINTER returns the original bindings (the rewrite is a separate FAIRMINTER_UPDATE event), which is how this site verifies the exact window for closed launches.
Reference launch
Support & terms
All data shown on this site is public on-chain data; anything you see here you can verify yourself against a Counterparty node. The site is an interface, not a counterparty to any transaction — it never holds funds and cannot reverse, expedite, or refund anything (the protocol handles refunds on its own). Nothing here is investment advice; tokens launched through XCP-69 can and will lose value. Read the risk disclosures before minting.