Put it on a mint
A mint with your hook, a pool or a bonding curve your program opens, or a launch on Bordrless's launchpad from a config.
On a mint you create#
import { Keypair } from '@solana/web3.js';
import { TOKEN_HOOK_FLAGS } from '@bordrless/shared';
import { TAX_HOOK_PROGRAM, fetchTokenHook, token } from '@bordrless/sdk';
const mint = Keypair.generate();
const flags = TOKEN_HOOK_FLAGS.BEFORE_TRANSFER | TOKEN_HOOK_FLAGS.TRANSFER_RETURNS_DELTA;
const create = token.createMint(creator, mint.publicKey, {
decimals: 6,
name: 'My token', symbol: 'MINE', uri: 'ipfs://…',
maxSupply: 1_000_000_000_000_000n,
mintAuthority: creator,
freezeAuthority: null,
hookProgram: TAX_HOOK_PROGRAM, // the mint names its hook…
hookFlags: flags, // …and the callbacks it subscribes to
hookAuthority: creator, // kept while the hook is set up
metadataAuthority: creator,
});
// Change the hook or its flags while the hook authority is held…
const change = token.setHook(creator, mint.publicKey, TAX_HOOK_PROGRAM, flags);
// …then lock it: with no hook authority, which program runs is fixed.
const lock = token.setAuthority(creator, mint.publicKey, 'hook', null);
// A transfer of a hooked mint carries the hook's accounts, resolved for that transfer.
const hook = await fetchTokenHook(connection, { mint: mint.publicKey, source, destination, authority });
const send = token.transfer(authority, source, destination, mint.publicKey, 1_000_000n, hook);
Every transfer, mint and burn of a hooked mint carries the hook program, its signer and the extras: fetchTokenHook resolves them and the builders take the result. A pool for the token is swap.createPool (below); swap.swapWithHooks takes each side’s hook.
Your own pools and curves#
swap.createPool is permissionless for an ordinary pool: any two mints of the standard, any LP fee from 0 to 9,000 bps, with or without a pool hook. A bonding curve (virtual reserves) is opened only by its hook program, which signs with hookAuthority(program), its ["hook-authority"] PDA, and graduates it with finalizeCurve: a launchpad of your own. The pool hook’s extras live in a registry under the pool, registryAddress(program, pool), whose address poolAddress gives before the pool exists.
import { PublicKey } from '@solana/web3.js';
import { POOL_HOOK_FLAGS } from '@bordrless/shared';
import { BRIDGED_SOL_MINT, SWAP_CONFIG, decodeSwapConfig, fetchAccount, holdingAddress, hookAuthority, lpMintAddress, poolAddress, registryAddress, swap } from '@bordrless/sdk';
const MY_CURVE = new PublicKey('…'); // your deployed pool hook
// 1. An ordinary pool: anyone, any two mints of the standard, any LP fee from 0 to 9,000 bps. Base and quote
// come from the authority's holdings; the LP goes to its LP holding. The protocol fee is the config's.
const config = (await fetchAccount(connection, SWAP_CONFIG, decodeSwapConfig))!;
const plain = swap.createPool(me, me, config.treasury, mint, BRIDGED_SOL_MINT, {
lpFeeBps: 100,
hookProgram: null, hookFlags: 0,
virtualBase: 0n, virtualQuote: 0n, // zero: not a curve
baseAmount: 500_000_000_000n, quoteAmount: 10_000_000_000n,
baseHookAccounts: 0, quoteHookAccounts: 0, // a hooked base passes its token-hook slice in extras
hookData: new Uint8Array(),
});
// 2. 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 (CurveNeedsHook otherwise), so this instruction is what your
// program sends by CPI, invoke_signed for the caller and for the authority that holds the supply.
const caller = hookAuthority(MY_CURVE); // PDA(["hook-authority"], MY_CURVE)
const vault = new PublicKey('…'); // your program's PDA that owns the base supply
const pool = poolAddress(mint, BRIDGED_SOL_MINT, 30, MY_CURVE);
const registry = registryAddress(MY_CURVE, pool); // ["bordrless-hook-accounts", pool]: write it first if your callbacks need extras
const curve = swap.createPool(me, vault, config.treasury, mint, BRIDGED_SOL_MINT, {
lpFeeBps: 30,
hookProgram: MY_CURVE,
hookFlags: POOL_HOOK_FLAGS.BEFORE_SWAP | POOL_HOOK_FLAGS.BEFORE_SWAP_RETURNS_DELTA,
virtualBase: 0n, virtualQuote: 30_000_000_000n, // the curve's offset
baseAmount: 1_000_000_000_000n, quoteAmount: 0n, // a curve deposits base only
baseHookAccounts: 0, quoteHookAccounts: 0,
hookData: new Uint8Array(),
}, extras, caller);
// Your hook gets no initialize callbacks for a pool it opens itself; before_swap and after_swap run on every trade.
// 3. Graduation: your program's own instruction CPIs finalize_curve with the same PDA signing. The virtual
// reserves go, the real ones stay, and the first LP is minted to the holding you name. Liquidity is locked
// (CurveLocked) until then.
const [, bump] = PublicKey.findProgramAddressSync([Buffer.from('hook-authority')], MY_CURVE);
const graduate = swap.finalizeCurve(caller, bump, pool, mint, BRIDGED_SOL_MINT, holdingAddress(lpMintAddress(pool), vault));
A pool you open pays the DEX’s protocol fee, protocolFeeBps of the quote side as the config sets it when the pool is created, and keeps every cut its hook takes; the share model (the LP fee and a quarter of the cuts to Bordrless) applies only to the curves the launchpad opens. A swap on it is swap.swapWithHooks with each mint’s token hook and the pool hook’s extras. What is open and what stays Bordrless’s: build on Bordrless.
Integrator fee (router)#
An app that routes swaps (a terminal, a wallet, a bot) can charge its own fee on any of the DEX’s pools, launch pools and ordinary ones alike: build the swap as you would, then wrap it with router.swapV2 (a native pool’s swap_v2, every launch since 15 Oct 2026), or router.swap for the classic swap of a coin still on bridged SOL. The fee is 1 to 100 bps (ROUTER_MAX_FEE_BPS, 1%) of the quote side, rounded down; a buy pays it from its input before the DEX swaps the rest, a sell from what the DEX delivered (minQuoteAfterFee is the trader’s floor on what they keep). A fifth goes to the DEX config’s treasury, the rest to your app’s wallet in SOL (with router.swap, to your holding of the pool’s quote: bridged SOL, which you unwrap with the bridge). The DEX’s fees and every hook’s still apply, as on a direct swap. Bordrless’s own site charges none.
import { SWAP_CONFIG, decodeSwapConfig, launch, launchKeysOf, router, routerFeeSplit } from '@bordrless/sdk';
// Bordrless's share goes to the DEX treasury's wallet; yours to your app's wallet, in SOL.
const { treasury } = decodeSwapConfig((await connection.getAccountInfo(SWAP_CONFIG))!.data);
// A buy, built as without the router: amountIn is the trader's whole input, your fee included.
// launch.swap builds swap_v2 for a native launch (SOL from the trader's wallet, no wrap)…
const keys = launchKeysOf(l); // the launch, decoded
const buy = launch.swap(keys, trader, trader, 1, 1_000_000_000n, minTokensOut);
// …then wrapped by the router: 0.5% of it to you, a fifth of that to Bordrless.
const routedBuy = router.swapV2(trader, app, treasury, buy, 50);
routerFeeSplit(1_000_000_000n, 50); // { fee: 5_000_000n, bordrlessShare: 1_000_000n, integrator: 4_000_000n }
// A sell: the DEX swaps first, the fee comes from the SOL it delivered; the trader keeps at least minSolAfterFee.
const sell = launch.swap(keys, trader, trader, 0, tokensIn, minSolOut);
const routedSell = router.swapV2(trader, app, treasury, sell, 50, minSolAfterFee);
// A coin still on bridged SOL: launch.swapClassic, and router.swap with your holding of bridged SOL.
Partner fee#
An app or a service that launches coins through the SDK can take a share of every coin’s creator fee without writing a hook, Meteora-style: make a partner config with buildCreatePartnerConfig. It fixes the creator fee and names a partner wallet with its share, 1% to 100% of the creator fee (what is left after Bordrless’s quarter), paid to the partner on every claim of every coin launched from it, whoever launches, in SOL (to its wallet; on a coin from before 15 Oct 2026, to its bridged-SOL holding). It can carry any custom hook and an author’s share too (at most 100% in all), and it never changes. Anyone may pay a coin’s fees out with launch.distributeFeesSol (launch.distributeFees on an older coin), every party at once to its own fixed wallet or holding, so a partner claims whenever it likes; the creator’s and the author’s claims pass the partner’s accounts (partnerOf reads them). A coin with a companion can’t launch from a partner config: share the launcher’s part with payees.
import { NO_LAUNCH_RULES, buildCreatePartnerConfig, decodeLaunch, feeSplit, launch, launchAddress, partnerOf } from '@bordrless/sdk';
// 1. Once: a config with a 1% creator fee, all of it (after Bordrless's share) to your partner wallet.
const made = buildCreatePartnerConfig(me, { rules: NO_LAUNCH_RULES, creatorFeeBps: 100, customHook: null, customHookFlags: 0, label: 'mine' }, partnerWallet, 10_000);
// send made.instruction, signed by me and made.keypair.
// 2. Every coin your users launch passes made.address as its launch config (createLaunch's options.launchConfig).
// 3. Collect whenever you like: anyone may send it, each party is paid SOL to its own wallet
// (a coin from before 15 Oct 2026: bridged SOL to its holdings, with launch.distributeFees).
const l = decodeLaunch((await connection.getAccountInfo(launchAddress(mint)))!.data);
const p = await partnerOf(connection, l); // { config, partner }
const ix = l.isNative
? launch.distributeFeesSol(mint, p!.config, l.creator, p!.partner)
: launch.distributeFees(mint, l.quoteMint, p!.config, l.creator, p!.partner);
// What a claim of 1 SOL pays (no author share): feeSplit(1_000_000_000n, 0, 10_000) → partner 1 SOL, creator 0
Transactions#
buildV0Transaction(payer, instructions, blockhash, [protocolLookupTable(key)]) builds a v0 message that loads the fixed addresses from the protocol table (its first 18 never change; later ones are only appended). explainFailure(logs) names the program that failed and its error, in words.
On the launchpad#
The rules travel in a LaunchConfig, made once and reused; paste its key under “Custom config” on the launch page. The hook must be immutable, timelocked, or upgradeable only by Bordrless (make it immutable after deploying); the launch program refuses the config otherwise. The launch creates the mint, so prepare your hook for the mint address the page shows first (the launch does it itself for a Workshop hook). Bordrless has not read a hook you deployed yourself: the site shows it as a custom hook written by its creator, with who can upgrade it, and no reading; a Workshop hook’s config shows Bordrless’s reading of the hook instead.
import { TOKEN_HOOK_FLAGS } from '@bordrless/shared';
import { buildCreateConfig, buildCreateListedConfig, inspectConfig, inspectTokenHook, NO_LAUNCH_RULES, taxHook, templateHook } from '@bordrless/sdk';
// 0. Your own hook: after deploying it, make it immutable (tax_hook is the protocol's, so it may stay upgradeable):
// solana program set-upgrade-authority <PROGRAM_ID> --final
// 1. Prepare the hook for the mint the launch page shows: the template's standard prepare (its state and its
// registry), or your hook's own instruction (tax_hook.prepare(me, mint, collector, 100, 0): 1% to collector).
const prepare = templateHook.prepare(hook, me, mint);
const seen = await inspectTokenHook(connection, hook, mint); // accepted: true, registryReady: true, accounts resolved
// 2. A config: burn and the creator fee go with a custom hook; holder rewards, max wallet and the locks do not.
const flags = TOKEN_HOOK_FLAGS.BEFORE_TRANSFER | TOKEN_HOOK_FLAGS.TRANSFER_RETURNS_DELTA; // what the hook answers
const args = {
rules: { ...NO_LAUNCH_RULES, burnBuyBps: 25, burnSellBps: 25 },
creatorFeeBps: 100,
customHook: hook,
customHookFlags: flags,
label: 'mine',
};
const made = buildCreateConfig(me, args);
// or, listed: my share (1 to 5_000 bps; here half, the most) of the creator fee of every launch someone else makes from it
const listed = buildCreateListedConfig(me, { ...args, label: 'mine, listed' }, 5_000);
// send made.instruction signed by me and made.keypair; paste made.address into the launch page.
// 3. The same checks the page makes: the bounds now, the hook and who can upgrade it, its registry for the mint.
const config = await inspectConfig(connection, made.address, mint); // problems: [], hookAccepted: true, registryReady: true
Bordrless takes 25% of what a launch’s rules collect on each swap (the creator fee, holder rewards and any cut your hook takes), in SOL; a config can’t change that. The registry must not depend on who sends or receives: the launch passes one slice of accounts for every transfer, mint and burn of the token. A config offered on the launch page’s own list is a Workshop hook’s: the one its builder made on its Workshop page, or one they made here with buildCreateListedConfig and launched with, which the Workshop adopts. Any other config is used by its key.