Docs · Integration

Integrating the Hooked standard.

For trading terminals, aggregators, indexers, wallets and portfolio trackers that want to list, chart and trade tokens of the Hooked standard.

Start here

Tokens of the Hooked standard are not SPL or Token-2022 tokens. They live in Hooked's own token program and trade on Hooked's own DEX. Code written for SPL won't see them: wallets don't list them, getTokenAccountsByOwner doesn't return them, and aggregators don't route them.

Everything is public and needs no key or permission. There are three ways in, and they mix freely:

  • The HTTP API. Launches, one token, trades, holders, a wallet's balances, and prepared transactions for buys and sells that your user only has to sign. The fastest way to list and trade. See The HTTP API.
  • The chain. Every figure the API returns is read from accounts and events you can read yourself. See Reading the chain and Indexing trades.
  • The IDLs. All of the programs are Anchor programs. Their IDLs are downloadable, so any Anchor client in any language can decode the accounts and events and build the instructions. See IDLs and other languages.

What these tokens are, for a trader, is in the main docs: The Hooked standard. To see any of it live, decoded, use the explorer: link to /standard/explorer/tx/<signature> and /standard/explorer/address/<address> wherever you would link to a block explorer.

Addresses

The programs have the same addresses on mainnet and devnet.

Fixed addressAddressNotes
Wrapped SOL mint7C1y3LjARhr6SFREBeAdgsbNo5LkCsh6PsYiNdS2ydXNThe quote of every launch. 9 decimals, 1 wrapped SOL = 1 SOL.
DEX config3GvnuXnFMYoC5qZLjB9eqkaTCwnvsErjkFdkFCCieEnxFee settings of the DEX.
Launchpad configE75tBAfyvEYcrAttCep9WepW181tqjMjFF5mAfig9cY3Launch fee, curve and rule limits.
Bridge configBGzB2BE5iQ8RXTE73L25ZS3hYWRQH2nQYA2RNpNoQsiL
Protocol lookup table, mainnetDCAgnvcmr67nafZbCPdpd7f5HRdviZXkzZ3vpVqTRBNqThe 18 fixed addresses every swap uses.
Protocol lookup table, devnet7TDogRcMTBETAqFRhjinWaEkmqAkhvk6NbSwKn9tTe2P

Put the lookup table in every v0 transaction you build. A swap names many accounts; without the table a buy or sell with SOL comes close to Solana's 1,232-byte limit and leaves no room for a priority fee or a graduation.

The programs are upgradeable by Hooked. Check any of them with solana program show <address>.

Tokens, holdings, pools, launches

On SPLOn the Hooked standardNotes
Mint accountMint, owned by the token programName, symbol and metadata link are stored in the mint itself. There is no Metaplex account to read.
Associated token accountHolding at ["holding", mint, owner] under the token programExactly one per mint and owner.
getTokenAccountsByOwnergetProgramAccounts on the token program, filtered by ownerSee Reading the chain.
Transfer hookToken hook, named on the mintRuns before a transfer and can take part of it, not only allow or refuse it.

A mint holds decimals, supply, maxSupply, its authorities (mint, freeze, hook, metadata), hookProgram and hookFlags, name, symbol, uri, createdAt and creator. Launched tokens have 6 decimals and start at a supply of 1,000,000,000. The link points to the usual metadata JSON (name, symbol, description, image).

A holding holds mint, owner, amount, an optional delegate and delegatedAmount, frozen, and 64 bytes of hookData that only the mint's hook can write (holder-reward accounting lives there). Creating one is idempotent and costs about 0.0023 SOL of rent.

A pool is a constant-product pool with virtual reserves:

price (quote per base) = (quoteReserve + virtualQuote) / (baseReserve + virtualBase)

Its address is ["pool", baseMint, quoteMint, lpFeeBps as u16 LE, hookProgram] under the DEX, and its two vaults are holdings owned by the pool. The pool account keeps the real and virtual reserves, the fees and running totals (swapCount, baseVolume, quoteVolume, lastSwapAt). Every launch pool has the launchpad as its pool hook, which applies the launch's fees on each swap.

