Skip to content

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

↑ ↓ to move · Enter to open

Contents · §8 SDK

§8 · Building

The TypeScript SDK

@cohort/sdk reads pods, quotes wraps and unwraps, and builds unsigned v0 transactions for a wallet to sign.

@cohort/sdk is the TypeScript client for the Cohort program. Its main export, createCohortClient, returns an object that reads pods, quotes wraps and unwraps with the program's own integer math, and builds unsigned v0 transactions. Signing and sending stay with your wallet. Amounts are raw base units as bigint; addresses are base58 strings.

Install§

The package lives in the repository's sdk/ directory and isn't published to npm yet. Build it and depend on it by path:

Shell
cd sdk
npm install
npm run build        # emits sdk/dist (ESM + .d.ts)

# in your project
npm install ../path/to/peasv2/sdk @solana/web3.js

It depends on @solana/web3.js 1.x, @solana/spl-token 0.4 and @coral-xyz/anchor 0.29, and runs in Node 18+ and in browsers.

Create a client§

TypeScript
import { Connection } from "@solana/web3.js";
import { createCohortClient } from "@cohort/sdk";

const connection = new Connection("https://api.devnet.solana.com", "confirmed");
const client = createCohortClient(connection);
// A different deployment: createCohortClient(connection, "<program id>")

createCohortClient(connection, programId?) returns a CohortSdkClient. Without a program ID it uses COHORT_PROGRAM_ID, the ID compiled into the program (see §7 Program reference). The devnet deployment uses that same ID; its pods and mints are listed on §4 The pods.

Reading pods§

listPods§

TypeScript
const pods = await client.listPods(); // PodView[], sorted by address
for (const p of pods) console.log(p.symbol, p.supply, p.components.map((c) => c.symbol));

Finds every Pod account by its discriminator with getProgramAccounts, then fetches the pod mints, fee vaults, component mints and vaults in batches of 100. Public RPC endpoints may rate-limit getProgramAccounts; use a dedicated RPC for anything busy.

getPod§

TypeScript
const pod = await client.getPod(podAddress);
pod.wrapFeeBps;          // 30
pod.pendingFees;         // cohorts waiting in the FeeVault, raw
pod.components[0].issuerPowers; // { permanentDelegate, freezeAuthority, pausable, transferHook, scaledUiAmount }
pod.components[0].paused;       // live: the mint is paused
pod.components[0].frozen;       // live: the pod's vault is frozen

A PodView carries the pod's fees and shares, its supply and pending fees, and per component the mint, token program, decimals, recipeUnits, trackedBalance, shortfall, live pause and freeze state, and the issuer powers present on the mint. symbol comes from the mint's Token-2022 metadata, or a shortened address if it has none.

Quotes§

TypeScript
const q = await client.quoteWrap(podAddress, 10_000_000_000n); // 10 cohorts, gross
q.amountsIn; // bigint[], raw component units, indexed like pod.components
q.fee;       // cohorts to the FeeVault
q.podOut;    // cohorts you receive = podAmount − fee

const u = await client.quoteUnwrap(podAddress, 10_000_000_000n);
u.fee;        // cohorts to the FeeVault
u.amountsOut; // bigint[], raw component units

Quotes replay the program's arithmetic exactly: ceil on the way in, floor on the way out, the fee rounded up. They also apply the seizure check the program runs first, so a seizure that hasn't been reconciled yet is already reflected. The math is exported for offline use as wrapAmounts, unwrapAmounts, feeFor, effectiveTracked and splitFees.

Quotes are raw units. For display, divide by 10 ** decimals, and for a ScaledUiAmount component multiply by its current multiplier. quoteUnwrap throws if the amount exceeds supply.

Building and sending transactions§

Every builder takes TxParams (pod, user, podAmount, slippageBps) or the addresses it needs, and returns an unsigned VersionedTransaction with the user as fee payer and a fresh blockhash. Sign and send it promptly.

buildWrapTx§

TypeScript
const tx = await client.buildWrapTx({ pod: podAddress, user: owner, podAmount: 10_000_000_000n, slippageBps: 50 });
const sig = await wallet.sendTransaction(tx, connection); // wallet-adapter
await connection.confirmTransaction(sig, "confirmed");

Sets max_in to each quoted amount plus slippageBps, rounded up, and creates your cohort token account if it doesn't exist. You need every component in your associated token accounts.

buildUnwrapTx§

TypeScript
const tx = await client.buildUnwrapTx({ pod: podAddress, user: owner, podAmount: 4_000_000_000n, slippageBps: 50 });

Sets min_out to each quoted amount minus slippageBps, rounded down, and creates any missing component token accounts.

buildUnwrapPartialTx§

