Protocol
bordrless-programsBuild on Bordrless
The token program, the DEX and the bridge take instructions from anyone. Your own tokens with your own hooks, your own pools and bonding curves, your own bridged quote assets: none of it goes through Bordrless's launchpad.
What is open#
Three programs make the standard anyone can build on. Each takes its instructions from any signer; the launchpad, the kit and the rest of what Bordrless runs on them are callers like any other (what stays Bordrless’s).
- The token program
create_mintis permissionless: the mint is a keypair that signs, the payer pays the rent. Its arguments are decimals (at most 12), a name (1 to 32 bytes), a symbol (1 to 10), a URI (at most 200),max_supply(0 for unlimited) and four optional authorities, mint, freeze, hook and metadata, each replaceable or revoked for ever withset_authority. The hook is any program but the token program itself; while a hook authority is held,set_hookchanges the program or its flags (the token program).- The DEX
create_poolis permissionless for an ordinary pool: any two different mints of the standard, anylp_fee_bpsfrom 0 to 9,000. A bonding curve is a pool with virtual reserves, and only its hook program may open one: the DEX wants the hook’s["hook-authority"]PDA as a signer (CurveNeedsHookotherwise). So any deployed program opens a curve as its own pool hook, and graduates it withfinalize_curve, which the same PDA signs. That is a launchpad.- The bridge
registeris permissionless: the first wallet to bridge SOL, an SPL token or a Token-2022 token makes its wrapped mint,["wrapped", underlying], and anyone wraps and unwraps one for one after that. Bordrless’s own pools are quoted in bridged SOL; a pool of yours may be quoted in it, or in anything you bring in (the bridge).
Today the site’s trade path finds a token’s pool as the deepest one quoted in bridged SOL, and its token pages show launches; a pool quoted in anything else, or a second pool of a pair, trades through a client of your own. The indexer records every mint and every pool created, whoever made it.
What you get#
- A token hook
- Runs before and after every transfer, mint and burn of the mint, for the callbacks its flags subscribe to. Before a transfer it may answer up to three cuts, each to a holding it named in its registry, write the 64 bytes of hook data every holding keeps, or refuse. It never holds the user’s signature; it answers, and the token program applies the answer (the hook protocol, the 64 bytes).
- A pool hook
- Runs on pool creation, on liquidity and on every swap.
before_swapmay answer up to three cuts and a burn from the input,after_swapthe same from the output, andbefore_swapmay set the swap’s LP fee, up to 9,000 bps; any callback may refuse. It cannot move the reserves. A hook that opens its own pool gets no initialize callbacks for it. - Together
- A program that is a pool hook opens the curve, takes its cuts on every trade, refuses what it wants and graduates with
finalize_curve; a token hook of yours, or none, goes on the mint; the quote is bridged SOL or an asset you registered. Every piece Bordrless’s launchpad is made of is yours to make.
What it costs#
- Rent for the accounts you create and Solana’s transaction fees. The DEX config may charge a pool creation fee (
pool_creation_fee_lamports); it is zero as of this writing. - On an ordinary pool, the DEX’s protocol fee:
protocol_fee_bpsof the quote side of every swap, set in the DEX config and fixed in the pool when it is created, for the pool’s life. The decision for pools created from now on is 0.25% (docs/dex.md §0, decision 1); the config on chain,SWAP_CONFIG, is the authority for what a pool made today gets. The LP fee stays in the pool, for its liquidity providers. - A pool you open keeps 100% of what its hook cuts. Only a curve the launchpad itself opens pays the share model, in which the LP fee (0.3%) and 25% of what the hooks cut go to Bordrless; a curve any other program opens is an ordinary pool for the fee (
FEE_MODEL_FLATinbordrless_swap/src/constants.rs).
What stays Bordrless’s#
Your own launchpad is yours to build: a program that mints on the token program and opens curves on the DEX as its own pool hook, as What is open describes, with whatever rules you write into its hooks. What you cannot reuse are Bordrless’s own products on the three programs, which third parties use through the site or not at all:
- The launchpad (
bordrless_launch): launches, the curve it opens as its own pool hook, launch configs and their rule on who may upgrade a custom hook. That rule covers launches only. - The kit: holder rewards, max wallet, the creator and early-buyer locks. Its
initaccepts only the launchpad’s["kit-caller", mint]PDA as the caller, so kit rules cannot go on a mint you create. - The fee hooks on the companion program (
bordrless_companion) and their games, the Workshop, and Half-Life (whoseprepareexempts the mint’s launch PDA and nothing else, so it does not fit a free mint).
The source is Apache-2.0 (bordrless-programs): re-implement what you want in hooks of your own. programs/tax_hook is the worked example of a token hook that fits a mint you create; a launch on the launchpad with a hook you wrote is the other way in.
The contracts#
The instructions, as the SDK builds them. create_mint neither calls the hook nor checks its registry, so a hook that keeps per-mint state prepares ["bordrless-hook-accounts", mint] before the first transfer; a pool hook’s registry hangs off the pool, whose address is known before it exists. The hook interface itself is the crate bordrless-hook (write a hook).
// A mint of your own, with a hook of your own
token.createMint(payer, mint, {
decimals, name, symbol, uri, maxSupply,
mintAuthority, freezeAuthority, hookProgram, hookFlags,
hookAuthority, metadataAuthority,
})
// While the hook authority is held:
token.setHook(hookAuthority, mint, hookProgram, hookFlags)
// Revoke it, and which program runs is fixed:
token.setAuthority(authority, mint, 'hook', null)
// ["bordrless-hook-accounts", mint]: write it before the first transfer
registryAddress(hookProgram, mint)
// A pool of your own: an ordinary one, by anyone
swap.createPool(payer, authority, treasury, baseMint, quoteMint, args)
// A curve your program opens as its pool hook, by CPI
swap.createPool(payer, authority, treasury, baseMint, quoteMint, args,
extras, hookAuthority(program))
// Its graduation, by CPI
swap.finalizeCurve(hookAuthority(program), bump, pool, baseMint, quoteMint,
lpRecipient)
// The pool hook's extras
registryAddress(program, poolAddress(baseMint, quoteMint, lpFeeBps, program))
// A quote asset of your own: registered once per underlying mint
bridge.register(payer, underlying, underlyingProgram, { name, symbol, uri })
// One for one, and back with unwrap
bridge.wrap(user, underlying, underlyingProgram, userUnderlying, amount)
Budgets#
- CPI height
- Mainnet allows an invoke height of 5. A launchpad of your own on top of the DEX is at 1, the DEX at 2, the token program at 3, the token hook at 4; a CPI from that hook is 5, the limit. Bordrless’s own hooks make no CPIs and emit no events from their callbacks (docs/hooks-v2.md §4.12).
- Transaction size
- 1,232 bytes. A swap on a hooked pair carries both mints’ hook slices and the pool hook’s extras: use a lookup table of your own (
createProtocolLookupTablein the SDK makes one from any list of addresses; the protocol’s table holds Bordrless’s fixed addresses, not yours). - Accounts
- 64 account locks and 64 trace entries per transaction (docs/hooks-v2.md §6: the limits on mainnet today).
Trust#
- The programs are upgradeable by Bordrless’s key until audited: who holds each one, read from the chain. While that is so their code can change under your pools and mints.
- The DEX config’s
pausedflag haltscreate_pool,swap,add_liquidityandremove_liquidityon every pool, yours included. The bridge config has a flag of its own. - The hook interface is the crate: the callbacks are instructions named after them, their flags and account prefixes are constants the crate’s tests hold to their values, and the registry carries a layout version (1). A hook written against it keeps working as long as the programs do.
- Bordrless reviews nothing you deploy. The launchpad’s rule on who may upgrade a hook covers launches only: a mint you create may name any hook, upgradeable by anyone, so who can upgrade a hook is the first thing a client should read (
hookRiskLabelin the SDK).
Balances and decimals, for integrators#
A Bordrless holding is an account of the token program, not an SPL or Token-2022 account, so the RPC’s preTokenBalances and postTokenBalances say nothing about it: a terminal reading a trade from the transaction meta sees no token movement at all. The token program emits an event for every balance change instead, as a self-CPI in the inner instructions, and each carries the post-balances of the holdings it touched: Transferred has source_post, destination_post and every hook delta with its holding, owner, amount and post; Minted and Burned have destination_post or source_post and supply_post. The pre-balances follow: the source had source_post + amount, the destination destination_post − (amount − Σ deltas) (a hook’s cuts never reach it), each delta holding post − amount.
- Decimals
- Per mint, fixed at
create_mint, 0 to 12. The launchpad’s tokens are 6 (TOKEN_DECIMALSin@bordrless/shared, and will stay so); bridged SOL is 9; an LP mint is 9 (LP_DECIMALS); a free mint is whatever its creator chose. Read them from the mint account withdecodeMint, or from theMintCreatedevent; never hardcode 6. - The SDK
tokenBalanceChanges(tx, { connection })takes anygetTransactionreply (web3.js’s, the parsed one or the raw JSON) and answerspreTokenBalancesandpostTokenBalancesin web3.js’sTokenBalanceshape (accountIndex,mint,owner,programId,uiTokenAmount) plus the holding’s address asaccount, one row per holding aschanges(pre,post,change,created,closed) and the decimals used asdecimalsBy. A holding created in the transaction has no pre entry and one closed in it no post entry, as SPL accounts do; a failed transaction lists nothing.holdingBalanceChangesis the same over decoded events, with no RPC. A mint whose decimals it cannot read is aMissingDecimalsError, never a guess.- The API
GET https://bordrless.app/api/tx/<signature>/balancesanswers{ signature, slot, blockTime, err, preTokenBalances, postTokenBalances, changes, decimalsBy }for any confirmed transaction (the backend’s/v1/tx/:signature/balances, behind the site’s key); 404 for a signature the node has no confirmed transaction for, 400 for a malformed one.
import { Connection } from '@solana/web3.js';
import { tokenBalanceChanges } from '@bordrless/sdk';
const connection = new Connection(RPC_URL);
const tx = await connection.getTransaction(signature, {
maxSupportedTransactionVersion: 0,
});
const { preTokenBalances, postTokenBalances, decimalsBy } =
await tokenBalanceChanges(tx!, { connection });
for (const b of postTokenBalances) {
const amount = b.uiTokenAmount.uiAmountString;
console.log(b.account, b.owner, amount, decimalsBy[b.mint]);
}
Read next#
- A mint with your hook and your own pools and curves, with the SDK.
- Write a hook: the callbacks, the answer, the flags, the registry.
- The hook protocol and the token program, in full.