A launch is the account ["launch", mint] under the launchpad. It points to its pool and keeps the token's market:

  • pool, quoteMint (always wrapped SOL), status (0 on the curve, 1 graduated);
  • the curve: virtualQuote, virtualBase, curveTokens (750,000,000), reserveTokens (250,000,000), graduationQuote;
  • fees: lpFeeBps (0.3%), creatorFeeBps (0 to 2%), and the sniper fee sniperStartBps (80%) falling in a straight line to lpFeeBps over sniperWindowSecs (30) after createdAt;
  • the token rules (rules, modules) and an optional customHook;
  • running totals: creatorFeesAccrued, creatorFeesClaimed, holderFeesAccrued, burnedOnTrades.

Graduation. 75% of the supply is sold on the curve. When the pool's real SOL reaches graduationQuote (twice the opening virtualQuote), anyone can call graduate. It tops the pool up from the 25% reserve so the price doesn't jump, burns what is left of the reserve and drops the virtual reserves. Trading continues in the same pool: there is no migration and no new address to follow.

Hooked's share. A launch pool pays Hooked a quarter (protocolShareBps 2,500) of what the launch's rules collect on each swap, in SOL. A burn and the pool fee are not shared. An ordinary pool, not made by a launch, pays a flat 1% of the quote side.

Account layouts

Every account is an Anchor account: an 8-byte discriminator, then the fields in IDL order, Borsh encoded.

AccountSizeDiscriminator (hex)Fixed offsets for memcmp filters
Mint51950bcf5145f8a399cdecimals 9, supply 10 (u64)
Holding204176040faebbf0090mint 10, owner 42, amount 74 (u64)
Pool411f19a6d0411b16dbcbaseMint 11, quoteMint 43
Launch565903333a3ce55d526mint 10, creator 42, pool 74, quoteMint 106, status 138
LaunchConfig12a109e066911d5e
KitConfig431bf93e526a1148293
Wrappera10b6d77563da388

Each program's own Config has the discriminator 9b0caae01efacc82; tell them apart by owner. Use the offsets only for getProgramAccounts filters and decode with the IDL, because fields after an optional key move.

AddressSeedsUnder
Holding"holding", mint, ownerToken program
Pool"pool", baseMint, quoteMint, lpFeeBps (u16 LE), hookProgramDEX
LP mint"lp", poolDEX
Launch"launch", mintLaunchpad
Kit config"kit", mintRules kit
Wrapper"wrapper", original mintBridge
Wrapped mint"wrapped", original mintBridge
SOL vault"sol-vault"Bridge
A program's config"config"That program
A program's event authority"__event_authority"That program

The HTTP API

Base URL https://www.hookedpad.com. No key. Responses are JSON with ok: true, or ok: false and an error sentence you can show a user. Amounts are strings in the smallest units: lamports for SOL, and the token's own decimals (6 for launched tokens) for tokens. Read endpoints allow requests from any origin. Please cache what you can; trades and holders are cached for a few seconds on our side.

RequestReturns
GET /api/std/launchesEvery launch, newest first.
GET /api/std/token/<mint>?wallet=<address>One token. With wallet: that wallet's balance, claimable rewards and locks.
GET /api/std/trades/<mint>The token's trades, oldest first (up to the latest 5,000).
GET /api/std/holders/<mint>Every holder, biggest first.
GET /api/std/portfolio?wallet=<address>Every token of the standard a wallet holds.
GET /api/std/bridge?mint=<original mint>&wallet=<address>The bridge's view of an ordinary Solana token.
GET /api/std/explorer?program=dexThe explorer's counters and the latest decoded events of every program (program is optional: token, dex, bridge, launchpad, kit or hooks).
GET /api/std/explorer?tx=<signature>Every event in one transaction, field by field.
GET /api/std/explorer?address=<address>What an address is (a token, a pool, a holding or a wallet) with its facts, holders, pools, trades and recent events.
POST /api/std/txA prepared, unsigned transaction. See Trading.

A launch row