TypeScript
const tx = await client.buildUnwrapPartialTx({ pod: podAddress, user: owner, podAmount: 1_000_000_000n, slippageBps: 50, skip: [2] });

skip lists component indices to turn into claim tickets. It must not be empty. The SDK picks a random 64-bit nonce and appends the ticket accounts. See §6 Partial unwrap and claim tickets.

listClaims and buildRedeemClaimTx§

TypeScript
const claims = await client.listClaims(owner); // ClaimTicketView[]
const ready = claims.filter((c) => c.redeemable);
const tx = await client.buildRedeemClaimTx(ready[0].address, owner);

redeemable is true when the component's mint isn't paused and neither the pod's vault nor your token account is frozen. buildRedeemClaimTx throws if the ticket belongs to someone else.

buildDistributeFeesTx§

TypeScript
const tx = await client.buildDistributeFeesTx(podAddress, payer);

Burns the FeeVault after paying the creator and protocol shares. Anyone may send it; it fails with NoFees when the FeeVault is empty, so check pod.pendingFees first. It creates the creator's and treasury's cohort token accounts if they are missing, at the payer's expense.

Signing without a wallet adapter§

TypeScript
import { Keypair } from "@solana/web3.js";

const tx = await client.buildWrapTx({ pod, user: keypair.publicKey.toBase58(), podAmount, slippageBps: 50 });
tx.sign([keypair]);
const sig = await connection.sendTransaction(tx);

What the builders take care of§

  • Compute budget. Each transaction starts with a compute-unit limit sized to the work: 60,000 plus 25,000 per token transfer or hook call plus 30,000 per account created, capped at 1.4M. No priority fee is added.
  • Missing token accounts. Associated token accounts are created with the idempotent instruction, so a race with another transaction is harmless.
  • The 64-instruction trace limit. Creating accounts costs inner instructions. If a large pod plus several new accounts would exceed Solana's limit, the builder throws and tells you to create the token accounts first.

Transfer hooks§

When a component mint has a transfer hook enabled, every transfer of it needs the hook program's extra accounts. The builders resolve them for you with addExtraAccountMetasForExecute from @solana/spl-token, for the exact source, destination and amount of each transfer, and pass them to the program with a per-component count in hook_extras. The program forwards them to the token program.

Resolve them at build time and never cache them: an issuer can change what its hook requires. If you build instructions yourself with client.ix (the CohortInstructions class), pass hookExtras as one AccountMeta[] per component, in component order; componentRemainingAccounts shows the layout the program expects.

Lookup tables§

A wrap of a seven-component pod names dozens of accounts, more than a legacy transaction can hold. The program records one address lookup table per pod with set_lookup_table, and the builders compile against it automatically when pod.lookup_table is set.

Anyone can register the table, once. It must be frozen (no authority, so nobody can close or change it) and contain the pod, the pod mint, the FeeVault and every component's mint and vault:

TypeScript
import { AddressLookupTableProgram, PublicKey } from "@solana/web3.js";
import { decodePod, podPda } from "@cohort/sdk";

const podKey = podPda(podMint);
const state = decodePod((await connection.getAccountInfo(podKey))!.data);
const addresses = [podKey, state.podMint, state.feeVault, ...state.components.flatMap((c) => [c.mint, c.vault])];

const slot = await connection.getSlot("finalized");
const [create, lut] = AddressLookupTableProgram.createLookupTable({ authority: payer, payer, recentSlot: slot });
const extend = AddressLookupTableProgram.extendLookupTable({ lookupTable: lut, authority: payer, payer, addresses });
const freeze = AddressLookupTableProgram.freezeLookupTable({ lookupTable: lut, authority: payer });
// Send create + extend, then freeze, then:
const register = await client.ix.setLookupTable({ pod: podKey, lookupTable: lut });

The program's tests run every instruction on a ten-component pod with a lookup table in one transaction each, using less than 60% of the compute limit.

Lower-level exports§

ExportWhat it is
CohortSdkClientThe class behind createCohortClient; exposes connection, programId and ix
CohortInstructionsAn instruction builder for every program instruction, with no network calls
configPda, issuerPda, podPda, claimPdaPDA derivation
decodePod, decodeClaimTicket, decodeProtocolConfig, DISCRIMINATORSAccount decoding
parseMint, parseTokenAccountToken and Token-2022 parsing, including pause, hook and issuer powers
wrapAmounts, unwrapAmounts, feeFor, effectiveTracked, splitFees, withMaxSlippage, withMinSlippageThe share math
IDL, COHORT_PROGRAM_ID, POD_DECIMALS, ONE_POD, MAX_FEE_BPS, …The IDL and constants

The CohortClient interface in sdk/src/types.ts is the contract between the SDK and the Cohort app; changing it means changing both.

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