Skip to content

Search every page of the documentation, including the program reference and error codes.

↑ ↓ to move · Enter to open

Contents · §7 Program reference

§7 · Building

Program reference

Every instruction, account, event and error in the Cohort program, generated from its IDL at build time.

Program
6uRisJz3Naz2cRmXmDWkCHtoGdNWnx6Hg42rxRy5aqeD(declare_id; see the devnet page for deployments)
IDL
sdk/src/idl/cohort.json · cohort 0.1.0 · Anchor 0.29
IDL sha256
fe30587d044e0a99…

This page is generated by scripts/docs-build.mjs before every build: accounts, arguments, layouts and error codes come straight from the IDL. The summaries and invariants are notes checked against the IDL by a test, so an instruction or error the program gains without a note fails npm test. Amounts are raw base units; cohorts have 9 decimals. For the reasoning behind the rules, read §2 How it works first.

Instructions

14 instructions: initialize_config, set_wraps_paused, add_issuer, remove_issuer, set_pod_verified, create_pod, wrap, unwrap, unwrap_partial, redeem_claim, distribute_fees, reconcile, set_creator_share, set_lookup_table.

initialize_configSDK: initializeConfig

Creates the one ProtocolConfig account: admin, treasury and the protocol's share of fees for pods created afterwards.

Signer · The program's upgrade authority, once. The program and programData accounts prove it, so nobody can front-run the initialisation.

Accounts

initialize_config accounts
#AccountNotes
0payermutsignerMust be the program's upgrade authority (prevents front-running the init).
1configmut
2program
3program_data
4system_program

Arguments

initialize_config arguments
ArgumentType
adminPubkey
treasuryPubkey
protocol_share_bpsu16

Checks and invariants

  • protocol_share_bps ≤ 5,000.
  • max_components is set to 10 and wraps_paused to false.

Program errors

6001 Unauthorized6007 ProtocolShareTooHigh

set_wraps_pausedSDK: setWrapsPaused

Blocks or unblocks new wraps and new pods, protocol-wide.

Signer · config.admin.

Accounts

set_wraps_paused accounts
#AccountNotes
0adminsigner
1configmut

Arguments

set_wraps_paused arguments
ArgumentType
pausedbool

Checks and invariants

  • Has no effect on unwrap, unwrap_partial or redeem_claim. Those instructions don't take the config account at all.

Program errors

6001 Unauthorized

add_issuerSDK: addIssuer

Adds a key (a freeze authority, permanent delegate, pause authority, transfer-hook authority or hook program) to the issuer allowlist.

Signer · config.admin, who pays rent for the new account.

Accounts

add_issuer accounts
#AccountNotes
0adminmutsigner
1config
2issuermut
3system_program

Arguments

add_issuer arguments
ArgumentType
keyPubkey
labelstring

Checks and invariants

  • label is at most 32 bytes.
  • One account per key: adding the same key twice fails at account creation.

Program errors

6001 Unauthorized6010 LabelTooLong

remove_issuerSDK: removeIssuer

Closes an allowlist entry and returns its rent to the admin.

Signer · config.admin.

Accounts

remove_issuer accounts
#AccountNotes
0adminmutsigner
1config
2issuermut

Arguments

None.

Checks and invariants

  • Only future create_pod calls are affected. Existing pods keep working, and unwraps never consult the allowlist.

Program errors

6001 Unauthorized

set_pod_verifiedSDK: setPodVerified

Sets or clears a pod's verified flag.

Signer · config.admin.

Accounts

set_pod_verified accounts
#AccountNotes
0adminsigner
1config
2podmut

Arguments

set_pod_verified arguments
ArgumentType
verifiedbool

Checks and invariants

  • A curation label only. It changes nothing about how the pod wraps, unwraps or pays fees.

Program errors

6001 Unauthorized

create_podSDK: createPod

Creates a pod: validates every component mint, creates the clean Token-2022 pod mint and its vaults, and performs the fee-free genesis wrap.

Signer · Anyone (the creator). The pod mint is a fresh keypair that signs.

Accounts

create_pod accounts
#AccountNotes
0creatormutsigner
1configmut
2podmut
3pod_mintmutsignerNew Token-2022 mint keypair; created and initialised here.
4fee_vaultmut
5creator_pod_accountmut
6pod_token_program
7associated_token_program
8system_program

