Quickstart
Yinsi is a TypeScript-first monorepo: a Next.js app (this site) and four packages — @finch/sdk, @finch/providers, @finch/flightpath, @finch/db. There is no onchain program to build or deploy.
# in the monorepo root
npm install
# server-side environment (never client-side)
# GROQ_API_KEY=… model compute (free tier); OPENROUTER_API_KEY=… also free
# HYPERBOLIC_API_KEY=… paid alternative, used only when no free provider is set
# MONGODB_URI=… optional: registry + memory + ledgers
# SOLANA_RPC_URLS=… optional locally; the public mainnet RPC is rate limited
npm run dev # web app on http://localhost:3000
npm run typecheck # all workspacesThen launch your first agent — observer mode, read-only, safe by default:
import { createAgent, hyperbolic } from "@finch/sdk";
const agent = await createAgent("first-agent")
.describe("Reads balances and reports. Nothing more.")
.model(hyperbolic("meta-llama/Llama-3.3-70B-Instruct"))
.memory({ kind: "ephemeral" })
.tools("balance_native", "token_data")
.wallet({ mode: "observer" }) // read-only — the safe default
.launch();
const result = await agent.run(
"What are the decimals, supply and mint authority of EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v?",
);
console.log(result.output);
console.log(result.steps); // every model + tool step, loggedSDK
An agent is a portable manifest — a finch.manifest/0.1 document: identity, model, memory, tools, permissions, wallet, triggers, budget, deployment, publisher, endpoints, IO schemas. The fluent builder and the visual agent builder emit the same document, and hatch() launches it against live infrastructure. Manifests cannot widen their own permissions: write tools are stripped unless the wallet grants operator mode, RWA interactions are always registry-limited, and simulation is not optional.
// Launch a manifest built in the visual agent builder (/app/build):
import manifest from "./market-watcher.manifest.json";
import { launchFromManifest, hyperbolic } from "@finch/sdk";
const agent = await launchFromManifest(manifest, {
provider: hyperbolic(manifest.model.model),
});Runs return a full trace: output, steps, executions (execution-layer records), usage, and a haltedBy reason — including the kill switch.
Execution layer — Solana
The execution layer (@finch/flightpath) is how agents reach Solana: SOL and SPL balances and transfers, SPL approvals, single instructions to allowlisted programs, Jupiter swaps, token, market and portfolio reads, and approved RWA interactions. An intent carries the exact instructions it will execute, and the policy engine decodes those instructions — not the intent's summary — before anything else happens. Every write follows one path — policy → simulate → (approve) → sign → submit → confirm → log — and produces an idempotent ExecutionRecord.
// Operator mode: bounded writes. The key comes from the RUNTIME env —
// it is never part of a manifest and never the fee wallet's key.
import { createFlightpath } from "@finch/flightpath";
import { createAgent, hyperbolic } from "@finch/sdk";
const flightpath = createFlightpath({
// base58 64-byte secret key, or a JSON array of 64 numbers
operatorKey: process.env.FLIGHTPATH_OPERATOR_KEY,
});
const agent = await createAgent("payments-runner")
.model(hyperbolic("Qwen/Qwen3-235B-A22B"))
.tools("balance_native", "transfer_native")
.wallet({
mode: "operator",
allowances: [{ asset: "native", perDay: "0.1", perTx: "0.02" }], // SOL
allowedPrograms: [], // plain transfers need none; program_invoke and swaps do
allowedRecipients: ["<an address you trust>"],
approvalThreshold: 0.5,
})
.launch({ flightpath });
// every write: policy → simulate → (approval) → sign → submit → confirm → logSimulation is simulateTransaction against current state; a failure stops the intent there, with the program logs that explain it. The signature comes from the operator keypair (FLIGHTPATH_OPERATOR_KEY, runtime only) or, on this site, from the visitor's own wallet — see User-signed execution. A write counts as done only when the network confirms the transaction, and only a confirmed one gets an execution proof.
The execution layer targets Solana mainnet-beta by default. SOLANA_CLUSTER selects devnet or testnet, SOLANA_RPC_URLS lists provider endpoints tried in order — the public mainnet endpoint is the fallback, and it is rate limited — and FLIGHTPATH_FORCE_DEV=1 switches to a labelled devnet target for testing. Yinsi deploys no program of its own: the registry is signed memo transactions on Solana.
Tool catalog
These are the tools a manifest can name. The table is generated from FLIGHTPATH_TOOLS in @finch/flightpath — the same list the runtime hands to models and the agent builder renders — so it cannot drift from the code. Reads carry no risk. Writes are simulated first, checked against the wallet policy, and logged; an agent in observer mode never sees them.
| tool | mode | category | what it does |
|---|---|---|---|
| network_status | read | network | Read live Solana status: cluster, latest slot and block height, epoch and its progress, transactions per second (all and non-vote), observed slot time, the network fee for one signature, recent priority fees, RPC latency and node version. |
| block_read | read | network | Read one block by slot (or the latest confirmed one): block time, block height, transaction count, blockhash and parent slot. Some slots are skipped by their leader and have no block — that is reported, not an error. |
| chain_stats | read | explorer | Whole-network counters: total transactions since genesis, SOL supply (total, circulating, non-circulating), current epoch and progress, TPS and slot time, and the SOL price in USD from Jupiter (null when unavailable). |
| wallet_profile | read | explorer | What an address is: a wallet, a program (with its upgrade authority), a token account, a mint, an account owned by some other program, or empty. Its SOL balance, owner program, size, how many token accounts it holds, and its recent activity window. Lifetime transaction counts are not available from the chain and are not estimated. |
| wallet_transactions | read | explorer | Most recent transactions involving an address, newest first: signature, slot, time, success or failure, fee, this address's SOL change, programs invoked and any memo. |
| wallet_holdings | read | explorer | Every SPL token (SPL Token and Token-2022) an address holds with a non-zero balance, with USD price and value where Jupiter prices it. Unpriced tokens report null rather than a guess. |
| token_profile | read | explorer | A token mint: name, symbol, decimals, supply, mint and freeze authority (null means revoked), token program, plus holder count, USD price, market cap, liquidity, 24h volume and Jupiter's verification flag where known. |
| token_holders | read | explorer | Largest token accounts for a mint (up to 20), largest first: owner, balance, share of total supply in percent, and whether the owner is a program-derived address (typically a pool, vault or program-controlled account). Also the total holder count where Jupiter reports one. |
| token_activity | read | explorer | Recent transactions that reference a mint account, newest first, with each one's balance changes in this token by owner. Transfers that do not pass the mint account (plain unchecked transfers) are not visible here, so this is recent activity, not a complete transfer log. |
| token_list | read | market | Tokens Jupiter ranks by 24h trading on Solana, with USD price, market cap, liquidity, 24h volume, holder count and verification flag where known. |
| token_price | read | market | USD prices from Jupiter for up to 50 mints. A mint Jupiter cannot price is absent from the answer — never zero. |
| token_markets | read | market | Where a token trades: its DEX markets from DexScreener, largest liquidity first — DEX, pair address, base and quote token, price, liquidity in USD, 24h volume and buy/sell counts, market cap and FDV. |
| market_pair | read | market | One DEX pair or pool by its address, from DexScreener: tokens, price, liquidity, 24h volume and trade counts. |
| swap_quote | read | market | A Jupiter route quote for swapping an exact input amount: expected output, the minimum output at the given slippage, price impact and the route's legs. Read-only — nothing is signed. |
| pump_curve | read | market | Read a pump.fun launch straight off its bonding-curve account: whether the mint exists, whether it is still on the curve or has graduated, how far along the curve it is (against the launch parameters read from pump.fun's global config), SOL paid in, tokens left on the curve, spot price on the curve, and the creator. A mint that exists but has no pump.fun curve is reported as not_on_pump. |
| tx_lookup | read | explorer | One transaction by signature: success or failure (with the error), slot, time, fee, compute units, fee payer and signers, programs invoked, SOL and token balance changes, and the last program log lines. |
| program_verified | read | explorer | Whether an address is an executable program, whether it can still be upgraded and by which authority (or is immutable), and whether its build is verified against public source (OtterSec verify API). Unverified is a definite answer, not an error. |
| balance_native | read | balances | Read the SOL balance of an address. |
| balance_spl | read | balances | Read a holder's balance of one SPL token, summed across every token account it owns for that mint. |
| token_data | read | tokens | Read a mint account directly: decimals, supply, mint and freeze authority, token program, and on-chain name and symbol where the mint carries metadata. |
| portfolio_snapshot | read | portfolio | Snapshot the SOL balance plus the balances of a given list of SPL mints for an address. |
| account_read | read | accounts | Raw read of any account: owner program, lamports, whether it is executable, data size, and the decoded data where the RPC can parse it (token accounts, mints, stake, vote, config and similar); otherwise the start of its raw data. |
| rwa_registry | read | rwa | List the tokenized real-world assets (SPL mints) approved for agent interaction, with issuer restrictions. |
| transfer_native | write | transfers | Transfer SOL. Simulated first; bounded by wallet allowances; logged. |
| transfer_spl | write | transfers | Transfer an SPL token (checked transfer, to the recipient's associated token account, which must already exist). Simulated first; bounded by wallet allowances; logged. |
| spl_approve | write | spl | Approve a delegate to spend an amount of an SPL token. Approvals count against allowances. |
| program_invoke | write | programs | Send one raw instruction to a program. The program must be on the allowlist, the only signer may be this agent's wallet, and any System or Token instruction inside must be one the policy can price. |
| swap_exact_in | write | swaps | Swap an exact input amount through Jupiter, with a slippage bound the route enforces on chain. The Jupiter program must be on the allowlist; the input amount counts against the input asset's allowance. |
| rwa_interact | write | rwa | Transfer or approve an approved RWA token. Hard-restricted to the approved registry. |
Model providers
The model layer is a provider abstraction. Hyperbolic serves compute first (hyperbolic(model)), and openAICompatible({…}) binds any standard endpoint — Yinsi is never permanently coupled to one vendor. Providers are server-side only; keys never reach a browser or an agent's own context.
Model compute — free by default
An agent names a model; the environment decides who serves it. Every provider below speaks the same OpenAI-compatible shape, so switching is configuration rather than an integration — which is the point of keeping the model layer abstract.
| provider | cost | env | notes |
|---|---|---|---|
| Groq | free-tier | GROQ_API_KEY | Free tier with rate limits; very fast inference. Good default for public preview traffic. |
| Cerebras | free-tier | CEREBRAS_API_KEY | Free tier with daily limits; fastest token throughput of the hosted options. |
| OpenRouter | free-tier | OPENROUTER_API_KEY | Routes to many models; :free variants cost nothing but are rate limited and can be busy. |
| Google AI Studio | free-tier | GEMINI_API_KEY | Free tier via the OpenAI-compatible endpoint; generous limits for read-only workloads. |
| Ollama (local) | local | ENABLE_OLLAMA | Fully free and fully private — runs on your own machine. No key, no quota, no per-request cost. |
| Together AI | paid | TOGETHER_API_KEY | Paid per token, with occasional free models. |
| Hyperbolic | paid | HYPERBOLIC_API_KEY | Paid per token; open-model serverless inference. |
Selection prefers free tiers first, then local, then paid — so an operator who sets only GROQ_API_KEY gets working previews at no per-visitor cost. FINCH_PROVIDER overrides the order. The app header shows which provider is actually serving, and with none configured previews refuse rather than fabricate a response.
Model ids are env-overridable (GROQ_MODEL, OLLAMA_MODEL, …) because provider catalogs change faster than this page does — treat the defaults as a starting point, not a guarantee.
Data layer — MongoDB
@finch/db owns operational data: agents, swarms, directory listings, execution records, per-signer spend, vector memory, the public treasury ledger, double-entry compute credits, service-call metering and hashed API keys. Unique indexes double as idempotency guarantees. Without MONGODB_URI, the site serves labeled seed data read-only; scripts: npm run seed -w @finch/db, npm run indexes -w @finch/db. MongoDB accelerates the product; Solana stays the source of truth.
Permission model
Every write an agent attempts is evaluated against these rules, in this order, before anything is simulated or signed. The table is generated from POLICY_RULES in @finch/flightpath, and a test asserts that every rule the engine can actually emit appears here — so this cannot drift away from the code.
| rule | verdict | triggers when | why |
|---|---|---|---|
| wallet.mode | deny | The wallet is not in operator mode. | Observer and none-mode agents have no write authority at all. This is the default, so an agent is read-only until you deliberately grant otherwise. |
| instructions.recognized | deny | An instruction cannot be priced: an unchecked token transfer or approval (it names no mint), any other System or Token instruction that can move value (close account, set authority, burn…), or — outside program_invoke and swaps — an instruction to any other program. | The policy reads the instructions the transaction will actually execute. Anything it cannot put a price on does not get to ride along. |
| recipients.allowlist | deny | A counterparty is not on allowedRecipients (when that list is set). | Counterparty means whoever ends up able to move value: a transfer's recipient, an approval's delegate, a swap's output owner — not just transfer destinations. |
| programs.allowlist | deny | A program_invoke or swap calls a program outside allowedPrograms. | Anything that hands instructions to a program must name that program up front. Allowlisting a program is the act of trusting its code. |
| rwa.approved | deny | An RWA interaction targets a mint outside the approved registry. | Permissioned real-world assets are gated to an explicit registry, and a manifest cannot waive the gate. |
| allowance.missing | deny | The intent spends an asset with no configured allowance. | Spending authority is opt-in per asset. No allowance means no spend. |
| allowance.perTx | deny | A single spend exceeds the per-transaction cap. | Caps the blast radius of any one mistake, independently of the daily budget. |
| allowance.daily | deny | The rolling 24h spend would exceed perDay. | Spend is debited at submission, so a transaction that broadcasts always counts even if its confirmation is lost. |
| allowance.approvalThreshold | needs_approval | A spend exceeds approvalThreshold as a fraction of the daily allowance. | Parks the intent at awaiting_approval. Only a recorded human approval releases it — replaying the execution id will not. |
| human.approval | allow | A human approved a parked intent. | Recorded on the execution as who approved and when, and covered by the Proof of Flight hash. |
| default | allow | Every check above passed. | The intent proceeds to mandatory simulation before anything is signed. |
Two of these cannot be configured away. RWA interactions are always gated to the approved registry, and simulation always runs before signing — a manifest has no field that disables either.
Running an agent yourself
A launched manifest does not run on Yinsi — it runs wherever you point a runtime at it. That is the whole portability claim, so here is the entrypoint, in full.
// node --experimental-strip-types run-agent.ts
import { readFileSync } from "node:fs";
import { launchFromManifest, hyperbolic } from "@finch/sdk";
import { createFlightpath } from "@finch/flightpath";
const manifest = JSON.parse(readFileSync("./market-scout.manifest.json", "utf8"));
// Observer execution layer: real Solana reads, no signer, writes denied.
// Pass operatorKey here — and only here — to grant bounded write authority.
const flightpath = createFlightpath({ agentId: manifest.identity.handle });
const agent = await launchFromManifest(manifest, {
provider: hyperbolic(manifest.model.model), // HYPERBOLIC_API_KEY from env
flightpath,
});
const result = await agent.run("What is the current slot on Solana, and how busy is the network?");
console.log(result.output);
console.log(result.steps); // every model + tool step
console.log(result.usage); // tokens in / out
console.log(agent.unresolvedServices); // services the manifest declared but nothing resolvedToday the packages are consumed from the repo rather than from npm: clone it, or vendor packages/sdk, packages/providers and packages/flightpath into your project. They contain no TypeScript that needs compiling away — parameter properties and decorators are deliberately avoided — so node --experimental-strip-types runs them straight from source with no build step. Published npm packages are the next step, not a claim we are making now.
Triggers. A manifest can declare cron and webhook triggers, and they travel with it — but Yinsi does not schedule or receive them. Your host does: read manifest.triggers and wire them to your own scheduler or route handler. Manual is the only trigger this product acts on.
// Yinsi records triggers; your host acts on them.
for (const trigger of manifest.triggers) {
if (trigger.kind === "cron") {
schedule(trigger.schedule, () => agent.run("scheduled tick"));
}
if (trigger.kind === "webhook") {
app.post(`/hooks/${trigger.slug}`, async (req, res) => {
res.json(await agent.run(JSON.stringify(req.body)));
});
}
}Execution proof
An operator can claim anything about what their agent did. An execution proof is the smallest set of facts that lets someone else check the claim: which agent acted, under which policy, in which transaction, in which slot, at what fee — plus a SHA-256 over exactly those facts, so the receipt cannot be quietly edited afterwards.
{
"version": "proof-of-flight/0.2",
"finchId": "execution-agent", // which agent acted
"nestId": "my-swarm", // set when it came from a swarm task
"taskId": "t4",
"action": "transfer.native",
"summary": "transfer 0.01 SOL → <recipient>",
"chain": "solana:mainnet",
"signature": "5x…", // the transaction, base58 — where it happened
"slot": "…",
"feeLamports": "5000",
"computeUnits": "150",
"policy": { "verdict": "allow", "rule": "default" },
"approval": { "approvedBy": "operator", "at": "…" }, // if a human released it
"simulation": { "ok": true, "computeUnits": "150" },
"confirmedAt": "2026-09-26T00:00:03.000Z",
"executionHash": "9f2c…", // sha256 over every field above
"explorerUrl": "https://solscan.io/tx/5x…" // convenience only, not hashed
}The hash is taken over a canonical form with a fixed field order, so the same execution hashes identically on any machine in any language. Signatures are base58 and case-sensitive, so they are hashed exactly as given. Change the slot, the summary, the agent id or the approver and verifyProofOfFlight() fails.
A proof is issued only for an execution that actually confirmed. Pending, denied, reverted, or never-simulated actions throw ProofUnavailableError rather than producing a weaker receipt — that refusal is the whole point, since an execution proof means the execution happened.
import { buildProofOfFlight, verifyProofOfFlight } from "@finch/flightpath";
// record is the ExecutionRecord returned by any execution-layer write
const proof = await buildProofOfFlight(record, { nestId: "my-swarm", taskId: "t4" });
const { valid, expectedHash } = await verifyProofOfFlight(proof);
// valid === false for any edited fieldModel traces and tool logs stay in the execution record offchain; only the 32-byte hash needs anchoring. The network page counts proofs by counting confirmed executions, which is exactly the set for which a proof can be issued.
Publishing — open and free
Anyone can put an agent or a swarm in the directory. There is no charge and nothing to hold; the only requirement is a publisher key, and a key is issued to any Solana wallet that signs a plain message for one. The ed25519 signature proves control of the address; the address — exactly as written, since base58 is case-sensitive — becomes the key's owner; every listing published with it belongs to that wallet and can only be changed by it. One active key per wallet — signing again replaces the old one.
# 1. the wallet signs a plain message (solana:signMessage — not a transaction)
Yinsi publisher key
Address: <your base58 address>
Nonce: <random, 8-64 url-safe chars>
Signing this issues a key for publishing to the Yinsi registry. It is not a transaction.
# 2. exchange the signature for a key — shown once, only its hash is stored
POST /api/keys { "address": "<base58 address>", "nonce": "…", "signature": "<base58, 64 bytes>" }
→ 201 { "key": "finch_…", "owner": "<the same address, exactly>", "scopes": […] }
# 3. publish with it
POST /api/directory headers: x-finch-key: finch_… # a directory listing
POST /api/swarms headers: x-finch-key: finch_… # a swarm: nest.manifest/0.1
POST /api/agents headers: x-finch-key: finch_… # an agent: finch.manifest/0.1The gate is a switch, not an inference. PUBLISH_GATE unset or open is the default and the truth right now. hold turns on a token gate — a publisher must then hold at least PUBLISH_COST_FINCH of the configured token, read live at publish time — and the publish panel and GET /api/publish/status say so the moment it is on.
Published entries carry source: "published"; the network's own analysts carry source: "builtin". Either can be composed into a swarm by reference — { handle, ref: "registry" } — and is hydrated from the directory before the strict manifest schema runs.
A listing is anchored when the registry authority (FINCH_REGISTRY_AUTHORITY) has signed a memo transaction naming it: finch-registry/1 register <kind>:<handle> sha256:<manifest hash> [<uri>]. The index reads that address's signature history and counts a memo only if the authority signed it, so anyone can check a listing — and the manifest's hash — against Solana alone. Until the authority is configured, GET /api/registry reports every listing as not anchored.
Shared memory
Every swarm that runs teaches a shared memory, and every agent reads from it. Shared memory is not a chat log: it accepts only observations with provenance — which run, which swarm, which agent, which channel, and the address the finding is about. An agent recalling a prior finding sees it labelled exactly that way, [prior finding · my-swarm · 3h ago · unverified], so it can build on it without mistaking it for something it verified itself.
Built-in swarms write to shared memory and every swarm reads from it; none ship right now, so what it holds is what earlier runs found. The subject of a finding is the first address in the objective, so a token due-diligence run and a wallet analysis of the same mint meet in the same place. GET /api/memory shows what shared memory holds, with the provenance of every line.
User-signed execution
No key on the server ever signs for a visitor. An agent that is allowed to write — wallet.mode: "operator" with allowances — builds the instructions with the visitor as fee payer, simulates them, then parks the intent at awaiting_signature with the exact prepared instructions. The visitor's own wallet signs and sends the transaction. Nothing on this path turns "the API returned 200" into "the transaction succeeded".
# run an agent that is allowed to write, naming the wallet that will sign
POST /api/playground/run { "preset": "courier", "input": "send 0.01 SOL to <recipient>", "signer": "<your address>" }
→ executions: [{ id: "exec_…", state: "awaiting_signature",
intent: { kind: "transfer.native", summary, to, spendAsset: "native", spendAmount, meta },
simulation: { ok: true, computeUnits, feeLamports, simulatedAt },
prepared: { feePayer: "<your address>", instructions: [{ programAddress, accounts, data }], computeUnits } }]
# fetch the unsigned transaction, with a fresh blockhash
GET /api/executions/exec_…/transaction
→ { id, chain: "solana:mainnet", feePayer, transaction: "<base64 wire bytes>", lastValidBlockHeight }
# the wallet signs and sends it (solana:signAndSendTransaction); hand back the signature
POST /api/executions/exec_…/submitted { "signature": "<base58>", "from": "<your address>" }
→ the landed transaction is compared to `prepared` instruction by instruction:
fee payer · program · accounts and their roles · data.
Wallet-added compute-budget and Lighthouse assertions are tolerated; any other difference is refused and nothing advances.
→ match: { id, state: "confirmed" | "reverted", tx: { signature, submittedAt },
receipt: { status, slot, feeLamports, computeUnits, confirmedAt }, explorerUrl, proof }
reverted = it landed with an instruction error: the fee was paid, nothing else changed
→ confirmed: an execution proof is issued
# later, exactly as stored
GET /api/executions/exec_…States: created → simulated → awaiting_signature → submitted → confirmed | reverted, and every transition is a compare-and-set, so a double submit cannot double count. The per-transaction cap is enforced when the intent is prepared; the daily allowance is kept durably per signer, so the next intent any instance prepares for that wallet sees what it already spent today. Courier is capped at 0.1 SOL per transfer and 0.5 SOL per day. A swarm whose policy is not read-only takes the same signer on POST /api/swarms/run and surfaces its parked writes next to the task that prepared them.
Explorer and market tools
Alongside plain RPC reads, agents can research. The explorer tools read Solana itself — wallet_profile, wallet_transactions, wallet_holdings, tx_lookup, token_profile, token_holders, token_activity, block_read, chain_stats — with Jupiter filling in USD prices, token records and holder counts. token_activity sees the transactions that reference a mint account; transfers that never touch it are not visible there, and the tool's description says so to the agent.
The market tools read Jupiter (token_list, token_price, and swap_quote — a route quote only, nothing is signed) and DexScreener (token_markets, market_pair). program_verified reports whether a program can still be upgraded, and by whom, and whether OtterSec has verified that its deployed build matches its source. A tool that finds nothing returns nothing; the agent is told an empty result is the answer, not a prompt to invent one.
Security model
Custody is simple and layered. Agents hold only restricted operator keypairs funded with a small float, so the most a compromised operator can lose is what it holds — and the PolicyEngine bounds every intent below that: decoded instructions, per-asset allowances and per-transaction caps, program and recipient allowlists, human approval thresholds and kill switches. Visitors sign their own transactions in their own wallets; no key on the server signs for them. The fee wallet's key is held by the operator outside the deployment — never in the repo, the deployed site, or any agent environment — and nothing moves funds automatically. The registry authority's key stays on the operator's machine, used by one script that sends nothing without --send. Production deployment is gated on the audit checklist in AUDIT.md — critical findings block release.
Environment reference
| variable | scope | purpose |
|---|---|---|
| GROQ_API_KEY | server | Free-tier inference. Any one compute key enables previews and swarm runs. |
| CEREBRAS_API_KEY | server | Free-tier alternative; fastest throughput of the hosted options. |
| OPENROUTER_API_KEY | server | Free-tier alternative; :free model variants cost nothing. |
| GEMINI_API_KEY | server | Free-tier alternative via the OpenAI-compatible endpoint. |
| ENABLE_OLLAMA | server | Run inference locally with Ollama — no key, no quota, no per-request cost. |
| FINCH_PROVIDER | server | Force one provider by id. Otherwise Yinsi prefers free tiers automatically. |
| HYPERBOLIC_API_KEY | server | Paid provider. Used only when no free-tier provider is configured. |
| MONGODB_URI | server | Least-privilege user, readWrite on the app database only. Optional — seed fallback without it. |
| MONGODB_DB | server | Database name; defaults to finch. |
| SOLANA_CLUSTER | server | mainnet-beta (the default), devnet or testnet. |
| SOLANA_RPC_URLS | server | Comma-separated RPC endpoints, tried in order. The public mainnet endpoint is the fallback and is rate limited — production lists a provider here. |
| SOLANA_RPC_URL | server | A single endpoint, when there is only one. |
| SOLANA_EXPLORER_URL | server | Explorer used for links; defaults to https://solscan.io. |
| FLIGHTPATH_FORCE_DEV | server | Set to 1 to target devnet, labelled as such everywhere, for testing writes with devnet SOL. |
| FLIGHTPATH_DEV_RPC_URL | server | Devnet endpoint used with FLIGHTPATH_FORCE_DEV. |
| NEXT_PUBLIC_SOLANA_CLUSTER | public | The cluster the browser asks wallets to sign for. Keep it equal to SOLANA_CLUSTER. |
| NEXT_PUBLIC_SOLANA_EXPLORER_URL | public | Explorer base URL for links rendered in the browser. |
| JUPITER_API_URL | server | Jupiter API base for prices, token records and swap quotes; defaults to https://lite-api.jup.ag. |
| JUPITER_API_KEY | server | Optional. Sent as x-api-key for higher rate limits. |
| FLIGHTPATH_OPERATOR_KEY | runtime only | Restricted operator keypair (base58 or a 64-number array). Never in the web app, never the fee wallet. |
| FINCH_FEE_WALLET_ADDRESS | server | Fee-wallet address, display only. |
| FINCH_FEE_WALLET_PRIVATE_KEY | runtime only | Fee-wallet secret key; readable only by src/server/wallet.ts. Never client-side, never logged. |
| FINCH_TOKEN_MINT | server | The mint a token gate checks. Unset by default, and setting it gates nothing — PUBLISH_GATE is the switch. |
| FINCH_REGISTRY_AUTHORITY | server | The address whose signed memos are the registry. Until set, listings are reported as not anchored rather than implied verified. |
| FINCH_REGISTRY_AUTHORITY_KEY | local only | The authority's secret key, in .env.local for scripts/registry-anchor.mjs. Never on the deployment. |
| PUBLISH_GATE | server | open (the default) or hold. hold turns on a token gate: publishing requires holding PUBLISH_COST_FINCH of the FINCH_TOKEN_MINT token, and with no mint set it stays closed. |
| PUBLISH_COST_FINCH | server | Whole-token amount the hold gate requires. |
| RWA_APPROVED_ASSETS | server | JSON array of approved RWA assets for agent interaction; address is the SPL mint. |
Contributing
Protocol-level changes go through improvement proposals — one page: motivation, specification, security considerations. See the current set and the process on the research page. Code contributions follow the repository README; the audit checklist applies to anything touching signers, fees, or permissions.