Put it on a mint
Name your hook on a mint you create, or launch it on the 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; swap.swapWithHooks takes each side’s hook.
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, 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. Its code isn’t reviewed: the site labels the token “Custom hook, unverified”.
import { buildCreateConfig, inspectConfig, taxHook, TAX_HOOK_FLAGS, TAX_HOOK_PROGRAM, NO_LAUNCH_RULES } 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 (tax_hook's own instruction; write one like it).
const prepare = taxHook.prepare(me, mint, collector, 100, 0); // 1% of every transfer to collector
// 2. A config: burn and the creator fee go with a custom hook; holder rewards, max wallet and the locks do not.
const made = buildCreateConfig(me, {
rules: { ...NO_LAUNCH_RULES, burnBuyBps: 25, burnSellBps: 25 },
creatorFeeBps: 100,
customHook: TAX_HOOK_PROGRAM,
customHookFlags: TAX_HOOK_FLAGS,
label: 'taxed',
});
// 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 seen = 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.