{
  "mint": "…", "name": "…", "symbol": "…", "uri": "https://…",   // uri: the metadata JSON (image inside)
  "creator": "…", "createdAt": 1791426182,                        // unix seconds
  "graduated": false,
  "preset": "Burn",                // the rule set's name, or "custom"
  "rules": ["Holder rewards: 0.5% of every buy and sell, paid to holders in SOL", …],
  "customHook": null,              // a program address when the creator wrote their own hook
  "buyFeeBps": 155, "sellFeeBps": 155,     // all in: pool fee, creator fee, holder rewards, Hooked's share
  "burnBuyBps": 50, "burnSellBps": 50,
  "solReserve": "298700000",       // real SOL in the pool, lamports
  "mcapLamports": "26891169568",   // price × current supply
  "graduationLamports": "60000000000",
  "swaps": 3, "volumeLamports": "490000000",
  "supply": "999935162000000", "decimals": 6
}

GET /api/std/token/<mint> returns the same row plus pool, lpFeeBps, sniper (windowSecs, startBps, nowBps: the pool fee a buy pays right now), creatorFeesWaiting, holderFeesPaid, burned and readyToGraduate. With ?wallet= it adds wallet: tokens, lamports, claimable (holder rewards, lamports), isCreator and lockedUntil.

A trade

{ "t": 1791426217, "side": "buy", "lamports": "200000000", "tokens": "7274422899041",
  "mcapLamports": "27243524158",   // market cap right after the trade
  "trader": "…", "sig": "…" }

For candles, bucket mcapLamports by t; divide by the supply for a price per token. A holder is { owner, amount, tag }, where tag is pool (the liquidity), reserve (tokens kept back for graduation), creator or empty. A portfolio row is { mint, amount, name, symbol, decimals, launched, wrappedSol, original }; original is set for a token that came through the bridge.

Reading the chain

Every launch. Launch accounts are owned by the launchpad and start with their discriminator, so one getProgramAccounts with a memcmp at offset 0 finds them all. Read their mints in one batch for names and symbols. For a live feed, decode LaunchCreated events instead of polling (Indexing trades).

One token's page is three reads: the mint, its launch, and the launch's pool.

priceSol     = (quoteReserve + virtualQuote) / (baseReserve + virtualBase) × 10^(decimals − 9)
marketCapSol = priceSol × mint.supply / 10^decimals
liquiditySol = pool.quoteReserve / 1e9
progress     = launch.status == 1 ? 1 : pool.quoteReserve / launch.graduationQuote
poolFeeNow   = age >= sniperWindowSecs ? lpFeeBps
             : sniperStartBps − (sniperStartBps − lpFeeBps) × age / sniperWindowSecs
  • Use the mint's supply, not 1,000,000,000. Burns lower it: the burn rule on trades, and what is left of the reserve at graduation.
  • The price includes the virtual reserves. After graduation they are zero and the same formula holds.
  • Liquidity is the pool's quoteReserve. Hooked's share waiting to be collected sits in the same vault but is tracked apart (protocolFeesQuote); don't count it.
  • Dollars are the SOL price times these figures. Wrapped SOL is SOL, one for one.

Holders. Every holding of a mint is a 204-byte account with the mint at byte 10:

getProgramAccounts(FRxxeTHBMsdHpJ9oiHHSMpGFCJ2yNuxQcyQYkCJawTYh, {
  filters: [{ dataSize: 204 }, { memcmp: { offset: 10, bytes: <mint> } }]
})

Two holders aren't people; label or leave them out: the holding owned by the launch's pool (the liquidity) and the one owned by the launch account itself (the reserve, until graduation).

A wallet's balances. The same search with the owner at byte 42. For one known token read the holding's address directly; it is derived, so no search is needed. A holding of the wrapped SOL mint is the wallet's SOL on the standard: show it as SOL.

Freshness. Accounts change with every trade. Re-read the pool, subscribe to it, or take the reserves from each Swapped event, which carries the pool as it stands after the trade.

Indexing trades

