Skip to content
helixLaunch

Docs

Token program

Mints, holdings and the token instructions

A HELIX token is a Mint account; each owner's balance of it is a Holding. Both are accounts of the HELIX program, laid out at fixed offsets. Every transfer, mint and burn runs through the mint's hook, if it has one, and logs an event.

Every HELIX account starts with a one-byte kind. Fields are packed little-endian with no padding beyond what the layout declares, so the SDK's decoders (decodeMint, decodeHolding, …) read them at fixed offsets. The token standard uses the mint and the holding; the DEX, the launchpad and the bridge use the rest.

AccountAddressBytesKind
Config["config"]2401
MintA keypair account that signs its creation; for a bridged-in token, ["wrapped", spl_mint]4482
Holding["holding", mint, owner]1843
Pool["pool", mint]2164
Launch["launch", mint]3205
Position["position", pool, owner]966
Wrapper["wrapper", spl_mint]1927
Mirror["mirror", helix_mint]1608
PDAs are derived from the HELIX program id. The SDK has one helper per address: configAddress, holdingAddress, poolAddress, launchAddress, positionAddress, wrappedMintAddress, wrapperAddress, mirrorAddress.

The mint keeps its supply, its four authorities, its hook and its metadata. There is no separate metadata account: name, symbol and URI are on the mint.

FieldOffsetTypeMeaning
decimals1u8At most 18. Launches use 6.
flags2u81: the built-in kit is the hook. 2: created by a launch.
hook_flags6u16Which callbacks run and what they may answer (see flags). Zero when there is no hook.
supply8u64Tokens in existence, in base units.
max_supply16u64Zero: no cap. Otherwise mint_to refuses to pass it.
mint_authority24addressMay mint. Zero: nobody, for good.
freeze_authority56addressMay freeze and thaw holdings. Zero: nobody.
hook_authority88addressMay change the hook and its flags. Zero: nobody.
metadata_authority120addressMay change name, symbol and URI. Zero: nobody.
hook_program152addressZero: no hook. The HELIX program id: the built-in kit (launches only). Anything else: an external hook.
created_at184i64Unix time of creation.
name200[u8; 32]UTF-8, length in byte 3.
symbol232[u8; 16]UTF-8, length in byte 4.
uri248[u8; 200]UTF-8, length (u16) at offset 192.

One owner's balance of one mint, at the PDA ["holding", mint, owner]. There is exactly one per pair, so there is nothing like an “associated” account to look up. Anyone can create it for anyone, paying its rent: about 0.00217 SOL (2,171,520 lamports for 184 bytes at Solana's current rate), returned by close_holding.

FieldOffsetTypeMeaning
frozen2u8Non-zero: the holding can neither send nor receive, nor be credited a hook’s cut.
mint8addressThe token.
owner40addressThe wallet, or a pool for a pool’s own holding.
amount72u64Balance in base units.
delegate80addressOne delegate at a time. Zero: none.
delegated_amount112u64What the delegate may still move or burn.
hook_data120[u8; 64]Belongs to the mint's hook; only the hook can change it (see hook data).

The first byte of instruction data is the tag; the arguments follow, little-endian. (s) marks a signer, (w) a writable account. …hook is the mint's hook accounts: nothing for a mint without a hook, [launch] for the built-in kit, and [hook_program, hook_signer, …extras] for an external hook. The SDK's hookAccounts() builds them.

