Stoke

Hooks for your coin

Stoke v3: a program a creator adds to their coin at launch. What a hook can do, the ones available, and how to write one.

A hook is a small program a creator adds to their coin when they launch it. Stoke calls it before every trade of that coin, on the curve and in the pool. This is Stoke v3: anyone can write a hook, and creators add one to a coin from the list Stoke makes available.

What a hook can do

  • Set a trade's fee, anywhere from 0 to 10% of its SOL side. The fee splits like every Stoke fee: 30% to the creator, 30% to holders, 40% into the coin's reserves.
  • Refuse a trade.

That is all. A hook never holds or moves anyone's money, can't touch a coin's liquidity or change where fees go, and never receives a wallet's signature. The program enforces this: a hook gets one account when it's called, and it can't write to it.

The hooks available

HookWhat it doesProgram
Launch taxA higher fee at launch that falls to your coin's own fee.3zbuMvvfy73QL6U8hsceeZxCpju2mDNed3YaQfTcdtjS
Max buyNo single buy above a size you set, for the coin's first minutes.AXDdW1eY1YcxrxsrR6wGnj45hAFNtGtHrXCRsxZzKMqo

Launch tax

  • The creator sets it for buys, sells or both: where the fee starts (above the coin's own, up to 10%) and how long it takes to fall (1 minute to 7 days).
  • It falls in a straight line to the coin's own fee. A 10% sell tax over 60 minutes is 5.5% at 30 minutes on a 1% coin. After that the hook changes nothing.
  • The creator's dev buy, in the launch transaction, is exempt. Their later trades pay like anyone's.

Max buy

  • For the coin's first minutes (1 minute to 24 hours), no single buy can be larger than the size the creator set (0.05 SOL or more).
  • It applies to everyone, the creator's dev buy included.
  • It caps one buy, not a wallet: someone can still buy more than once. Sells are never limited.
  • While the cap lasts, a buy has to spend an exact SOL amount, which is how every app buys.

Adding a hook to your coin

  • Pick it under Hook on the launch form and set it up. It goes on in the launch transaction, before the first trade.
  • It can't be changed, swapped or removed afterwards, by you or by Stoke. Nobody ever buys a coin whose hook could still change.
  • One hook per coin. For now a coin launches with a hook or with fee zones, not both: together they don't fit in one launch transaction.
  • The hook checks your settings itself, in the launch transaction. If it refuses them, the coin doesn't launch.

Trading a coin with a hook

A coin's page shows its hook, the creator's settings in plain words, and what it does to a trade right now. Quotes on the site and in the open API include it.

  • A fee the hook sets replaces the coin's own fee for that trade. It applies to everyone, the creator included: their fee-free sells don't apply to it.
  • If a sell is also inside a fee zone, it pays the higher of the two.
  • In a coin's first second, a hook's fee on a buy stops where the anti-snipe tax would leave the buyer nothing.
A hook can refuse trades, so a bad one could stop a coin from being sold. That's why creators can only add hooks Stoke has listed, and why Stoke can pause a listed hook: its coins then trade as if they had no hook, at their normal fee, until it's unpaused. Stoke can't change a coin's hook or its settings. Like the rest of Stoke's programs, the hooks haven't had a professional audit and their admin can still upgrade them.

Writing a hook

A hook is any Solana program with two instructions. Both are Anchor-compatible: an 8-byte discriminator (the first 8 bytes of sha256("global:on_attach") or sha256("global:on_trade")), then the Borsh-encoded argument. The types, and helpers to decode them without Anchor, are in protocol/crates/meridian-core/src/coin_hook.rs.

InstructionWhenWhat it does
on_attach(AttachArgs)Once, when a creator adds the hookChecks the creator's settings. An error fails the launch.
on_trade(TradeArgs) → TradeReturnBefore every trade of the coinReturns nothing to leave the trade as it is, a fee to set it, or an error to refuse it.
FieldMeaning
coinThe coin: mint, creator, its own fee (bps), and when it launched (unix seconds and slot).
configThe creator's settings for this coin, up to 64 bytes, exactly as they gave them at launch.
traderThe wallet trading.
is_buy, exact_inThe side, and whether the trader fixed what goes in or what comes out.
sol, tokensThe trade's size before fees, in lamports and token units. The side the trader fixed is exact; the other is valued at the price before the trade.
graduatedFalse on the curve, true in the pool.
mcapThe coin's market cap in lamports, before the trade.
now, slotThe cluster's clock.

TradeReturn is one field, fee_bps: Option<u16>. Anything above 1000 is cut to 1000.

A whole hook: no buy above 1 SOL in a coin's first 10 minutes
use anchor_lang::prelude::Pubkey;
use anchor_lang::solana_program::{account_info::AccountInfo, entrypoint::ProgramResult,
    program::set_return_data, program_error::ProgramError};
use meridian_core::coin_hook::{discriminator, verify_coin_hook_call, TradeArgs, TradeReturn};

anchor_lang::solana_program::entrypoint!(process);

pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
    if data.len() < 8 || accounts.is_empty() {
        return Err(ProgramError::InvalidInstructionData);
    }
    // Only Stoke Launch can call a hook: the one account is this hook's listing, and it signed.
    verify_coin_hook_call(program_id, &accounts[0])?;
    let (disc, body) = data.split_at(8);
    if disc == discriminator::ON_ATTACH {
        return Ok(()); // no settings to check
    }
    let t = TradeArgs::parse(body).ok_or(ProgramError::InvalidInstructionData)?;
    if t.is_buy && t.age() < 600 && t.sol > 1_000_000_000 {
        return Err(ProgramError::Custom(1)); // refuse the trade
    }
    let (ret, len) = TradeReturn::default().encode(); // leave it as it is
    set_return_data(&ret[..len]);
    Ok(())
}
  • One account. A hook is called with a single account: its own listing, a Stoke Launch account that only Stoke Launch can sign with. verify_coin_hook_call checks it, so nobody else can call your hook. Hooks are stateless in this version: everything they need arrives in the arguments.
  • Stay small and cheap. A hook runs inside every trade of its coins. The first two hooks are about 20 KB each and add about 13,000 compute units to a trade.
  • Never fail by accident. An error from on_trade refuses the trade. Check settings in on_attach, and treat anything unexpected in on_trade as "leave the trade as it is".
  • Getting listed. Send the source to @use_stoke on X. A hook is listed once Stoke has read it, built it and tested it, and its settings can be shown to traders in plain words. The program has to be immutable, or upgradeable by Stoke's admin only: the launch program refuses to list one whose code someone else could still change.