Every program emits Anchor event CPIs. Each event is an inner instruction from the program to its own event authority (["__event_authority"]), whose data is the 8-byte tag e445a52e51cb9a1d, then the event's discriminator, then its Borsh body. They are in the transaction's meta.innerInstructions, not its logs, so log truncation doesn't lose them.

  • Resolve account indexes against the full key list: the static keys, then the lookup table's writable addresses, then its read-only ones (meta.loadedAddresses). Nearly every transaction uses the lookup table.
  • Only trust an instruction whose single account is the program's event authority. Only the program can sign as it, so a faked event fails on-chain.
  • Skip failed transactions (meta.err): their events never happened.

Swapped: a trade

Emitted by the DEX. Its discriminator is d93434539387606d.

FieldMeaning
pool, trader, recipientThe pool, who signed, and whose holding received the output.
direction1 is a buy (SOL in, token out). 0 is a sell.
amountInWhat left the trader, in the input token.
burnIn, cutsIn, receivedInOf the input: burned, taken by hooks for someone, and what reached the pool.
lpFee, lpFeeBpsThe pool fee, in the input token, and its rate on this swap (the sniper fee shows here).
protocolFeeHooked's share. Always in SOL.
amountOutWhat the curve gave, in the output token.
burnOut, cutsOut, deliveredOutOf the output: burned, taken by hooks, and what reached the recipient.
deltasIn, deltasOutEach hook cut: the holding it went to and the amount.
baseReserve, quoteReserve, virtualBase, virtualQuoteThe pool after the trade.
swapCount, slot, tsThe pool's trade counter (a stable ordering key), the slot and the unix time.

Units follow the side. Input-side fields are in the input token: SOL on a buy, the token on a sell. Output-side fields are in the other one. Don't add cutsIn to cutsOut.

buy         = direction == 1
solAmount   = buy ? amountIn     : deliveredOut     // SOL spent, or SOL received
tokenAmount = buy ? deliveredOut : amountIn         // tokens received, or tokens sold
priceAfter  = (quoteReserve + virtualQuote) / (baseReserve + virtualBase) × 10^(decimals − 9)

Which transactions to read. A pool's address is in every swap on it, so getSignaturesForAddress(pool) lists a token's trades. For the whole market, stream transactions that mention the DEX program. Launches, graduations and creator claims go through the launchpad; holder claims through the rules kit.

Every event

ProgramEvents
Token programMintCreated, Minted, Burned, Transferred, HoldingCreated, HoldingClosed, HookDataWritten, HookSet, AuthoritySet, DelegateSet, FrozenSet, MetadataUpdated
DEXPoolCreated, Swapped, LiquidityAdded, LiquidityRemoved, CurveFinalized, ProtocolFeesCollected, ConfigSet
LaunchpadLaunchCreated, Graduated, CreatorFeesClaimed, LaunchConfigCreated, ConfigSet
Rules kitKitInstalled, KitGraduated, RewardsClaimed, RewardsShared
BridgeWrapperRegistered, Wrapped, Unwrapped, ConfigSet

LaunchCreated (discriminator 3b26bee621225914) carries everything a new token row needs: mint, pool, creator, name, symbol, uri, supply, decimals, the curve, the fees and the rules. A token has graduated when Graduated is emitted or Launch.status reads 1; the same pool keeps trading.

Trading

The simplest way is to ask for a prepared transaction and have your user's wallet sign and send it. The server holds no key and signs nothing: the transaction comes back unsigned, and the wallet named in the request is the only one whose funds it can move.

POST https://www.hookedpad.com/api/std/tx
{ "kind": "buy",  "wallet": "<address>", "mint": "<token>", "amount": "100000000", "slippageBps": 300 }   // lamports to spend
{ "kind": "sell", "wallet": "<address>", "mint": "<token>", "amount": "2500000000", "slippageBps": 300 }  // tokens, smallest units

→ { "ok": true,
    "tx": "<base64 v0 transaction, unsigned>",
    "expect": "2303261489051",   // what the wallet should receive (tokens on a buy, lamports on a sell)
    "min": "2234163644379",      // the least it can receive before the trade fails
    "burn": "0",
    "units": 158402 }            // the compute limit already set in the transaction