TagInstruction and accountsWhat it does
10create_mintpayer (s, w), mint (s, w), systemCreates a mint at a fresh keypair address. Arguments: decimals, hook flags, max supply, the four authorities, hook program, name, symbol, URI.
11create_holdingpayer (s, w), holding (w), mint, owner, systemCreates owner’s holding. Idempotent: if it already exists for that mint and owner, it does nothing, so it is safe to prepend to any transaction.
12transfer(amount)authority (s), source (w), destination (w), mint, …hookMoves tokens through the hook. The authority is the owner or its delegate. Context: Plain.
13mint_to(amount)mint_authority (s), mint (w), destination (w), …hookMints into a holding through the hook, up to the max supply.
14burn(amount)authority (s), holding (w), mint (w), …hookBurns from a holding through the hook. Owner or delegate.
15approve(amount)owner (s), holding (w), delegateSets the delegate and its allowance, replacing any earlier one.
16revokeowner (s), holding (w)Clears the delegate.
17set_frozen(frozen)freeze_authority (s), mint, holding (w)Freezes or thaws a holding.
18close_holdingowner (s), holding (w), destination (w), mintCloses an empty holding and sends its rent to destination.
19set_authority(kind, new)current (s), mint (w)Hands an authority over. kind: 0 mint, 1 freeze, 2 hook, 3 metadata. A zero address revokes it for good.
20set_hook(program, flags)hook_authority (s), mint (w)Changes the hook and its flags. A zero program removes the hook.
21update_metadata(name, symbol, uri)metadata_authority (s), mint (w)Rewrites the metadata.
22write_hook_data(data)hook_self (s), mint, holding (w)Lets the mint's external hook write a holding's 64 bytes outside a callback (see Hooks).

Rules worth knowing

  • create_mint refuses more than 18 decimals, unknown flag bits, and the HELIX program id as hook: the built-in kit comes only with a launch. With no hook program, the flags are stored as zero.
  • Name, symbol and URI are at most 32, 16 and 200 bytes. A launch also refuses an empty name or symbol.
  • create_holding works even if someone sent lamports to the holding's address in advance, so nobody can block a holding from being created.
  • close_holding needs a zero balance. On a kit token it also needs no unclaimed rewards; on a token whose hook writes hook data, the 64 bytes must be all zero.
  • A transfer to the same holding is refused; so is an amount of zero.
  • A delegate's transfers and burns come out of its allowance; a transfer that uses it up clears the delegate.

The DEX and launchpad instructions are on DEX and Launches; the admin's on Security.

  1. HELIX checks that source and destination differ, that the amount isn't zero, that both holdings belong to this mint and neither is frozen, and that the source holds the amount (and a delegate its allowance).
  2. If the mint's flags ask for it, HELIX calls before_transfer. The hook may refuse (the whole transaction fails) or answer with cuts and new hook data.
  3. The source loses the full amount. The destination gains the amount less the cuts; each cut is credited to the holding the hook named. Hook data from the answer is written.
  4. If the flags ask for it, HELIX calls after_transfer with the balances after the move and delta set to the cuts' total.
  5. HELIX logs a Transfer event with the amount, the cuts and the context.

Mints and burns follow the same pattern with before_mint/after_mint and before_burn/after_burn; they can't answer cuts. For a mint the mint account stands in for the source; for a burn, for the destination.

Each mint has four authorities: mint, freeze, hook and metadata. Each can be handed over with set_authority, or revoked by setting it to zero. A revoked authority can never be set again: set_authority, mint_to, set_frozen, set_hook and update_metadata all refuse with NoAuthority.

HELIX logs an event as program log data: one Program data: <base64> line per event, whose first byte is the kind and whose fields follow at fixed widths (addresses 32 bytes, amounts u64, times i64). parseEvents(logs) in the SDK decodes them, and keeps only data logged while HELIX itself was executing, so a hook can't forge a HELIX event from inside a callback.