On-chain

Everything lives in Stoke Launch (CAcC7pz43rLg14jAE8iR3w3sgXcd2GGtoeFWUBB1SGVq).

AccountSeedsHolds
Hook listing["hook-listing", hook program]A hook creators can add: its program, whether it's paused, and how many coins launched with it.
Coin hook["coin-hook", launch]A coin's hook: the program, its listing, and the creator's settings.
InstructionWhoDoes
attach_hookCreatorAdds a listed hook to the coin with their settings. In the launch transaction only, before any trade.
list_hookAdminMakes a hook program available to creators. Refused unless the program is immutable or upgradeable by the admin only.
set_hook_pausedAdminPauses a listed hook (its coins trade without it) or unpauses it.

A coin with a hook publishes three more accounts in its pool's hook account list (["hook-accounts", pool]): its coin hook account (writable), the hook's listing and the hook program. Every swap of it has to pass them, in any order after the launch account, or it fails with HookAccountsMissing. Clients that already read that list, as Stoke v1 for terminals describes, need no change. Events: HookListed, HookPaused, HookAttached.

One rule for anyone trading a hooked coin's pool directly: use the amount limit for slippage, not the price limit. When the trader fixes the SOL side, the fee is taken up front on all of it, so a swap its price limit cuts short fails with SwapCutShort instead of paying a fee on SOL that never traded.