→ { "ok": false, "error": "The pool can't fill a trade that size." }      // HTTP 422, safe to show
  • It is already checked. The server simulates the transaction before returning it. If it wouldn't land, you get the reason in a sentence instead of a transaction.
  • Send it soon. It carries a recent blockhash, so it expires in about a minute. Ask again for a fresh one.
  • Slippage is checked on-chain against what the wallet's holding actually gains, after every fee and hook cut. Default 300 (3%); the range is 10 to 5,000.
  • SOL in, SOL out. A buy wraps the SOL and a sell unwraps what it delivers, inside the same transaction. Wrapped SOL the wallet already held is left alone.
  • Graduation. A buy bigger than the room left on the curve is cut down to what fills it; the response then has capped: "1" and paid (the lamports really spent). The buy that fills the curve also graduates the token: graduates: "1".
  • Other kinds: claim (a holder's rewards), creatorClaim, graduate (a full curve nobody has graduated yet), and wrap / unwrap for the bridge, where mint is the original token.

Building it yourself

Nothing requires our server. A buy with SOL is these instructions, in a v0 transaction with the lookup table:

ComputeBudget: set compute unit limit (simulate first and add 15%; a buy uses roughly 150,000 to 250,000 units)
ComputeBudget: set compute unit price (your priority fee)
token.create_holding(payer, wrapped SOL mint, owner)     // idempotent
bridge.wrap_sol(user, lamports)                          // SOL → wrapped SOL
token.create_holding(payer, token mint, owner)           // idempotent
launchpad.swap … direction 1, amount_in, min_amount_out  // the trade
launchpad.graduate …                                     // only if this buy fills the curve

A sell is create_holding (wrapped SOL), the swap with direction 0, then bridge.unwrap_sol_above(keep), where keep is the wrapped SOL the wallet held before, so only what the sale delivered is unwrapped. Account lists are in the IDLs.

Quoting. A buy is, in order: the creator and holder fees from the SOL going in, Hooked's quarter of those fees and the pool fee on what reaches the vault, the constant-product curve, then the burn from the tokens coming out. A sell is: the burn from the tokens going in, the pool fee on what reaches the vault, the curve, then the creator and holder fees from the SOL coming out, with Hooked's quarter of them held back. Three things change the result:

  • The pool fee falls from 80% to 0.3% over the first 30 seconds. Use the time the trade will land, not when the user started typing.
  • Holder rewards are only taken while the kit's eligible is at least its minEligible: a token takes none until enough of the supply is held outside the pool.
  • A creator-written hook can take a cut no formula knows about. For those tokens, simulate the transaction and read the wallet's holding afterwards.

Simulation needs no signature (sigVerify: false, replaceRecentBlockhash: true), so you can quote exactly for any wallet that holds the input. The prepared-transaction endpoint does this for you.

When a trade is refused

ErrorWhyWhat to show
SlippageThe price moved, or a hook took more than expected.Raise slippage or retry.
InsufficientLiquidityThe buy is bigger than what is left on the curve.Offer the largest buy that fits.
MaxWalletExceeded (kit)The token caps how much one wallet can hold until graduation.The cap.
CreatorLocked (kit)The creator's wallet can't sell or send until the lock ends.The unlock time.
EarlyLocked (kit)Tokens bought in the launch's first seconds are locked for a while.The unlock time.
PausedThe DEX or the launchpad is paused.Trading is paused.

Sending. Let the user's wallet sign and send wherever you can. The only signer in a trade is the user.

Rules and what to show

A token of the standard can run a token hook: a program called before every transfer, mint and burn. It can refuse the operation, take cuts from the amount and keep 64 bytes of state in every holding. Tell users which hook a token runs, because that decides whether a transfer can be taxed or refused.

CaseHow to tellWhat it can doShow
No token hookmint.hookProgramNothing. A plain token.
The rules kitthe hook is the kit, 2m3jVc…Only the rules fixed at launch, below.The rules.
Hooked's rules hookthe hook is 59gZna…, and its config at ["rules", mint] names this mint and the launch's poolWhat its rule set says: refuse a transfer, or take up to 25% of it for the burn, the creator or one wallet.The rules. The API returns them in words.
A creator-written hookany other program; the launch's customHook is setAnything, including refusing every sell.“Custom hook, unverified” and the program.

Tokens with AI rules. A launch whose customHook is the rules hook runs a rule set written in Hooked's rule language, fixed at launch. In the HTTP API such a token has aiRules: true and preset: "AI rules", its rules array reads each rule in words, and GET /api/std/token/<mint> adds ai: the rule text (source) and what the rules have taken so far (burned, waitingToBurn, toCreator, toWallet, wallet, in the token's smallest units). What the rules take from a trade depends on the wallet (how long it has held, how often it has sold), so quote these tokens by simulating the trade; the prepared-transaction endpoint does that and its expect is exact. A trade a rule refuses fails with custom error 10000 + the rule's number from the rules hook, and the endpoint returns the rule in words. On a sell the take comes out of the tokens before they reach the pool; in the Swapped event it is in cutsIn (a sell) or cutsOut (a buy).