KindEventFieldsLogged by
1Transfermint, from owner, to owner, authority, amount, cut, contextevery transfer, swaps and liquidity included
2MintTomint, to owner, amountmint_to, create_launch
3Burnmint, from owner, amountburn, the burn rule of a trade, graduation
4Swapmint, trader, side, SOL, tokens, protocol fee, creator fee, holder fee, sniper fee, burned, SOL reserve, token reserve, mode, timeswap
5LaunchCreatedmint, creator, pool, hook program, rule bits, the six fee and wallet rules, creator unlock, early window end, early unlock, timecreate_launch
6Graduatedmint, AMM SOL, AMM tokens, tokens burned, timethe buy that sells the curve out
7PoolCreatedmint, creator, tokens, SOL, LPcreate_pool
8Liquiditymint, owner, removed, SOL, tokens, LPadd_liquidity, remove_liquidity
9RewardsClaimedmint, owner, lamportsclaim_rewards
10Sharedmint, from, lamportsshare_with_holders
11CreatorClaimedmint, creator, lamportsclaim_creator_fees
12ProtocolCollectedmint, lamportscollect_protocol_fees
13MintCreatedmint, payer, hook programcreate_mint
14ConfigUpdatedbyinit_config, update_config, accept_admin
In the Swap event, SOL is what the trader paid (a buy) or received (a sell); tokens are what the trader received (a buy) or sent (a sell). The reserves are the price reserves after the trade: virtual on the curve, real on the AMM.
Program data: <base64( [kind u8] [field] [field] … )>

HELIX fails with a custom program error from 6000 up. explainFailure(logs) turns a failed transaction's logs into the name and a sentence; errorName(code) gives the name alone.

All 46 errors
CodeNameMeaning
6000InvalidInstructionThe instruction data or accounts are malformed.
6001InvalidAccountOwnerAn account is not owned by HELIX.
6002InvalidAccountKindAn account is not the kind of HELIX account expected.
6003InvalidAccountDataAn account is too small to be a HELIX account.
6004InvalidAddressAn account is not at the address it should be.
6005MissingSignatureA required signature is missing.
6006NotWritableAn account that must be writable is read-only.
6007UnauthorizedThe signer has no authority here.
6008AlreadyInitializedThe account already exists.
6009MathOverflowA number overflowed.
6010MintMismatchA holding belongs to another token.
6011OwnerMismatchA holding belongs to someone else.
6012InsufficientFundsNot enough tokens or SOL.
6013HoldingFrozenThe holding is frozen.
6014MaxSupplyExceededThe mint would pass its maximum supply.
6015NoAuthorityThat authority has been revoked.
6016SameHoldingSource and destination are the same account.
6017HoldingNotEmptyThe holding still has tokens.
6018HookDataNotEmptyThe holding still keeps hook state (rewards owed).
6019InvalidNameA name, symbol or link is too long or empty.
6020HookProgramMismatchThe hook program passed is not the mint's hook.
6021HookSignerMismatchThe hook signer is not HELIX's signer for that hook.
6022HookAccountsMissingThe hook's accounts are missing.
6023UnsupportedHookReturnThe hook answered something its flags do not allow.
6024DeltaTooLargeThe hook's cuts add up to more than the amount.
6025InvalidDeltaAccountA hook cut names an account it may not credit.
6026KitOnlyThroughLaunchThe built-in kit is installed only by a launch.
6027PausedThis is paused right now (sells and withdrawals never are).
6028PoolMismatchThe pool does not belong to this token.
6029SlippageExceededThe price moved past your slippage limit.
6030ZeroAmountThe amount is zero.
6031InsufficientLiquidityThe pool does not have enough liquidity.
6032CurveLockedThe pool is still a bonding curve; liquidity opens at graduation.
6033PoolExistsThis token already has a pool.
6034LaunchMismatchThe launch does not belong to this token.
6035RuleOutOfBoundsA launch rule is outside its bounds.
6036CreatorLockedThe creator's wallet is locked until its unlock time.
6037EarlyBuyerLockedTokens bought in the first seconds are locked until their unlock time.
6038MaxWalletExceededThis would put the wallet above the max-wallet cap.
6039NothingToClaimThere is nothing to claim.
6040ShareTooSmallA share must be at least 0.001 SOL.
6041FeeCollectorMismatchThe fee collector is not the one in the config.
6042ConfigOutOfBoundsA config value is outside its ceiling.
6043UnsupportedMintThis token has an extension the bridge refuses (it could freeze or seize the vault), or it is already bridged.
6044BridgeNotReadyA launch can go out through the bridge once it has graduated.
6045BridgeMismatchThe bridge accounts do not belong to this token.