Read market state onchain
A Tribe quote comes from Solana account state. Read the accounts below for market discovery and quote calculation.
This page builds a reusable tribe-market.ts module that:
- Resolves a mint to its Token account with a filtered program-account lookup.
- Reads the Token account's onchain token ID, creator, curve, launch, and fee-tier keys.
- Decodes the selected
FeeTierConfigand chooses the rate active at the current price. - Reads the token and SOL vault balances needed for buy and sell checks.
- Validates every derived address, owner, mint, fee-tier link, and liquidity invariant.
Use the resulting state immediately. A terminal or aggregator should subscribe to or index Token accounts and invalidate its quote cache on every update rather than running discovery for every quote.
What is the onchain token ID?
The onchain token ID is a sequential u64 stored in each Tribe Token account. It is not a mint, symbol, database ID, permission, or user-facing identifier. The program uses it in the PDA seeds for that market's Token and SOL-vault accounts:
- Token PDA:
"token", token_id (u64 little-endian) - SOL vault PDA:
"sol_vault", token_id (u64 little-endian)
An integration can start with the mint users already know. The resolver below filters Tribe Token accounts by the mint field, reads the ID from the matching account, and verifies that the ID derives back to that exact Token PDA. No Tribe endpoint is involved.
Install and copy the IDL
pnpm add @anchor-lang/[email protected] @solana/[email protected] @solana/[email protected] [email protected] [email protected]
Copy the swap IDL from Integrate permissionless swaps into your project as tribe-swap-idl.json.
Configure SOLANA_RPC_URL with your Solana mainnet RPC URL. All reads, simulations, submissions, and confirmations in these guides use confirmed commitment.
Resolve a mint
The Token discriminator and mint offset come directly from the swap IDL. The eight-byte discriminator is at offset 0; the mint begins at offset 48 after the discriminator, token_id, and creator.
import { BorshAccountsCoder, type Idl } from "@anchor-lang/core";
import { PublicKey } from "@solana/web3.js";
import { Buffer } from "buffer";
import swapIdl from "./tribe-swap-idl.json";
export const TRIBE_PROGRAM_ID = new PublicKey("B1x53qgNmAdZMfPVZvu89qDmNn3RpdKajRFXqCzE7UPU");
const TOKEN_DISCRIMINATOR_BASE58 = "P5VjaUJzPi6";
export function u64Seed(value: bigint): Buffer {
if (value < 0n || value > 18_446_744_073_709_551_615n) throw new Error("Value does not fit in u64");
const bytes = Buffer.alloc(8);
bytes.writeBigUInt64LE(value);
return bytes;
}
export function tribePda(seeds: (Buffer | Uint8Array)[]): PublicKey {
return PublicKey.findProgramAddressSync(seeds, TRIBE_PROGRAM_ID)[0];
}
type DecodedToken = {
token_id: { toString(): string };
creator: PublicKey;
mint: PublicKey;
sqrt_price_x64: { toString(): string };
liquidity: { toString(): string };
virtual_liquidity: { toString(): string };
created_at: { toString(): string };
paused: boolean;
lp_liquidity: { toString(): string };
virtual_token_amount: { toString(): string };
lp_fee_reserved_sol: { toString(): string };
fee_tier: PublicKey;
anti_snipe_until: { toString(): string };
anti_snipe_start_rate: number;
permissionless_buy_unlocked: boolean;
};
type DecodedBreakpoint = {
sqrt_price: { toString(): string };
total_fee_rate: number;
creator_fee_rate: number;
lp_fee_rate: number;
};
type DecodedFeeTier = {
index: { toString(): string };
start_total_fee_rate: number;
start_creator_fee_rate: number;
start_lp_fee_rate: number;
active_breakpoint_count: number;
breakpoints: DecodedBreakpoint[];
};
function decodeAccount<T>(coder: BorshAccountsCoder, name: "Token" | "FeeTierConfig", data: Buffer): T {
return coder.decode(name, data) as T;
}
export async function resolveTokenAccount(connection: import("@solana/web3.js").Connection, mint: PublicKey) {
const matches = await connection.getProgramAccounts(TRIBE_PROGRAM_ID, {
commitment: "confirmed",
filters: [
{ memcmp: { offset: 0, bytes: TOKEN_DISCRIMINATOR_BASE58 } },
{ memcmp: { offset: 48, bytes: mint.toBase58() } },
],
});
if (matches.length !== 1) {
throw new Error(`Expected one Tribe market for ${mint.toBase58()}, found ${matches.length}`);
}
const match = matches[0];
if (!match) throw new Error("Filtered Tribe market result is missing");
if (!match.account.owner.equals(TRIBE_PROGRAM_ID)) throw new Error("Token account has the wrong owner");
const coder = new BorshAccountsCoder(swapIdl as Idl);
const decoded = decodeAccount<DecodedToken>(coder, "Token", Buffer.from(match.account.data));
const tokenId = BigInt(decoded.token_id.toString());
const expectedAddress = tribePda([Buffer.from("token"), u64Seed(tokenId)]);
if (!expectedAddress.equals(match.pubkey) || !decoded.mint.equals(mint)) {
throw new Error("Mint, token ID, and Token PDA do not identify the same market");
}
return { address: match.pubkey, tokenId, decoded, coder };
}
Load and validate quote state
Add the following to tribe-market.ts. It converts decoded integers to bigint, validates the account graph, and returns one plain state object that both quote directions use.
import { Connection, SYSVAR_CLOCK_PUBKEY, SystemProgram } from "@solana/web3.js";
import { TOKEN_2022_PROGRAM_ID, unpackAccount } from "@solana/spl-token";
export type FeeRates = { total: bigint; creator: bigint; lp: bigint };
export type TribeMarket = {
tokenId: bigint;
mint: PublicKey;
creator: PublicKey;
sqrtPriceX64: bigint;
liquidity: bigint;
virtualLiquidity: bigint;
lpLiquidity: bigint;
createdAt: bigint;
paused: boolean;
virtualTokenAmount: bigint;
lpFeeReservedSol: bigint;
antiSnipeUntil: bigint;
antiSnipeStartRate: bigint;
permissionlessBuyUnlocked: boolean;
feeSchedule: {
start: FeeRates;
breakpoints: Array<{ sqrtPriceX64: bigint; rates: FeeRates }>;
};
tokenVaultAmount: bigint;
solVaultLamports: bigint;
rentExemptLamports: bigint;
accounts: {
protocol: PublicKey;
token: PublicKey;
solVault: PublicKey;
tokenVault: PublicKey;
poolAuthority: PublicKey;
protocolVault: PublicKey;
feeTierConfig: PublicKey;
eventAuthority: PublicKey;
};
};
function toBigInt(value: { toString(): string }): bigint {
return BigInt(value.toString());
}
function assertOwner(name: string, info: { owner: PublicKey } | null | undefined, owner: PublicKey): void {
if (!info?.owner.equals(owner)) throw new Error(`${name} has the wrong owner`);
}
export async function loadOnchainMarket(connection: Connection, mint: PublicKey): Promise<TribeMarket> {
const resolved = await resolveTokenAccount(connection, mint);
const token = resolved.decoded;
const tokenIdSeed = u64Seed(resolved.tokenId);
const protocol = tribePda([Buffer.from("protocol")]);
const protocolVault = tribePda([Buffer.from("protocol_vault")]);
const solVault = tribePda([Buffer.from("sol_vault"), tokenIdSeed]);
const poolAuthority = tribePda([Buffer.from("pool_authority")]);
const tokenVault = tribePda([Buffer.from("token_vault"), mint.toBuffer(), resolved.address.toBuffer()]);
const eventAuthority = tribePda([Buffer.from("__event_authority")]);
const infos = await connection.getMultipleAccountsInfo(
[TRIBE_PROGRAM_ID, protocol, mint, solVault, tokenVault, protocolVault, token.fee_tier],
"confirmed",
);
const [programInfo, protocolInfo, mintInfo, solVaultInfo, tokenVaultInfo, protocolVaultInfo, feeTierInfo] = infos;
if (!programInfo?.executable) throw new Error("Tribe program is missing or not executable");
assertOwner("Protocol", protocolInfo, TRIBE_PROGRAM_ID);
assertOwner("mint", mintInfo, TOKEN_2022_PROGRAM_ID);
assertOwner("SOL vault", solVaultInfo, SystemProgram.programId);
assertOwner("token vault", tokenVaultInfo, TOKEN_2022_PROGRAM_ID);
assertOwner("protocol vault", protocolVaultInfo, SystemProgram.programId);
assertOwner("fee tier", feeTierInfo, TRIBE_PROGRAM_ID);
if (!tokenVaultInfo || !solVaultInfo || !feeTierInfo) throw new Error("Market account is missing");
if (solVaultInfo.data.length !== 0 || protocolVaultInfo?.data.length !== 0) {
throw new Error("SOL vaults must be zero-data System Program accounts");
}
const vault = unpackAccount(tokenVault, tokenVaultInfo, TOKEN_2022_PROGRAM_ID);
if (!vault.mint.equals(mint) || !vault.owner.equals(poolAuthority)) {
throw new Error("Token vault has the wrong mint or authority");
}
const feeTier = decodeAccount<DecodedFeeTier>(resolved.coder, "FeeTierConfig", Buffer.from(feeTierInfo.data));
const feeTierIndex = toBigInt(feeTier.index);
const expectedFeeTier = tribePda([Buffer.from("fee_tier"), u64Seed(feeTierIndex)]);
if (!expectedFeeTier.equals(token.fee_tier)) throw new Error("Token points to an invalid fee tier PDA");
const liquidity = toBigInt(token.liquidity);
const virtualLiquidity = toBigInt(token.virtual_liquidity);
const lpLiquidity = toBigInt(token.lp_liquidity);
if (liquidity !== virtualLiquidity + lpLiquidity) throw new Error("Token liquidity invariant failed");
if (token.paused) throw new Error("This market is paused");
const count = feeTier.active_breakpoint_count;
if (!Number.isInteger(count) || count < 0 || count > 15) throw new Error("Invalid fee breakpoint count");
return {
tokenId: resolved.tokenId,
mint,
creator: token.creator,
sqrtPriceX64: toBigInt(token.sqrt_price_x64),
liquidity,
virtualLiquidity,
lpLiquidity,
createdAt: toBigInt(token.created_at),
paused: token.paused,
virtualTokenAmount: toBigInt(token.virtual_token_amount),
lpFeeReservedSol: toBigInt(token.lp_fee_reserved_sol),
antiSnipeUntil: toBigInt(token.anti_snipe_until),
antiSnipeStartRate: BigInt(token.anti_snipe_start_rate),
permissionlessBuyUnlocked: token.permissionless_buy_unlocked,
feeSchedule: {
start: {
total: BigInt(feeTier.start_total_fee_rate),
creator: BigInt(feeTier.start_creator_fee_rate),
lp: BigInt(feeTier.start_lp_fee_rate),
},
breakpoints: feeTier.breakpoints.slice(0, count).map((breakpoint) => ({
sqrtPriceX64: toBigInt(breakpoint.sqrt_price),
rates: {
total: BigInt(breakpoint.total_fee_rate),
creator: BigInt(breakpoint.creator_fee_rate),
lp: BigInt(breakpoint.lp_fee_rate),
},
})),
},
tokenVaultAmount: vault.amount,
solVaultLamports: BigInt(solVaultInfo.lamports),
rentExemptLamports: BigInt(await connection.getMinimumBalanceForRentExemption(0, "confirmed")),
accounts: {
protocol,
token: resolved.address,
solVault,
tokenVault,
poolAuthority,
protocolVault,
feeTierConfig: token.fee_tier,
eventAuthority,
},
};
}
export function activeFeeRates(market: TribeMarket): FeeRates {
let rates = market.feeSchedule.start;
for (const breakpoint of market.feeSchedule.breakpoints) {
if (market.sqrtPriceX64 <= breakpoint.sqrtPriceX64) rates = breakpoint.rates;
else break;
}
if (rates.total >= 1_000_000n || rates.creator + rates.lp > rates.total) {
throw new Error("Invalid fee schedule");
}
return rates;
}
export async function readChainUnixTime(connection: Connection): Promise<bigint> {
const clock = await connection.getAccountInfo(SYSVAR_CLOCK_PUBKEY, "confirmed");
if (!clock || clock.data.length < 40) throw new Error("Solana Clock sysvar is unavailable");
return clock.data.readBigInt64LE(32);
}
Quote freshness
- Call
loadOnchainMarketimmediately before calculating a quote. - Build the transaction from that same returned account graph.
- Simulate before wallet signing. If simulation reports stale price, fee, gate, or balance state, reload and requote instead of weakening the user's limit.
- For streaming quotes, subscribe to the Token account, selected
FeeTierConfig, SOL vault, and token vault. Any change invalidates the quote.
The Token account's permissionless_buy_unlocked field is a monotonic onchain latch. When it is already true, the buy gate is open. When it is still false, the program may open it during the next buy if the onchain age or threshold rule has become eligible; unsigned simulation is the authoritative readiness check. Sells do not use this gate.