Also check mint.hookAuthority. Empty means the hook can never be changed. Tokens launched on Hooked always have it empty.

The kit's rules are fixed at launch in launch.rules. launch.modules says which are on, as bits: 1 holder rewards, 2 max wallet, 4 creator lock, 8 early-buyer lock.

RuleFieldsEffect
Holder rewardsholderFeeBuyBps, holderFeeSellBpsA share of each trade, in SOL, to holders by how much they hold. Each holder claims their own.
BurnburnBuyBps, burnSellBpsA share of the tokens bought or sold is destroyed.
Max walletmaxWalletBpsNo wallet can hold more until graduation. It never blocks a sell.
Creator wallet locklaunch.creatorUnlockAtThe creator's wallet can't sell or send before then.
Early-buyer locklaunch.earlyWindowEnd, launch.earlyUnlockAtTokens bought before the window ends can't move before the unlock time.

Creator fee, holder rewards and burn add up to 3% per side at most. The rules array of the HTTP API already has each of these as a sentence, and buyFeeBps / sellFeeBps are the all-in figures.

Transfers between wallets run the same hook. A transfer of a kit token must carry the kit's accounts, and the receiver gets the amount less whatever the hook cut; the Transferred event lists each cut. Locks and the max wallet apply to transfers too.

The bridge

The bridge holds SOL, SPL tokens and Token-2022 tokens and issues tokens of the standard for them, one for one. It is how SOL enters the standard: every launch is priced in wrapped SOL.

  • Wrapped SOL is the mint 7C1y3LjARhr6SFREBeAdgsbNo5LkCsh6PsYiNdS2ydXN, 9 decimals, backed by plain SOL in the bridge's vault and always redeemable. Show a wallet's holding of it as SOL and value it one for one.
  • Any other token. Its twin on the standard is ["wrapped", original mint] under the bridge, with the same decimals. register sets a token's wrapper up once, by anyone; then wrap and unwrap move it in and out. The wrapper records totalWrapped, and every wrapped token is backed by the vault.
  • Pricing a wrapped token: it is the original, one for one. If you already price the original, use that.
  • Transfer fees and hooks. Wrapping credits what actually arrives in the vault, so a Token-2022 transfer fee is paid on the way in and on the way out. A Token-2022 token with a transfer hook can't be wrapped.

For users, the bridge page does all of this. A wrapped token has no hook and trades in ordinary DEX pools.

IDLs and other languages

Everything here is plain Solana: Anchor accounts, Anchor instructions and Anchor event CPIs. The IDL of each program:

Their licence and notice files are at /idl/LICENSE.txt and /idl/NOTICE.txt.

  • Accounts: an 8-byte discriminator, then Borsh. Sizes and filter offsets are in Account layouts.
  • Instructions: an 8-byte discriminator, then Borsh arguments. Account order is the IDL's. An optional account that is absent is passed as the program's own id. A hook's accounts follow as remaining accounts.
  • Events: inner instructions to the program's event authority whose data is e445a52e51cb9a1d, then the event discriminator, then Borsh.

Any Anchor client can use them: anchorpy, anchor-go, the Rust anchor-client, or @coral-xyz/anchor in TypeScript.

Building something on the standard, or stuck? Reach us on X at @hookedpad.