SDK
Build on the standard
The TypeScript SDK builds every instruction, decodes every account and event, and resolves a hook's accounts. The hook interface is open: anyone can write one.
Install#
npm install https://mizoapp.xyz/sdk.tgzVersion 0.5.0 of both, on npm; the IDLs ship inside the package. New to the standard? Start with why this standard.
For your coding agent#
You are helping me build on a token standard of its own on Solana: not Token-2022, not SPL.
Tokens live in its token program, which calls a mint's hook before and after every transfer, mint
and burn and applies what the hook answers; its DEX does the same with pool hooks on every swap.
INSTALL (published on npm; the IDLs ship inside the package):
npm install https://mizoapp.xyz/sdk.tgz
IMPORTS (real export names):
import { token, swap, bridge, launch, kit } from 'mizo-sdk'; // instruction builders
import { holdingAddress, poolAddress, launchAddress, registryAddress } from 'mizo-sdk'; // PDAs
import { decodeMint, decodeHolding, decodePool, decodeLaunch, fetchAccount } from 'mizo-sdk'; // decoders
import { eventsOf, typedEvent, explainFailure } from 'mizo-sdk'; // events, errors
import { fetchTokenHook, tokenHookOf, resolveHookAccounts, kitTokenHook } from 'mizo-sdk'; // hook accounts
import { buildV0Transaction, protocolLookupTable } from 'mizo-sdk'; // v0 transactions
import { buildCreateConfig, inspectConfig, taxHook, TAX_HOOK_FLAGS } from 'mizo-sdk'; // build your own
import { companion, companionAddress, companionCreatorAddress, decodeCompanion, companionVested,
COMPANION_TEMPLATES, COMPANION_DEFAULTS, companionReady } from 'mizo-sdk'; // companions
import { PROGRAM_IDS, TOKEN_HOOK_FLAGS, POOL_HOOK_FLAGS } from 'mizo-sdk/shared';
PROGRAM IDS (one set: the same on mainnet, devnet and localnet):
token 2XoEWp8cF3kRXg74eVwPAyTFhVCAztn3V88komxAvr22
DEX GyzKSnnEu2uN5bBRecE4XYY2enbfR2D2MtxnbJPGy7hk
bridge CtLkuFVitoXHTa86Hfp8KmfSDfqJaMYFWr6EGmQVsKb7
launchpad 1jcBymHxBjniZDhNPy51Vgm5Nz7pLUdxa9UBHc4TavC
kit 14RJQXPdJfkehit6ezktjd3xujamf8nVSKw2shKamaEH
tax_hook 8tjnVSreJGBRQFyDBf1SyyhBgLsdBxa2rHYh9sbxFyX7
companion 6ZUM1gWBH9hBBNoJoaVAGwSftyZ6CUda6vUZTW9MsJuo
CLUSTER: the programs are live on mainnet; the RPC from SOLANA_RPC_URL (any Solana RPC, e.g. Helius).
HOOKS: a hook is an ordinary Solana program written against the mizo-hook crate (download: /downloads/mizo-hook.zip).
A token hook implements before_/after_ transfer, mint, burn; a pool hook before_/after_ initialize, add_liquidity,
remove_liquidity, swap. A before_* callback (and a pool's after_swap) returns, in its return data:
pub struct HookReturn {
pub deltas: Vec<Delta>, // up to 3 cuts { amount: u64, account: u8 } from the amount
pub burn: u64, // pool swap callbacks only
pub lp_fee_bps: Option<u16>, // before_swap only
pub source_hook_data: Option<[u8; 64]>, // token callbacks only: the holding's 64 bytes of hook state
pub destination_hook_data: Option<[u8; 64]>,
}
A mint's hook_flags (TOKEN_HOOK_FLAGS) say which callbacks run and what they may answer; Pool.hook_flags likewise.
REGISTRY: extra accounts a hook needs are published at registryAddress(hook_program, mint-or-pool)
as a HookAccountList of fixed keys and PDAs; the SDK resolves it (fetchTokenHook) and appends them after the 5 fixed
accounts (hook signer, mint, source, destination, authority).
RULE: a hook never gets the user's signature. It answers, and the token program (or the DEX) checks and applies the
answer, never more than the amount being moved, signing every call with PDA(["hook-authority", hook_program]).
PUT A HOOK ON A MINT: token.createMint(payer, mint, { ..., hookProgram, hookFlags, hookAuthority }) names it;
token.setHook changes it while a hook authority is held; token.setAuthority(authority, mint, 'hook', null) locks it.
Every transfer of a hooked mint takes the hook's accounts: token.transfer(auth, src, dst, mint, amount, hook).
LAUNCH IT ON THE LAUNCHPAD (build your own): the rules travel in a LaunchConfig account made once with
buildCreateConfig(creator, { rules, creatorFeeBps, customHook, customHookFlags, label }) from 'mizo-sdk'
(send its instruction signed by the creator and its keypair; its address is pasted into the launch page under
"Custom config"). The launch creates the mint, so prepare the hook for the mint address the page shows first:
its registry at registryAddress(hook, mint) (taxHook.prepare is the worked example). A config
with a hook may have burn and a creator fee, not holder rewards, max wallet or the locks (one token hook per
mint). inspectConfig(connection, config, mint) runs the page's checks. The protocol takes 25% of what a launch's
rules collect on each swap (creator fee, holder rewards, your hook's cuts), in SOL; a config can't change that.
The site labels such a token "Custom hook, unverified".
UPGRADE AUTHORITY (required to launch from a config): the hook program MUST be immutable, or upgradeable only by
the protocol (CS1NRyXNCPxEUP4CRoa26cHQSeSJCxXh5SPijwFhDW6W or 5xsibKwtiN6ruxsYrEyWVpV3KcwuzSPbQd1n28a7spEd). The launch program's create_config refuses any other hook
(HookUpgradeable). Right after deploying your own hook, make it immutable (signed by its current upgrade authority):
solana program set-upgrade-authority <PROGRAM_ID> --final
A config made before that rule whose hook someone else can upgrade can't launch either: the launch page says
"This config’s hook can be upgraded by someone outside the protocol, so it can’t launch here.". Check it
before making the config: `solana program show <PROGRAM_ID>` shows the Authority (inspectTokenHook and
inspectConfig say so too). Do not keep the
authority yourself.
COMPANIONS (live on mainnet): a launch whose creator is a program. The companion creates the launch and signs as
its creator, companionCreatorAddress(mint) = PDA(["creator", mint], companion), so every creator fee lands with it
and only its code spends it, by a split fixed at create. Its account: companionAddress(mint) = PDA(["companion", mint]).
THE SPLIT of every creator fee claim: { buybackBps, holdersBps, beneficiaryBps }, whole basis points summing to 10,000,
any mix (bought back and burned / streamed to holders through the kit / paid to the launcher in SOL). The launch page
calls it "where the creator fee goes": You (no companion), Buy back & burn, Holders, or a Split.
TEMPLATES (COMPANION_TEMPLATES; preset splits, accepted by name too):
buysItself buyback+burn 100% / holders 0% / launcher 0%; no dev, no first buy
rugProofDev buyback+burn 0% / holders 50% / launcher 50%; first buy vests to the launcher; needs holder rewards on
buybackAndReward buyback+burn 50% / holders 50% / launcher 0%; first buy vests to the launcher; needs holder rewards on
LAUNCH WITH ONE: with the SDK builders below (WITH THE BUILDERS): set up the companion, launch, first buy,
each signed by the wallet and the mint keypair.
RULES: the shares sum to 10,000; a creator fee above 0; no creator
wallet lock (creatorLockDays 0: the companion vests the first buy instead); holder rewards on when holdersBps > 0 (and
then the launching wallet must be a plain wallet); no launch config and no custom hook (inline rules only: the
companion program refuses a custom hook, CustomHookUnsupported). The first buy is capped by max wallet; a split that buys back everything has no dev, so send no first buy. The
beneficiary is the launching wallet.
KEEP THE MINT KEYPAIR (write it to disk before signing) until the launch transaction confirms. If "Set up the
companion" landed and the launch didn't, only that key can launch it or take its SOL back: companion.refund(sender, mint, beneficiary), the mint
signing, takes its SOL back; then launch with a fresh mint.
WITH THE BUILDERS: companion.create(payer, beneficiary, mint, { split (any, or COMPANION_TEMPLATES.<template>),
...COMPANION_DEFAULTS, fund }) signed by payer and mint (fund = the LaunchConfig's launchFeeLamports + 50_000_000
rent + 890_880); then companion.launch(launcher, mint, launch.createLaunch(companionCreatorAddress(mint), mint,
config.treasury, config.quoteMint, config.lpFeeBps, args), args), signed by the launcher and the mint, as a v0
transaction with a lookup table holding PROTOCOL_LOOKUP_TABLE in order (the protocol keeps one on mainnet:
4vxVcYLdkqT1rMfGHjAu4kMa9XSEhUdQU5ThVfU5grGQ; companionReady(table) must be true; it
doesn't fit without); then optionally companion.devBuy(beneficiary, launchKeys(...), lamports, minOut) within 10 minutes.
CRANK ANY COMPANION (permissionless; each step checks on chain that it is due and pays its sender bountyBps of what
it moves, 0.5% by default (COMPANION_DEFAULTS), at most 1%): companion.claimFees(cranker, mint) (creator fees in,
split), companion.buyback(cranker, keys, rewards) (buys on the pool and burns; at most maxBuyback and 1% of the
pool's SOL, buybackInterval apart, waits while the price is over 3% above its reference), companion.share(cranker,
mint) (holders' part into the kit), companion.withdraw(sender, mint, beneficiary) (launcher's part, in SOL),
companion.release(cranker, keys, rewards, beneficiary) (vested first buy). keys = launchKeysOf(decodeLaunch(...)),
rewards = rewardsOn(launchRulesInputOf(launch.rules)). One step a transaction, claimFees first.
READ ONE: decodeCompanion(data) gives split, pendingBuyback, pendingHolders, pendingBeneficiary, lastBuybackAt,
burnedTotal, sharedTotal, paidBeneficiaryTotal, devTokens, devReleased; companionVested(c, now) - c.devReleased is
what release sends now.
With the builders any split summing to 10,000 bps works (the token page then
names no template). A companion program of your own isn't possible yet: Studio can't write one.
STATUS: the programs are live on mainnet and keep an upgrade authority until audited; the SDK is on npm (0.5.0).What’s in it#
import {
token, swap, bridge, launch, kit, // instruction builders, one object per program
holdingAddress, poolAddress, launchAddress, // every PDA
decodeMint, decodeHolding, decodePool, // typed accounts from the IDL coders
eventsOf, typedEvent, // events from a transaction's inner instructions
fetchTokenHook, kitTokenHook, // a hook's accounts, resolved for one operation
buildV0Transaction, protocolLookupTable, // v0 transactions with the protocol table
explainFailure, // a failed transaction, in words
companion, decodeCompanion, // companions: launch one, crank one, read one
} from 'mizo-sdk';| Module | Gives |
|---|---|
instructions.ts | Builders for every program: token, swap, bridge, launch, kit, tax_hook, in each program’s account order. |
addresses.ts | Program ids and every PDA; the tests hold each fixed one to the value compiled into the programs. |
accounts.ts | Typed accounts: mints, holdings, pools, launches, the kit’s config. Integers as bigint. |
hooks.ts | A hook’s registry: decode, encode, and resolve it for one operation, as the Rust does. |
events.ts | Events from a transaction’s inner instructions, in execution order. |
errors.ts | Program errors in words, keyed by the program that failed. |
transactions.ts | v0 transactions with the protocol lookup table: create_launch with kit rules only fits with it (1,042 bytes, 1,366 without). |
inspect.ts | A LaunchConfig read and checked as create_launch checks it, and buildCreateConfig to make one. |
companion.ts | Companions: create, launch, the dev buy, every permissionless step, the refund, and the account decoded. |
coders.ts | Borsh coders from the programs’ IDLs: no layout is written by hand. |
Status#
- Package
- mizo-sdk 0.5.0, on npm.
- Programs
- Live on mainnet, one set of ids on every cluster; upgradeable until audited (who holds each key).
- Your hook
- Goes on a mint you create, or launches from a config once nobody outside the protocol can upgrade it (deploying it). Its code isn’t reviewed: the site says so wherever the token shows.
- Companions
- Live on mainnet: launch with one, or crank any (companions).
- Tested
- Every builder against the IDLs, v0 sizes to the byte, layouts, events and errors through the coders, and the hook crate’s checks.