# How Tribe.run works Source: /docs # How Tribe.run works Every token on Tribe.run is linked to a GitHub repo. You launch a token from one of your own public repos, one repo backs at most one token, and the link never changes. The token is the repo's market: a way to back a project and its creator with real liquidity. Holding is sponsorship. Anyone with at least 10,000 tokens counts as one of the token's sponsors, and the top sponsors appear on the repo's sponsor badge: a live image of the token's biggest holders, made for the README. The badge is verified, not self-reported. Tribe.run checks the repo's README on GitHub and marks the token page **Badge live** only while the badge is actually there; remove it and the status drops. Tribe.run is a launchpad and AMM in one, so every token trades natively from launch with no external exchange and no migration. Every trade pays the creator, and holders above the sponsor bar get the Tribe, the token's holders-only chat. ## Start with the mechanism - [Launch a token](/docs/launch) to understand supply, requirements, cost, and creator earnings. - [Read how trading works](/docs/trading) for the pool, pricing, launch protection, and liquidity providers. - [Review the fee schedule](/docs/fees) to see how each trade is split. - [Integrate swaps](/docs/integrate/permissionless-swaps) directly, earn referral fees, and let any Solana wallet trade without a Tribe account. ## Use Tribe.run - [Open the web app](/app) to trade, manage your portfolio, launch tokens, and track your rewards. - [Download the iOS or Android app](/download) to create an account with an invite code. - Follow [@tribedotrun on X](https://x.com/tribedotrun) for project updates. Tribe 1.0 winds down on July 31, 2026. Wallet export remains available after the shutdown through [Legacy Tribe](https://legacy.tribe.run). --- # Launch a token Source: /docs/launch # Launch a token Connect your GitHub account, pick one of your public repos, and launch. The app prefills the token's name, symbol, and image from the repo; you can edit everything before launch and nothing after. One repo backs at most one token. | | | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Eligibility** | Any public GitHub repo you have push access to. Private repos are not eligible. | | **Supply** | 1,000,000,000 tokens, fixed. At the current launch allocation, 2,500,000 tokens go to the referrer or creator and the remaining 997,500,000 seed the pool. The mint authority is then revoked. | | **Cost** | No protocol fee. You pay Solana rent of about 0.012 SOL, plus an optional first buy. Buying first protects your launch from snipers. | | **What you earn** | The creator share of every trade, starting at 1.00% and settling to 0.20% as the market grows, plus every anti-sniping fee from your token's first 60 seconds. Both land in your wallet at trade time; there is nothing to claim. | | **Referrer bonus** | 2,500,000 tokens (0.25% of supply) go to your direct referrer at launch, or to you if nobody referred you. | --- # Trading and the AMM Source: /docs/trading # Trading and the AMM Every token trades against its own pool inside Tribe.run's Solana program, from the moment it launches. The AMM is native: there is no external exchange, no graduation, and no migration. The pool that opens at launch is the pool forever. ## The pool The pool is a constant-product AMM seeded with 35 SOL of virtual liquidity against nearly the full 1 billion token supply. Virtual liquidity gives the market depth from the first trade and can never be withdrawn. The last 2,500,000 tokens of the supply, worth about 0.08 SOL at launch, can never be sold back to the pool. `B1x53qgNmAdZMfPVZvu89qDmNn3RpdKajRFXqCzE7UPU` is the Tribe DEX program on Solana mainnet. ## Price and market cap The pool sets the price. A buy pulls tokens out and adds SOL, so the price rises; a sell does the opposite. The bigger the trade relative to the pool's depth, the more the price moves. A token's market cap is the current price times the 1 billion token supply, quoted in SOL. Market cap drives the fee schedule and how quickly buying opens beyond the app. ## Launch protection A fresh launch is the easiest target on a DEX: the first buyer gets the best price the token will ever offer. Tribe.run counters snipers in three ways. First, the creator's opening buy settles in the same transaction that creates the token, so nothing can trade against the pool before the creator does. Second, for the first 60 seconds after launch every buy pays a dynamic anti-sniping fee. It starts at 99% of the trade and decays linearly down to the token's normal fee rate as the window closes. The full anti-sniping fee is paid to the token's creator at trade time. Sells never pay it, and the creator's own buys are exempt. Third, a new token can only be bought through Tribe.run at first. Buys open to any Solana wallet directly on the program once the token's market cap reaches 420 SOL or the token is 24 hours old, whichever comes first. Once open, buying stays open even if the market cap later falls. Selling is open to everyone from the first second. ## Liquidity providers Anyone can add real liquidity on top as a liquidity provider (LP). LP positions are NFTs, start from 0.01 SOL, and lock for 48 hours after every deposit. LPs earn up to 0.20% of every trade. The LP rate scales with the share of pool liquidity that real LPs provide, and fees accrue to your position until you claim them. --- # Fees Source: /docs/fees # Fees Every trade pays a fee on its SOL side: taken from the SOL you spend on a buy, and from the SOL you receive on a sell. The fee splits two ways: a creator share, and a protocol share that funds referral rewards and LP payouts. Rates fall as the token's market cap grows: young tokens pay more, established ones pay less. | Market cap | Creator | Protocol, LP, and incentives\* | | --------------- | ------: | -----------------------------: | | Under 10k SOL | 1.00% | 2.00% | | 10k SOL and up | 0.80% | 1.70% | | 20k SOL and up | 0.60% | 1.40% | | 40k SOL and up | 0.40% | 1.10% | | 60k SOL and up | 0.30% | 0.70% | | 80k SOL and up | 0.25% | 0.50% | | 100k SOL and up | 0.20% | 0.30% | \* Up to 0.20% of each trade pays LPs. The LP rate scales with the share of pool liquidity that real LPs provide, so with no LPs it is zero. - The creator share lands in the creator's wallet at trade time. There is nothing to claim. - Referral rewards and the referred-trader discount are paid from the protocol share. - Launching itself carries no protocol fee. - During the anti-sniping window, early buys pay the decaying fee described under [Launch protection](/docs/trading#launch-protection) on top of these rates. All of it goes to the creator. ## Permissionless integration referrals An external client can set its payout wallet as the public swap's referrer and receive the default **15% of Tribe's protocol fee** without an account or approval. At the common under-10k-SOL tier, with no real LP liquidity, that works out to approximately **0.30% of the trade's SOL side**. The reward splits the existing protocol fee and does not add a new trader fee. This integration referral is separate from the invite-based referral tree. It has one payout wallet per swap, no trader discount, no multi-level split, and no 180-day attribution window. See [Permissionless swaps](/docs/integrate/permissionless-swaps#earn-from-every-referred-swap) for the default and performance tiers. --- # Integrate permissionless swaps Source: /docs/integrate/permissionless-swaps # Integrate permissionless swaps Tribe's native AMM is open to any Solana client. Submit the `swap` instruction directly to the onchain program. Anyone can sell from launch; permissionless buys open when the token reaches the onchain threshold or age gate described under [Launch protection](/docs/trading#launch-protection). Mainnet program: `B1x53qgNmAdZMfPVZvu89qDmNn3RpdKajRFXqCzE7UPU` Everything needed to discover a market, read a quote, build a buy or sell, and attribute a referral is available onchain. The guides use Anchor, Solana web3.js, Token-2022 helpers, and the swap IDL. AI agents can read this page as [raw Markdown](/docs/raw/integrate/permissionless-swaps), start from the [documentation index](/docs/llms.txt), or load [all documentation in one text file](/docs/llms-full.txt). ## Earn from every referred swap Set your own Solana wallet as `referral` when you build the `swap` instruction. The program pays your share to that wallet inside the same transaction. The referral wallet does not sign, and there is nothing to claim. Every integration starts at the same default tier: **15% of Tribe's protocol fee** on each attributed swap. At the common under-10k-SOL market-cap fee tier, when no real LP liquidity is present, this is approximately **0.30% of the trade's SOL side**. The exact payout changes with the token's live [fee tier](/docs/fees) and LP share because the referral is a share of the protocol fee, not a fixed percentage of trade volume. Integrations that demonstrate good trading volume through their clients can receive a higher onchain tier for their referral wallet. Everyone starts at 15%, and upgrades are performance-based. The integration instructions will be updated when higher tiers are assigned. To attribute a swap: 1. Pass your payout wallet as `referral`. It must differ from the trader. Prefund it once and keep it at or above the rent-exempt minimum. 2. Set `referrer_tier_account` to `protocol_vault`. The instruction requires this account slot, and `protocol_vault` tells the program to apply the default 15% tier. You do not need a separate tier account for the default tier. 3. Read the `SwapCompleted` event or the wallet's SOL balance change to reconcile the payout. Referral rewards are paid automatically by the program. > The current starting referral rate is 15% of Tribe's protocol fee. The docs will be updated if the starting rate changes. ## Choose a path | Goal | Start here | | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | Resolve a mint and read live quote inputs | [Read market state onchain](/docs/integrate/onchain-market-state) | | Build the shared transaction and wallet-signing layer | [Build a swap transaction](/docs/integrate/swap-transaction) | | Add an exact-input buy or sell | [Build TypeScript swap flows](/docs/integrate/typescript-swaps) | | Generate a client in another language | Use the swap IDL and account table below | | Add Tribe as an aggregator route | Index Token accounts, cache confirmed state, implement the quote math, and submit the same swap | ## Swap contract The integration surface is intentionally narrow: one instruction, its argument type, and the state decoders required to resolve swap accounts and calculate current fees and quotes. The inline IDL excludes launch, admin, privileged referral, LP, and protocol-management instructions. The swap IDL is embedded below so a person or AI agent can read the exact instruction, account order, discriminators, and required state layouts directly from this page. Use the **Copy IDL** button to copy it as JSON. ### What the swap IDL contains | Section | Why it is necessary | | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | `address` and `metadata` | Pin the mainnet program and identify the Anchor schema and program version for compatible tooling. Metadata grants no extra capability. | | `swap` instruction | Its discriminator selects the permissionless handler, while its 17 account metas define signer, writable, PDA, and fixed-program requirements. | | `SwapArgs` | Defines the exact Borsh layout for amount, slippage limit, buy or sell direction, and ExactIn or ExactOut mode. | | Account discriminators | The two eight-byte tags let a client prove it is decoding the expected `Token` or `FeeTierConfig` state. | | `Token` | Provides the onchain token ID, mint, creator, selected fee tier, curve state, reserves, and launch state used to validate the market and calculate a quote. | | `FeeTierConfig` and `Breakpoint` | Define the live fee schedule selected by the Token account. `Breakpoint` is present because `FeeTierConfig` contains an array of that nested type. | Each included account struct is complete because standard IDL decoders deserialize fields in order. Removing an apparently unused field, padding, or reserved bytes would shift later offsets or make the decoder reject valid onchain state. Instructions, accounts, events, and errors unrelated to a swap remain omitted. The `protocol` account appears in the instruction's account list because every swap must pass that PDA to the program. Its internal fields are not in the swap IDL because an integration neither decodes nor changes them to discover a market, calculate a quote, or build the instruction. ```json { "address": "B1x53qgNmAdZMfPVZvu89qDmNn3RpdKajRFXqCzE7UPU", "metadata": { "name": "tribe_amm", "version": "0.5.0", "spec": "0.1.0", "description": "Tribe AMM" }, "instructions": [ { "name": "swap", "discriminator": [ 248, 198, 158, 145, 225, 117, 135, 200 ], "accounts": [ { "name": "trader", "writable": true, "signer": true }, { "name": "protocol", "pda": { "seeds": [ { "kind": "const", "value": [ 112, 114, 111, 116, 111, 99, 111, 108 ] } ] } }, { "name": "token", "writable": true }, { "name": "mint" }, { "name": "sol_vault", "writable": true }, { "name": "token_vault", "writable": true }, { "name": "pool_authority" }, { "name": "trader_token_account", "writable": true }, { "name": "creator", "writable": true }, { "name": "protocol_vault", "writable": true }, { "name": "fee_tier_config" }, { "name": "token_2022_program", "address": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" }, { "name": "system_program", "address": "11111111111111111111111111111111" }, { "name": "event_authority" }, { "name": "program" }, { "name": "referral", "writable": true }, { "name": "referrer_tier_account" } ], "args": [ { "name": "_args", "type": { "defined": { "name": "SwapArgs" } } } ] } ], "accounts": [ { "name": "FeeTierConfig", "discriminator": [ 9, 18, 93, 9, 32, 89, 131, 0 ] }, { "name": "Token", "discriminator": [ 131, 254, 39, 144, 4, 179, 134, 127 ] } ], "types": [ { "name": "Breakpoint", "type": { "kind": "struct", "fields": [ { "name": "sqrt_price", "type": "u128" }, { "name": "total_fee_rate", "type": "u32" }, { "name": "creator_fee_rate", "type": "u32" }, { "name": "lp_fee_rate", "type": "u32" }, { "name": "_reserved", "type": { "array": [ "u8", 4 ] } } ] } }, { "name": "FeeTierConfig", "type": { "kind": "struct", "fields": [ { "name": "index", "type": "u64" }, { "name": "start_total_fee_rate", "type": "u32" }, { "name": "start_creator_fee_rate", "type": "u32" }, { "name": "start_lp_fee_rate", "type": "u32" }, { "name": "active_breakpoint_count", "type": "u8" }, { "name": "_padding", "type": { "array": [ "u8", 3 ] } }, { "name": "breakpoints", "type": { "array": [ { "defined": { "name": "Breakpoint" } }, 15 ] } }, { "name": "_reserved", "type": { "array": [ "u8", 64 ] } } ] } }, { "name": "SwapArgs", "type": { "kind": "struct", "fields": [ { "name": "amount", "type": "u64" }, { "name": "limit", "type": "u64" }, { "name": "is_buy", "type": "bool" }, { "name": "swap_mode", "type": "u8" } ] } }, { "name": "Token", "type": { "kind": "struct", "fields": [ { "name": "token_id", "type": "u64" }, { "name": "creator", "type": "pubkey" }, { "name": "mint", "type": "pubkey" }, { "name": "sqrt_price_x64", "type": "u128" }, { "name": "liquidity", "type": "u128" }, { "name": "virtual_liquidity", "type": "u128" }, { "name": "created_at", "type": "i64" }, { "name": "paused", "type": "bool" }, { "name": "bump", "type": "u8" }, { "name": "sol_vault_bump", "type": "u8" }, { "name": "token_vault_bump", "type": "u8" }, { "name": "lp_liquidity", "type": "u128" }, { "name": "fee_growth_global_sol", "type": { "array": [ "u8", 32 ] } }, { "name": "virtual_token_amount", "type": "u64" }, { "name": "lp_fee_reserved_sol", "type": "u64" }, { "name": "lp_position_count", "type": "u64" }, { "name": "fee_tier", "type": "pubkey" }, { "name": "anti_snipe_until", "type": "i64" }, { "name": "anti_snipe_start_rate", "type": "u32" }, { "name": "permissionless_buy_unlocked", "type": "bool" }, { "name": "_reserved", "type": { "array": [ "u8", 115 ] } } ] } } ] } ``` ### Swap arguments | Argument | Type | Meaning | | ----------- | ------ | ------------------------------------------------------------- | | `amount` | `u64` | Input amount for ExactIn; desired output amount for ExactOut. | | `limit` | `u64` | Minimum output for ExactIn; maximum input for ExactOut. | | `is_buy` | `bool` | `true` for SOL to token; `false` for token to SOL. | | `swap_mode` | `u8` | `0` for ExactIn; `1` for ExactOut. | ## Account map 1. Start with the token mint. Resolve its Token account and onchain token ID using [the filtered account lookup](/docs/integrate/onchain-market-state#resolve-a-mint). 2. Derive every PDA from the seed table. Use the trader's Token-2022 associated token account for the mint. 3. For no referral, pass `protocol_vault` as both `referral` and `referrer_tier_account`. For attribution, pass your prefunded payout wallet as `referral` and `protocol_vault` as `referrer_tier_account`. Self-referral is rejected. 4. Decode the Token account and its `FeeTierConfig` from fresh confirmed state before computing `amount` and `limit`. Add a compute-unit limit and priority fee, then simulate before asking the wallet to sign. | PDA account | Seeds in order | Expected owner | | ----------------- | -------------------------------- | ---------------- | | `protocol` | `"protocol"` | Tribe program | | `protocol_vault` | `"protocol_vault"` | System Program | | `token` | `"token", token_id (u64 LE)` | Tribe program | | `sol_vault` | `"sol_vault", token_id (u64 LE)` | System Program | | `token_vault` | `"token_vault", mint, token` | Token-2022 | | `fee_tier_config` | `"fee_tier", index (u64 LE)` | Tribe program | | `pool_authority` | `"pool_authority"` | Address-only PDA | | `event_authority` | `"__event_authority"` | Address-only PDA | Pass the Token account's `creator` and `fee_tier` keys directly. Pass the Token-2022 program, System Program, Tribe program, mint, trader, trader token account, and derived accounts in the order declared by the swap IDL. ## Safety checklist 1. Validate each account by role: Tribe state is owned by the Tribe program, token accounts are Token-2022-owned, SOL vaults are System-owned, and address-only PDAs are checked by derivation rather than owner. Verify the mint and executable program IDs separately. 2. Read and simulate with `confirmed` commitment; refresh the quote before asking the wallet to sign. 3. Never use zero as a convenience slippage limit. Reject stale quotes and surface simulation errors. 4. Always configure a compute-unit limit and a nonzero, current priority fee before submission. 5. Treat Token-2022 as required. A legacy SPL Token associated account is not interchangeable. 6. The permissionless instruction is `swap`. The separate privileged referral path requires a Tribe admin signature and is not an integration surface. --- # Read market state onchain Source: /docs/integrate/onchain-market-state # 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: 1. Resolves a mint to its Token account with a filtered program-account lookup. 2. Reads the Token account's onchain token ID, creator, curve, launch, and fee-tier keys. 3. Decodes the selected `FeeTierConfig` and chooses the rate active at the current price. 4. Reads the token and SOL vault balances needed for buy and sell checks. 5. 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 ```bash pnpm add @anchor-lang/core@1.0.2 @solana/web3.js@1.95.3 @solana/spl-token@0.4.14 bn.js@5.2.3 buffer@6.0.3 ``` Copy the **swap IDL** from [Integrate permissionless swaps](/docs/integrate/permissionless-swaps#swap-contract) 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`. ```ts 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(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(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. ```ts 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 { 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(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 { 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 1. Call `loadOnchainMarket` immediately before calculating a quote. 2. Build the transaction from that same returned account graph. 3. Simulate before wallet signing. If simulation reports stale price, fee, gate, or balance state, reload and requote instead of weakening the user's limit. 4. 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. --- # Build a swap transaction Source: /docs/integrate/swap-transaction # Build a swap transaction Buys and sells use the same `swap` instruction and account graph. Only the direction, quote amounts, and trader token-account preparation differ. This page builds a reusable `tribe-swap.ts` module. It expects the `TribeMarket` returned by [Read market state onchain](/docs/integrate/onchain-market-state) and a quote returned by the [TypeScript swap flows](/docs/integrate/typescript-swaps) guide. The module: 1. Validates the referral recipient and prevents self-referral. 2. Uses the trader's Token-2022 associated token account. 3. Creates that account idempotently for a buy, or verifies its mint, owner, and balance for a sell. 4. Sets `referrer_tier_account` to `protocol_vault` for the default referral tier. 5. Adds a compute-unit limit and a current nonzero priority fee. 6. Simulates before the wallet is asked to sign. ## Wallet boundary Your wallet connector must provide Anchor with `publicKey`, `signTransaction`, and `signAllTransactions`. Wallet Standard, wallet-adapter, mobile-wallet, injected-wallet, bot-keypair, and custody integrations can adapt to that interface. The referral address is a public payout address and never signs the swap. Only the trader's connected wallet signs. Configure `TRIBE_REFERRER` with the Solana address that should receive your integration's fees. Fund that wallet to the rent-exempt minimum before the first attributed swap. ## Build an exact-input swap ```ts import { AnchorProvider, BN, Program, type Idl } from "@anchor-lang/core"; import { ComputeBudgetProgram, Connection, PublicKey, SystemProgram, Transaction, type TransactionInstruction, } from "@solana/web3.js"; import { TOKEN_2022_PROGRAM_ID, createAssociatedTokenAccountIdempotentInstruction, getAccount, getAssociatedTokenAddressSync, } from "@solana/spl-token"; import swapIdl from "./tribe-swap-idl.json"; import { TRIBE_PROGRAM_ID, type TribeMarket } from "./tribe-market"; export type ExactInSwapQuote = { amountIn: bigint; amountOut: bigint; limit: bigint; creatorFee: bigint; grossProtocolFee: bigint; lpFee: bigint; totalFee: bigint; newSqrtPriceX64: bigint; }; type Wallet = ConstructorParameters[1]; async function validateReferralRecipient(connection: Connection, referral: PublicKey): Promise { const info = await connection.getAccountInfo(referral, "confirmed"); const rentMinimum = await connection.getMinimumBalanceForRentExemption(0, "confirmed"); if (!info || !info.owner.equals(SystemProgram.programId) || info.data.length !== 0) { throw new Error("Referral must be an existing zero-data System Program wallet"); } if (info.lamports < rentMinimum) { throw new Error(`Referral wallet must retain at least ${rentMinimum} lamports`); } } async function currentPriorityFee(connection: Connection, writableAccounts: PublicKey[]): Promise { const samples = await connection.getRecentPrioritizationFees({ lockedWritableAccounts: writableAccounts }); const values = samples.map((sample) => sample.prioritizationFee).sort((a, b) => a - b); if (values.length === 0) throw new Error("RPC returned no current priority-fee samples"); return Math.max(1, values[Math.ceil((values.length - 1) * 0.75)] ?? 1); } export async function buildExactInSwap(input: { connection: Connection; wallet: Wallet; market: TribeMarket; trader: PublicKey; referral: PublicKey; quote: ExactInSwapQuote; isBuy: boolean; }): Promise<{ transaction: Transaction; blockhash: string; lastValidBlockHeight: number }> { const { market } = input; const hasReferral = !input.referral.equals(market.accounts.protocolVault); if (hasReferral) { if (input.referral.equals(input.trader)) throw new Error("The referral wallet must differ from the trader"); await validateReferralRecipient(input.connection, input.referral); } const traderTokenAccount = getAssociatedTokenAddressSync(market.mint, input.trader, false, TOKEN_2022_PROGRAM_ID); const setupInstructions: TransactionInstruction[] = []; if (input.isBuy) { setupInstructions.push( createAssociatedTokenAccountIdempotentInstruction( input.trader, traderTokenAccount, input.trader, market.mint, TOKEN_2022_PROGRAM_ID, ), ); } else { const traderTokens = await getAccount(input.connection, traderTokenAccount, "confirmed", TOKEN_2022_PROGRAM_ID); if (!traderTokens.mint.equals(market.mint) || !traderTokens.owner.equals(input.trader)) { throw new Error("Trader Token-2022 account has the wrong mint or owner"); } if (traderTokens.amount < input.quote.amountIn) throw new Error("Insufficient token balance for sell"); } const provider = new AnchorProvider(input.connection, input.wallet, { commitment: "confirmed" }); const program = new Program(swapIdl as Idl, provider); const swapMethod = program.methods["swap"]; if (!swapMethod) throw new Error("Swap IDL did not expose the swap method"); const swapInstruction = await swapMethod({ amount: new BN(input.quote.amountIn.toString()), limit: new BN(input.quote.limit.toString()), isBuy: input.isBuy, swapMode: 0, }) .accountsPartial({ trader: input.trader, protocol: market.accounts.protocol, token: market.accounts.token, mint: market.mint, solVault: market.accounts.solVault, tokenVault: market.accounts.tokenVault, poolAuthority: market.accounts.poolAuthority, traderTokenAccount, creator: market.creator, protocolVault: market.accounts.protocolVault, feeTierConfig: market.accounts.feeTierConfig, token2022Program: TOKEN_2022_PROGRAM_ID, systemProgram: SystemProgram.programId, eventAuthority: market.accounts.eventAuthority, program: TRIBE_PROGRAM_ID, referral: input.referral, referrerTierAccount: market.accounts.protocolVault, }) .instruction(); const priorityFee = await currentPriorityFee(input.connection, [ market.accounts.token, market.accounts.solVault, market.accounts.tokenVault, ]); const transaction = new Transaction().add( ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 }), ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priorityFee }), ...setupInstructions, swapInstruction, ); const latest = await input.connection.getLatestBlockhash("confirmed"); transaction.feePayer = input.trader; transaction.recentBlockhash = latest.blockhash; return { transaction, blockhash: latest.blockhash, lastValidBlockHeight: latest.lastValidBlockHeight }; } ``` For a real referral, pass your funded payout wallet as `referral`. To build without attribution, pass `market.accounts.protocolVault`; the builder then uses the sentinel for both referral account slots. Every integration currently uses the default 15% referral tier. The instruction requires `referrer_tier_account`, so the builder always passes `protocol_vault`. No separate tier account is needed. ## Validate without signing An AI agent can verify transaction construction without controlling a private key: 1. Use an existing funded account as the read-only `trader` and fee payer. A buy needs enough SOL; a sell also needs the market's Token-2022 associated account and sufficient tokens. 2. Give Anchor a read-only wallet object with that `publicKey` and signing methods that always throw. 3. Build the transaction normally, then call `connection.simulateTransaction(transaction)`. Do not enable signature verification. 4. Require `simulation.value.err` to be `null`, record logs and compute units, and stop. Do not call a signing method or submit the transaction. This proves that discovery, quote math, account metas, priority fees, balance checks, and the swap instruction are valid against current mainnet state. It does not authorize a trade. ## Simulate, sign, and submit ```ts export async function submitSwap(input: { connection: Connection; wallet: Wallet; transaction: Transaction; blockhash: string; lastValidBlockHeight: number; }): Promise { const simulation = await input.connection.simulateTransaction(input.transaction); if (simulation.value.err) { throw new Error(`Swap simulation failed: ${JSON.stringify(simulation.value.err)}`); } const signed = await input.wallet.signTransaction(input.transaction); const signature = await input.connection.sendRawTransaction(signed.serialize(), { maxRetries: 3, skipPreflight: false, }); const confirmation = await input.connection.confirmTransaction( { signature, blockhash: input.blockhash, lastValidBlockHeight: input.lastValidBlockHeight, }, "confirmed", ); if (confirmation.value.err) { throw new Error(`Swap confirmation failed: ${JSON.stringify(confirmation.value.err)}`); } return signature; } ``` If simulation reports stale price, fee, gate, vault, or balance state, reload the onchain market and rebuild. Never silently lower the user's slippage protection. ## Verify the referral The referral wallet is writable but does not sign. Its reward is transferred directly in SOL and requires no claim. 1. Record its SOL balance before submission. 2. Confirm the transaction. 3. Read its balance again, or decode `SwapCompleted`. 4. At the default tier, the increase is `floor(gross_protocol_fee × 150_000 / 1_000_000)`. Referral rewards do not increase the trader's fee; they split the existing protocol portion. --- # Build TypeScript swap flows Source: /docs/integrate/typescript-swaps # Build TypeScript swap flows This guide contains complete exact-input flows for both swap directions: - **Buy:** spend an exact amount of SOL and receive at least the quoted token limit. - **Sell:** send an exact number of token base units and receive at least the quoted SOL limit. Both directions use the same market loader, transaction builder, `swap` instruction, account graph, referral configuration, and submission path. Their quote math remains separate because buys charge fees on SOL input and may use launch protection, while sells charge fees on SOL output and enforce circulation and vault constraints. Before this page: 1. Add `tribe-market.ts` from [Read market state onchain](/docs/integrate/onchain-market-state). 2. Add `tribe-swap.ts` from [Build a swap transaction](/docs/integrate/swap-transaction). 3. Copy the [swap IDL](/docs/integrate/permissionless-swaps#swap-contract) as `tribe-swap-idl.json`. AI agents can consume this guide as [raw Markdown](/docs/raw/integrate/typescript-swaps), or load it together with every dependency from [the complete documentation](/docs/llms-full.txt). ## Buy: SOL to token All SOL amounts are lamports. Tribe token amounts use 9 decimal places. Use `bigint` throughout; JavaScript `number` cannot safely represent the onchain curve values. An exact-input buy applies fees to the SOL input, sends the remaining SOL through the curve, and uses the user's slippage setting to produce the minimum token `limit` passed to the program. During a token's first minute, a non-creator buy can carry a decaying anti-sniping surcharge. The calculation below reads Solana's Clock sysvar and uses a two-second earlier time only for the fee-sensitive slippage floor, making the quote conservative while the surcharge decays. Create `tribe-buy.ts`: ```ts import { Connection, PublicKey } from "@solana/web3.js"; import { activeFeeRates, loadOnchainMarket, readChainUnixTime, type FeeRates, type TribeMarket } from "./tribe-market"; import { buildExactInSwap, submitSwap, type ExactInSwapQuote } from "./tribe-swap"; const FEE_DENOMINATOR = 1_000_000n; const Q128 = 1n << 128n; const MIN_SQRT_PRICE = 4_295_048_016n; const MAX_SQRT_PRICE = 79_226_673_521_066_979_257_578_248_091n; const MIN_TRADE_LAMPORTS = 10_000n; const U64_MAX = 18_446_744_073_709_551_615n; function ceilDiv(numerator: bigint, denominator: bigint): bigint { return (numerator + denominator - 1n) / denominator; } export function resolveBuyRates(market: TribeMarket, trader: PublicKey, chainNow: bigint): FeeRates { const tier = activeFeeRates(market); if (trader.equals(market.creator)) return tier; if ( market.antiSnipeUntil === 0n || market.antiSnipeUntil <= market.createdAt || chainNow >= market.antiSnipeUntil || market.antiSnipeStartRate <= tier.total ) { return tier; } const effectiveTotal = chainNow <= market.createdAt ? market.antiSnipeStartRate : market.antiSnipeStartRate - ((market.antiSnipeStartRate - tier.total) * (chainNow - market.createdAt)) / (market.antiSnipeUntil - market.createdAt); const surcharge = effectiveTotal > tier.total ? effectiveTotal - tier.total : 0n; return { total: tier.total + surcharge, creator: tier.creator + surcharge, lp: tier.lp }; } export function quoteExactInBuy( market: TribeMarket, amountIn: bigint, slippageBps: number, rates: FeeRates, ): ExactInSwapQuote { if (amountIn < MIN_TRADE_LAMPORTS || amountIn > U64_MAX) throw new Error("Invalid buy amount"); if (!Number.isInteger(slippageBps) || slippageBps < 1 || slippageBps >= 10_000) { throw new Error("slippageBps must be an integer from 1 through 9999"); } if (market.liquidity <= 0n) throw new Error("InsufficientLiquidity"); const totalFee = ceilDiv(amountIn * rates.total, FEE_DENOMINATOR); const creatorFee = (amountIn * rates.creator) / FEE_DENOMINATOR; const lpFee = market.lpLiquidity === 0n || rates.lp === 0n ? 0n : ceilDiv(amountIn * rates.lp * market.lpLiquidity, market.liquidity * FEE_DENOMINATOR); const grossProtocolFee = totalFee - creatorFee - lpFee; if (grossProtocolFee < 0n) throw new Error("Invalid fee schedule"); const netSol = amountIn - totalFee; if (netSol <= 0n) throw new Error("TradeTooSmall"); const denominator = market.liquidity + netSol * market.sqrtPriceX64; const newSqrtPriceX64 = ceilDiv(market.liquidity * market.sqrtPriceX64, denominator); if (newSqrtPriceX64 <= MIN_SQRT_PRICE || newSqrtPriceX64 >= MAX_SQRT_PRICE) { throw new Error("PriceOutOfRange"); } const amountOut = (market.liquidity * (market.sqrtPriceX64 - newSqrtPriceX64)) / Q128; if (amountOut <= 0n || amountOut > market.tokenVaultAmount) throw new Error("InsufficientLiquidity"); const limit = (amountOut * (10_000n - BigInt(slippageBps))) / 10_000n; if (limit <= 0n) throw new Error("Slippage floor is zero"); return { amountIn, amountOut, limit, creatorFee, grossProtocolFee, lpFee, totalFee, newSqrtPriceX64, }; } type Wallet = Parameters[0]["wallet"]; export async function executeExactInBuy(input: { connection: Connection; wallet: Wallet; mint: PublicKey; trader: PublicKey; referral: PublicKey; amountIn: bigint; slippageBps: number; }): Promise { const market = await loadOnchainMarket(input.connection, input.mint); const chainNow = await readChainUnixTime(input.connection); const rates = resolveBuyRates(market, input.trader, chainNow - 2n); const quote = quoteExactInBuy(market, input.amountIn, input.slippageBps, rates); const built = await buildExactInSwap({ connection: input.connection, wallet: input.wallet, market, trader: input.trader, referral: input.referral, quote, isBuy: true, }); return submitSwap({ connection: input.connection, wallet: input.wallet, ...built }); } ``` `executeExactInBuy` reloads current state, quotes, builds, simulates, asks the connected wallet to sign, submits, and confirms at `confirmed` commitment. Production clients must collect `amountIn` and `slippageBps` from the user rather than hardcoding them. ### Buy gate behavior The Token account exposes `permissionlessBuyUnlocked`: 1. When it is `true`, the monotonic buy gate is already open. 2. When it is `false`, the next buy can still open it if the onchain age or threshold rule has become eligible. 3. The quote remains valid curve math in either case. The unsigned simulation in `submitSwap` is the authoritative gate check and happens before wallet signing. 4. If simulation returns a gate error, show it and wait for eligibility. Do not ask the user to sign and do not replace the limit. Sells never use this buy gate. ## Sell: token to SOL Tribe token amounts use 9 decimal places, so one token is `1_000_000_000` base units. SOL output is in lamports. Keep all arithmetic in `bigint`. A sell uses the same `swap` instruction as a buy with these arguments: | Argument | Sell value | | ----------- | ------------------------------ | | `amount` | Exact token base units to sell | | `limit` | Minimum net SOL lamports out | | `is_buy` | `false` | | `swap_mode` | `0` for ExactIn | Sells are available from launch and do not use the permissionless-buy gate or anti-sniping surcharge. An exact-input sell: 1. Enforces the token's onchain virtual-circulation floor. 2. Moves the curve price using the token input. 3. Calculates gross SOL output with floor rounding. 4. Deducts the active creator, LP, and gross protocol fees from that SOL output. 5. Checks the SOL vault can fund the full gross output while retaining rent and reserved LP fees. 6. Applies the user's slippage setting to net SOL output to produce `limit`. Create `tribe-sell.ts`: ```ts import { Connection, PublicKey } from "@solana/web3.js"; import { activeFeeRates, loadOnchainMarket, type TribeMarket } from "./tribe-market"; import { buildExactInSwap, submitSwap, type ExactInSwapQuote } from "./tribe-swap"; const FEE_DENOMINATOR = 1_000_000n; const MAX_SQRT_PRICE = 79_226_673_521_066_979_257_578_248_091n; const MIN_TRADE_LAMPORTS = 10_000n; const TOTAL_SUPPLY = 1_000_000_000_000_000_000n; const U64_MAX = 18_446_744_073_709_551_615n; function ceilDiv(numerator: bigint, denominator: bigint): bigint { return (numerator + denominator - 1n) / denominator; } function maxSellableTokens(market: TribeMarket): bigint { if (market.tokenVaultAmount > TOTAL_SUPPLY) throw new Error("Invalid token vault balance"); const externallyCirculating = TOTAL_SUPPLY - market.tokenVaultAmount; return externallyCirculating > market.virtualTokenAmount ? externallyCirculating - market.virtualTokenAmount : 0n; } export function quoteExactInSell(market: TribeMarket, amountIn: bigint, slippageBps: number): ExactInSwapQuote { if (amountIn <= 0n || amountIn > U64_MAX) throw new Error("Invalid sell amount"); if (!Number.isInteger(slippageBps) || slippageBps < 1 || slippageBps >= 10_000) { throw new Error("slippageBps must be an integer from 1 through 9999"); } if (amountIn > maxSellableTokens(market)) throw new Error("InsufficientLiquidity"); if (market.liquidity <= 0n) throw new Error("InsufficientLiquidity"); const newSqrtPriceX64 = market.sqrtPriceX64 + (amountIn << 128n) / market.liquidity; if (newSqrtPriceX64 <= market.sqrtPriceX64 || newSqrtPriceX64 >= MAX_SQRT_PRICE) { throw new Error("PriceOutOfRange"); } const grossSolOut = (market.liquidity * (newSqrtPriceX64 - market.sqrtPriceX64)) / (market.sqrtPriceX64 * newSqrtPriceX64); if (grossSolOut < MIN_TRADE_LAMPORTS || grossSolOut > U64_MAX) throw new Error("TradeTooSmall"); const rates = activeFeeRates(market); const totalFee = ceilDiv(grossSolOut * rates.total, FEE_DENOMINATOR); const creatorFee = (grossSolOut * rates.creator) / FEE_DENOMINATOR; const lpFee = market.lpLiquidity === 0n || rates.lp === 0n ? 0n : ceilDiv(grossSolOut * rates.lp * market.lpLiquidity, market.liquidity * FEE_DENOMINATOR); const grossProtocolFee = totalFee - creatorFee - lpFee; if (grossProtocolFee < 0n) throw new Error("Invalid fee schedule"); const amountOut = grossSolOut - totalFee; if (amountOut <= 0n) throw new Error("TradeTooSmall"); const protectedLamports = market.rentExemptLamports + market.lpFeeReservedSol; const spendableLamports = market.solVaultLamports > protectedLamports ? market.solVaultLamports - protectedLamports : 0n; if (grossSolOut > spendableLamports) throw new Error("InsufficientVaultBalance"); const limit = (amountOut * (10_000n - BigInt(slippageBps))) / 10_000n; if (limit <= 0n) throw new Error("Slippage floor is zero"); return { amountIn, amountOut, limit, creatorFee, grossProtocolFee, lpFee, totalFee, newSqrtPriceX64, }; } type Wallet = Parameters[0]["wallet"]; export async function executeExactInSell(input: { connection: Connection; wallet: Wallet; mint: PublicKey; trader: PublicKey; referral: PublicKey; amountIn: bigint; slippageBps: number; }): Promise { const market = await loadOnchainMarket(input.connection, input.mint); const quote = quoteExactInSell(market, input.amountIn, input.slippageBps); const built = await buildExactInSwap({ connection: input.connection, wallet: input.wallet, market, trader: input.trader, referral: input.referral, quote, isBuy: false, }); return submitSwap({ connection: input.connection, wallet: input.wallet, ...built }); } ``` `executeExactInSell` reloads current state, quotes, confirms the trader's Token-2022 balance, builds, simulates, asks the connected wallet to sign, submits, and confirms at `confirmed` commitment. ### Why the sell checks differ 1. **No associated-account creation:** a seller must already own the Token-2022 account containing the tokens. The shared builder verifies it instead of creating an empty account. 2. **Virtual-token floor:** the program prevents sells from reducing externally circulating supply below the amount distributed at launch. `maxSellableTokens` mirrors that check from the live token-vault balance. 3. **Gross-output vault check:** fees and trader proceeds all originate in the SOL vault. The full gross SOL output must be available after rent and already-reserved LP fees. 4. **Fees on output:** the curve produces gross SOL first. Total, creator, LP, and protocol fees are then calculated from that gross amount, and the trader's `amountOut` is the remainder. 5. **No buy-only rules:** sell quotes do not read the Clock, permissionless-buy latch, or anti-sniping settings. ## Referral payout Pass your rent-funded integration wallet as `referral`. The shared builder passes `protocol_vault` as `referrer_tier_account`, applying the default 15% share of Tribe's gross protocol fee in either direction. The reward splits the existing protocol fee and does not add a new fee to the trader. ## Integration checklist 1. Reload the Token account, selected fee schedule, token vault, and SOL vault immediately before quoting. 2. Use Token-2022 for the mint, vault, and trader associated account. 3. Keep all arithmetic in `bigint` and mirror the documented floor and ceiling directions. 4. Use the user's nonzero slippage setting to calculate `limit`. 5. Prefund the referral wallet and keep it rent-exempt. 6. Add a current nonzero priority fee and a compute-unit limit. 7. Simulate before wallet signing and reload state after any stale-state failure. 8. For a buy, read the Clock, apply any anti-sniping surcharge, and let simulation enforce the permissionless-buy gate. 9. For a sell, verify the trader's token balance, enforce the virtual-token floor and SOL-vault capacity, and pass `isBuy: false` with `swapMode: 0`. --- # Badges, Tribes, and rewards Source: /docs/community # Badges, Tribes, and rewards ## The GitHub sponsor badge Holders of at least 10,000 tokens are a token's **sponsors**. The sponsor badge is a live image for your repo's README that shows them. Setup is one tap. The app opens a pull request against your repo and merges it for you; once the badge is on your README, your token page shows **Badge live**. The Markdown one-liner is also available in the app for manual setup. The image is rendered from live holder data on every view and links back to your token page on tribe.run. ## Tribes: token chat Tokens with chat enabled have a **Tribe**, a chat for their holders. Hold at least 10,000 tokens (0.001% of supply) and you are in. Membership is automatic, with no invites or caps. The creator is always a member. Sending messages requires a connected GitHub or X account, and the creator can ban members. The same 10,000-token bar drives chat membership and sponsor status on the badge. Chat lives in the mobile app. ## Referrals and invites This section describes referrals created by Tribe invite codes. External clients that send the public onchain swap use the separate [permissionless integration referral](/docs/integrate/permissionless-swaps#earn-from-every-referred-swap), which is single-level and does not use the invite tree or its expiry. Tribe 2.0 is invite-only. Invite codes are single-use, and you earn more by trading and by growing qualified volume on tokens you launch. | Milestone | Invites | | ----------------------------- | ----------------------------------------- | | Trade 1 SOL | 1 invite based on lifetime trading volume | | Trade 10 SOL | 2 more invites | | 20 SOL qualified token volume | 1 more invite | | 40 SOL qualified token volume | 2 more invites | "Qualified token volume" is trading on your token by people outside your invite chain: not you, not people you invited or their invitees, and not the chain of people who invited you. Together, these milestones can unlock up to 6 invite codes. Earned invites currently unlock 72 hours after you sign up; milestones you hit before then are delivered automatically when the cooldown ends. Used invites come back. At the start of each week, Monday 00:00 UTC, if you traded at least 0.5 SOL during the previous week, your invites are topped back up to everything your milestones have earned. Accounts still in the 72-hour cooldown are not refilled; their invites arrive when the cooldown ends. The invite system is new, and the milestones, amounts, and refill rules will likely change as we tune it. When someone signs up with your code they become one of your direct referrals. The people they invite are your 2nd referrals, and theirs are your 3rd. Referred traders get a discount on every trade. You earn a share of the fees from everyone in your referral tree; your cut comes out of the protocol share of their trades, after the discount. | Relationship | Reward | | ---------------- | ---------------------------------------------------------- | | Direct referrals | 30% on every trade they make | | 2nd referrals | 3% on their trades | | 3rd referrals | 2% on their trades | | Launch bonus | 2,500,000 tokens of every token a direct referral launches | Everything pays out automatically, in SOL or tokens, straight to your wallet. There is nothing to claim. For each person you refer, your rewards and their discount run for 180 days from the day they sign up. ## Wallets and accounts Sign in with email, Google, or Apple. Privy creates an embedded Solana wallet for your account, and the same wallet signs on mobile and web. Deposit SOL to your address, withdraw SOL or tokens to any Solana address, and export your Solana private key at any time through Privy's secure export flow. The key is never visible to the app. New accounts are created in the [iOS and Android app](/download) with an invite code. The [web app](/app) is for existing accounts: trade, manage your portfolio, launch tokens, and track your rewards.