Remaining accounts

  1. Per component, 4 accounts: mint, vault (the pod's associated token account for that mint), creator's source token account, token program.
  2. Then transfer-hook extra accounts, hook_extras[i] of them for component i.
  3. Then any number of IssuerAllowlist accounts covering the components' powers.

Arguments

create_pod arguments
ArgumentType
argsCreatePodArgs

Checks and invariants

  • 2 to 10 components, no duplicates, and none of them the pod mint itself.
  • wrap_fee_bps and unwrap_fee_bps ≤ 200; creator_share_bps ≤ 1,000; burn share = 10,000 − protocol share − creator share.
  • Every recipe unit is non-zero and genesis_amount ≥ 1 whole cohort (10⁹ raw).
  • Each component mint is owned by SPL Token or Token-2022, has no TransferFee and no extension outside the allowed set, and is not paused.
  • Every freeze authority, permanent delegate, pause authority, hook authority and hook program on a component is on the allowlist. The mint authority is not checked.
  • Each vault is the canonical associated token account, owned by the pod, not frozen, with no delegate and no close authority.
  • The pod mint has MetadataPointer and TokenMetadata only, 9 decimals, mint authority = the Pod PDA and no freeze authority.
  • Genesis deposit per component = ceil(genesis_amount × recipe_units ÷ 10⁹). The creator receives genesis_amount cohorts with no fee.
  • The protocol share in the config at this moment is copied into the pod and never changes for it. The pod starts unverified.
  • Blocked while wraps_paused is set.

Program errors

6002 WrapsPaused6003 InvalidComponentCount6004 FeeTooHigh6005 CreatorShareTooHigh6006 InvalidFeeSplit6009 MetadataTooLong6011 ZeroRecipeUnits6012 GenesisTooSmall6014 LengthMismatch6015 MissingAccounts6042 InvalidAllowlistAccount6018 InvalidTokenProgram6017 DuplicateComponent6019 InvalidMint6021 TransferFeeNotAllowed6022 DisallowedExtension6023 IssuerNotAllowlisted6026 ComponentPaused6024 InvalidVault6025 VaultFrozen6020 InvalidTokenAccount6013 ZeroAmount6030 UnexpectedTransferAmount6000 MathOverflow

wrapSDK: wrap

Deposits every component pro-rata and mints pod_amount cohorts gross: the fee to the FeeVault, the rest to the user.

Signer · Anyone holding the components.

Accounts

wrap accounts
#AccountNotes
0usermutsigner
1config
2podmut
3pod_mintmut
4fee_vaultmut
5user_pod_accountmut
6pod_token_program

Remaining accounts

  1. Per component, 4 accounts: mint, vault, user's source token account, token program.
  2. Then transfer-hook extras, hook_extras[i] for component i (an empty hook_extras means none).

Arguments

wrap arguments
ArgumentType
pod_amountu64
max_inVec<u64>
hook_extrasbytes

Checks and invariants

  • Before pricing, each component is synced: if the vault holds less than tracked_balance + claims_outstanding, the gap is recorded as shortfall.
  • amount_in[i] = ceil(pod_amount × tracked_balance[i] ÷ supply), or ceil(pod_amount × recipe_units[i] ÷ 10⁹) when supply is 0. It must be ≤ max_in[i].
  • The vault must grow by exactly amount_in[i], or the wrap fails.
  • fee = ceil(pod_amount × wrap_fee_bps ÷ 10,000). The user gets pod_amount − fee.
  • Fails if a component's transfer-hook program differs from the one recorded at creation.
  • Blocked while wraps_paused is set.

Program errors

6002 WrapsPaused6013 ZeroAmount6014 LengthMismatch6015 MissingAccounts6016 ComponentMismatch6018 InvalidTokenProgram6019 InvalidMint6027 HookChanged6020 InvalidTokenAccount6028 SlippageExceededIn6030 UnexpectedTransferAmount6031 ZeroBacking6000 MathOverflow

unwrapSDK: unwrap

Returns pod_amount cohorts: the fee goes to the FeeVault, the rest is burned, and every component is paid out pro-rata.

Signer · Any holder.

Accounts

unwrap accounts
#AccountNotes
0usermutsigner
1podmut
2pod_mintmut
3fee_vaultmut
4user_pod_accountmut
5pod_token_program
6system_program

Remaining accounts

  1. Per component, 4 accounts: mint, vault, user's destination token account, token program.
  2. Then transfer-hook extras, hook_extras[i] for component i.

Arguments

unwrap arguments
ArgumentType
pod_amountu64
min_outVec<u64>
hook_extrasbytes

Checks and invariants

  • Takes no config account, so no protocol switch can stop it.
  • pod_amount ≤ supply. fee = ceil(pod_amount × unwrap_fee_bps ÷ 10,000); net = pod_amount − fee must be > 0.
  • Each component is synced first, so a pending seizure is charged before anyone is paid.
  • amount_out[i] = floor(net × tracked_balance[i] ÷ supply) and must be ≥ min_out[i].
  • If any component is paused or frozen, its token transfer fails and so does the whole unwrap. Use unwrap_partial.

Program errors

6013 ZeroAmount6014 LengthMismatch6032 AmountExceedsSupply6033 UnwrapTooSmall6015 MissingAccounts6016 ComponentMismatch6018 InvalidTokenProgram6020 InvalidTokenAccount6029 SlippageExceededOut6000 MathOverflow

unwrap_partialSDK: unwrapPartial

Same as unwrap, but the components named in skip_mask are written to ClaimTicket accounts instead of being transferred.

Signer · Any holder, who pays rent for the tickets and gets it back on redemption.

Accounts

unwrap_partial accounts
#AccountNotes
0usermutsigner
1podmut
2pod_mintmut
3fee_vaultmut
4user_pod_accountmut
5pod_token_program
6system_program

Remaining accounts

  1. Per component, 4 accounts: mint, vault, user's destination token account, token program.
  2. Then transfer-hook extras, hook_extras[i] for component i.
  3. Then one ClaimTicket PDA per skipped component, in component order: seeds ["claim", pod, owner, component_index, nonce as u64 little-endian].

Arguments

unwrap_partial arguments
ArgumentType
pod_amountu64
min_outVec<u64>
skip_masku16
nonceu64
hook_extrasbytes

Checks and invariants

  • skip_mask ≠ 0 and has no bit at or above the component count.
  • A component may be skipped only if it is blocked right now: its mint is paused, its vault is frozen, the user's destination account for it is frozen, or its transfer-hook program differs from the one recorded at creation.
  • The ticket amount is computed exactly like an unwrap payout, rounded down, and is checked against min_out[i] too.
  • The ticketed amount leaves tracked_balance and is added to claims_outstanding, so it stays in the vault reserved for the ticket holder.
  • A (pod, owner, index, nonce) ticket can exist once; reusing a nonce while the ticket is open fails.

Program errors

6013 ZeroAmount6014 LengthMismatch6034 InvalidSkipMask6032 AmountExceedsSupply6033 UnwrapTooSmall6015 MissingAccounts6016 ComponentMismatch6018 InvalidTokenProgram6019 InvalidMint6020 InvalidTokenAccount6029 SlippageExceededOut6035 ComponentNotBlocked6036 InvalidClaimTicket6000 MathOverflow

redeem_claimSDK: redeemClaim

Pays out a claim ticket and closes it, returning the ticket's rent to its owner.

Signer · The ticket's owner.

Accounts

redeem_claim accounts
#AccountNotes
0ownermutsigner
1podmut
2ticketmut
3mint
4vaultmut
5owner_token_accountmut
6token_program

Remaining accounts

  1. Transfer-hook extras for the component's transfer, if it has a hook.

Arguments

None.

Checks and invariants

  • No admin switch is consulted. The program doesn't check for a pause itself: the token transfer simply fails while the component still can't move, and the ticket stays open.
  • Pays exactly raw_amount and lowers claims_outstanding by the same amount.

Program errors

6001 Unauthorized6036 InvalidClaimTicket6016 ComponentMismatch6039 InvalidComponentIndex6000 MathOverflow

distribute_feesSDK: distributeFees

Empties the pod's FeeVault: pays the creator and protocol shares and burns everything else.

Signer · Anyone. It is a permissionless crank that pays the caller nothing.

Accounts

distribute_fees accounts
#AccountNotes
0payersignerAnyone may crank.
1config
2pod
3pod_mintmut
4fee_vaultmut
5creator_pod_accountmut
6treasury_pod_accountmut
7pod_token_program

Arguments

None.

Checks and invariants

  • to_creator = floor(fees × creator_share_bps ÷ 10,000); to_protocol = floor(fees × protocol_share_bps ÷ 10,000); burned = fees − to_creator − to_protocol.
  • Rounding dust therefore burns rather than being paid out.
  • The recipients must be Token-2022 pod-token accounts owned by pod.creator and config.treasury.
  • Burning lowers supply without touching any vault, so backing per cohort rises.

Program errors

6037 NoFees6038 InvalidFeeRecipient6020 InvalidTokenAccount6000 MathOverflow

reconcileSDK: reconcile

Compares one component vault with the program's books and records any deficit as shortfall.

Signer · Anyone.

Accounts

reconcile accounts
#AccountNotes
0podmut
1vault

Arguments

reconcile arguments
ArgumentType
component_indexu8

Checks and invariants

  • Expected = tracked_balance + claims_outstanding. A vault below that is a seizure; the deficit (capped at tracked_balance) moves from tracked_balance to shortfall.
  • A vault above that (a donation) is ignored and never enters share math.
  • Idempotent: running it twice records nothing new. wrap and unwrap run the same sync on every component anyway.

Program errors

6039 InvalidComponentIndex6016 ComponentMismatch6020 InvalidTokenAccount6000 MathOverflow

set_creator_shareSDK: setCreatorShare

Lowers the creator's share of fees.

Signer · pod.creator.

Accounts

set_creator_share accounts
#AccountNotes
0creatorsigner
1podmut

Arguments

set_creator_share arguments
ArgumentType
new_bpsu16

Checks and invariants

  • new_bps must be strictly lower than the current creator share.
  • The freed share is added to burn_share_bps, never redirected, so burn + creator + protocol stays 10,000.

Program errors

6001 Unauthorized6008 CreatorShareCanOnlyDecrease6000 MathOverflow

set_lookup_tableSDK: setLookupTable

Records an address lookup table for the pod, once, so large pods fit a wrap or unwrap in one transaction.

Signer · Anyone.

Accounts

set_lookup_table accounts
#AccountNotes
0podmut
1lookup_table

Arguments

None.

Checks and invariants

  • The table must be frozen (no authority, never deactivated), so it can't be closed or changed later.
  • It must contain the pod, the pod mint, the fee vault and every component's mint and vault.
  • Only possible while pod.lookup_table is unset.

Program errors

6040 LookupTableAlreadySet6041 InvalidLookupTable

Accounts and PDAs

Four account types. The pod-token FeeVault and every component vault are ordinary associated token accounts owned by the Pod PDA (the FeeVault under Token-2022), not program accounts. Derive the PDAs with the SDK’s configPda, issuerPda, podPda and claimPda.

PDA derivation (sdk/src/pda.ts)
PublicKey.findProgramAddressSync([Buffer.from("config")], programId);
PublicKey.findProgramAddressSync([Buffer.from("issuer"), key.toBuffer()], programId);
PublicKey.findProgramAddressSync([Buffer.from("pod"), podMint.toBuffer()], programId);
PublicKey.findProgramAddressSync(
  [Buffer.from("claim"), pod.toBuffer(), owner.toBuffer(), Buffer.from([componentIndex]), nonceLe8],
  programId,
);

ProtocolConfig

Global protocol configuration. Seeds: `["config"]`.

PDA seeds
["config"]
Space
85 bytes allocated, including the 8-byte discriminator
ProtocolConfig layout
OffsetFieldTypeSizeNotes
0discriminator[u8; 8]8sha256("account:ProtocolConfig")[..8]
8adminPubkey32
40treasuryPubkey32
72protocol_share_bpsu162Protocol share of fees (bps) applied to pods created from now on.
74max_componentsu81
75wraps_pausedbool1Blocks new wraps and pod creation only. Unwrapping can never be paused.
76pod_countu648
84bumpu81

IssuerAllowlist

Existence of this account marks `key` (an issuer authority, permanent delegate or transfer-hook program) as accepted. Seeds: `["issuer", key]`.

PDA seeds
["issuer", key]
Space
77 bytes allocated, including the 8-byte discriminator
IssuerAllowlist layout
OffsetFieldTypeSizeNotes
0discriminator[u8; 8]8sha256("account:IssuerAllowlist")[..8]
8keyPubkey32
40labelstring36
—bumpu81

Pod

A pod. Seeds: `["pod", pod_mint]`.

PDA seeds
["pod", pod_mint]
Space
1762 bytes allocated, including the 8-byte discriminator
Pod layout
OffsetFieldTypeSizeNotes
0discriminator[u8; 8]8sha256("account:Pod")[..8]
8creatorPubkey32
40pod_mintPubkey32
72fee_vaultPubkey32
104lookup_tablePubkey32Frozen address lookup table for large pods (default = unset).
136wrap_fee_bpsu162
138unwrap_fee_bpsu162
140burn_share_bpsu162
142creator_share_bpsu162
144protocol_share_bpsu162
146verifiedbool1
147bumpu81
148componentsVec<Component>1614

ClaimTicket

Raw units of a paused/frozen component owed to `owner`. Seeds: `["claim", pod, owner, component_index, nonce_le]`.

PDA seeds
["claim", pod, owner, component_index, nonce_le]
Space
122 bytes allocated, including the 8-byte discriminator
ClaimTicket layout
OffsetFieldTypeSizeNotes
0discriminator[u8; 8]8sha256("account:ClaimTicket")[..8]
8podPubkey32
40ownerPubkey32
72mintPubkey32
104component_indexu81
105nonceu648
113raw_amountu648
121bumpu81

Types

CreatePodArgs

CreatePodArgs fields
FieldTypeSizeNotes
namestringvar
symbolstringvar
uristringvar
recipe_unitsVec<u64>varRaw units of each component per 1 whole cohort token.
wrap_fee_bpsu162
unwrap_fee_bpsu162
creator_share_bpsu162
genesis_amountu648Pod tokens (raw, 9 decimals) minted to the creator by the genesis wrap.
hook_extrasbytesvarTransfer-hook extra account count per component (empty = none).

Component161 bytes

Component fields
FieldTypeSizeNotes
mintPubkey32
token_programPubkey32
vaultPubkey32ATA(pod, mint, token_program).
hook_programPubkey32Transfer-hook program at creation (default = none). Wraps are blocked if it changes.
decimalsu81
recipe_unitsu648Raw units of this component per 1 whole cohort token at genesis.
tracked_balanceu648Program-tracked balance backing outstanding cohort tokens. All share math uses this.
shortfallu648Cumulative amount detected as removed by the issuer (socialised in this pod).
claims_outstandingu648Raw units held in the vault for unredeemed claim tickets.

Events

Emitted through Anchor’s emit! into the transaction logs.

Events
EventFields
PodCreatedpod: Pubkey, pod_mint: Pubkey, creator: Pubkey, genesis_amount: u64
Wrappedpod: Pubkey, user: Pubkey, pod_amount: u64, fee: u64, amounts_in: Vec<u64>
Unwrappedpod: Pubkey, user: Pubkey, pod_amount: u64, fee: u64, amounts_out: Vec<u64>, skip_mask: u16
ClaimCreatedpod: Pubkey, ticket: Pubkey, owner: Pubkey, component_index: u8, raw_amount: u64
ClaimRedeemedpod: Pubkey, ticket: Pubkey, owner: Pubkey, raw_amount: u64
FeesDistributedpod: Pubkey, burned: u64, to_creator: u64, to_protocol: u64
ShortfallDetectedpod: Pubkey, component_index: u8, amount: u64, tracked_balance: u64

Error codes

Custom program errors start at 6000 (0x1770). A failed transaction reports them as custom program error: 0x…. Match the hex against this table; @cohort/sdk exports the same list as IDL.errors.

Error codes
CodeHexNameMessageRaised by
60000x1770MathOverflowArithmetic overflowcreate_pod wrap unwrap unwrap_partial redeem_claim distribute_fees reconcile set_creator_share
60010x1771UnauthorizedSigner is not authorized for this actioninitialize_config set_wraps_paused add_issuer remove_issuer set_pod_verified redeem_claim set_creator_share
60020x1772WrapsPausedNew wraps are paused by the protocolcreate_pod wrap
60030x1773InvalidComponentCountA pod needs between 2 and 10 componentscreate_pod
60040x1774FeeTooHighWrap/unwrap fee exceeds 200 bpscreate_pod
60050x1775CreatorShareTooHighCreator share exceeds 1000 bpscreate_pod
60060x1776InvalidFeeSplitburn + creator + protocol shares must equal 10000 bpscreate_pod
60070x1777ProtocolShareTooHighProtocol share exceeds the allowed maximuminitialize_config
60080x1778CreatorShareCanOnlyDecreaseCreator share can only be loweredset_creator_share
60090x1779MetadataTooLongName, symbol or URI too longcreate_pod
60100x177aLabelTooLongLabel too longadd_issuer
60110x177bZeroRecipeUnitsRecipe units must be non-zerocreate_pod
60120x177cGenesisTooSmallGenesis wrap must be at least one whole cohort tokencreate_pod
60130x177dZeroAmountAmount must be greater than zerocreate_pod wrap unwrap unwrap_partial
60140x177eLengthMismatchArgument vector length does not match the number of componentscreate_pod wrap unwrap unwrap_partial
60150x177fMissingAccountsNot enough remaining accounts for the componentscreate_pod wrap unwrap unwrap_partial
60160x1780ComponentMismatchComponent account does not match the podwrap unwrap unwrap_partial redeem_claim reconcile
60170x1781DuplicateComponentDuplicate component mintcreate_pod
60180x1782InvalidTokenProgramAccount is not owned by an SPL token programcreate_pod wrap unwrap unwrap_partial
60190x1783InvalidMintAccount is not a valid mintcreate_pod wrap unwrap_partial
60200x1784InvalidTokenAccountAccount is not a valid token accountcreate_pod wrap unwrap unwrap_partial distribute_fees reconcile
60210x1785TransferFeeNotAllowedMints with a TransferFee extension are not allowedcreate_pod
60220x1786DisallowedExtensionMint has an extension that is not allowedcreate_pod
60230x1787IssuerNotAllowlistedMint authority/delegate/hook is not on the issuer allowlistcreate_pod
60240x1788InvalidVaultVault is not the canonical associated token account of the podcreate_pod
60250x1789VaultFrozenVault is frozencreate_pod
60260x178aComponentPausedComponent mint is pausedcreate_pod
60270x178bHookChangedTransfer hook program changed since pod creation; new wraps are blockedwrap
60280x178cSlippageExceededInAmount in exceeds max_in (slippage)wrap
60290x178dSlippageExceededOutAmount out below min_out (slippage)unwrap unwrap_partial
60300x178eUnexpectedTransferAmountVault received an unexpected amountcreate_pod wrap
60310x178fZeroBackingPod has no backing to wrap againstwrap
60320x1790AmountExceedsSupplyAmount exceeds pod supplyunwrap unwrap_partial
60330x1791UnwrapTooSmallUnwrap amount is too small to pay out after feesunwrap unwrap_partial
60340x1792InvalidSkipMaskInvalid skip maskunwrap_partial
60350x1793ComponentNotBlockedComponent can move; it cannot be turned into a claim ticketunwrap_partial
60360x1794InvalidClaimTicketClaim ticket account does not match the expected PDAunwrap_partial redeem_claim
60370x1795NoFeesNothing to distributedistribute_fees
60380x1796InvalidFeeRecipientFee recipient account is invaliddistribute_fees
60390x1797InvalidComponentIndexComponent index out of rangeredeem_claim reconcile
60400x1798LookupTableAlreadySetLookup table already set for this podset_lookup_table
60410x1799InvalidLookupTableLookup table is invalid, mutable, or missing pod addressesset_lookup_table
60420x179aInvalidAllowlistAccountInvalid issuer allowlist accountcreate_pod

Before you continue

Not available to US persons or in restricted regions.

Cohort is experimental software on Solana devnet. Cohort tokens are backed by tokenized stocks issued by third parties, who can freeze, pause or seize them.

You may not use this interface if you are a resident of, or located in:

  • the United States and its territories
  • Canada
  • the United Kingdom
  • Australia
  • any country or region subject to comprehensive sanctions (including Cuba, Iran, North Korea, Syria, and the Crimea, Donetsk and Luhansk regions)
Terms v2026-11-06