diff --git a/content/_generated/content.ts b/content/_generated/content.ts index bb57a34b..92fcc685 100644 --- a/content/_generated/content.ts +++ b/content/_generated/content.ts @@ -1,6 +1,6 @@ // AUTO-GENERATED by scripts/build-content.mjs — do not edit. Run `node scripts/build-content.mjs`. import type { Doc, NavNode } from "@/prototype/fixtures"; -export const CONTENT_DOCS: Record = {"/aa/contracts":{"slug":"/aa/contracts","title":"Account-abstraction contracts","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/contracts/src/aa/* (+ contracts/src/edu/Forwarder.sol)","syncedSha":"9d5959e","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"CitrateWallet","anchor":"citratewallet"},{"depth":3,"text":"CitrateWalletFactory","anchor":"citratewalletfactory"},{"depth":3,"text":"CitratePaymaster","anchor":"citratepaymaster"},{"depth":3,"text":"CitrateECDSAValidator","anchor":"citrateecdsavalidator"},{"depth":3,"text":"WebAuthnP256Validator","anchor":"webauthnp256validator"},{"depth":3,"text":"GuardianRecoveryModule","anchor":"guardianrecoverymodule"},{"depth":3,"text":"Forwarder, EIP-2771, cross-reference","anchor":"forwarder-eip-2771-cross-reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The contract reference for Citrate Keyring, the account-abstraction stack that gives a Citrate user a\nsmart-contract account with no seed phrase. This is the on-chain half: the account implementation, the\naccount factory, the gas paymaster, two signer validators, and the guardian recovery module. It pairs with\nthe concept pages at [passkeys](/aa/passkeys), [guardians](/aa/guardians), [paymaster](/aa/paymaster), and\n[identity](/aa/identity), and with the [JavaScript SDK](/sdks/js) that builds calldata against these\ncontracts.\n\n## What it is\n\nCitrate Keyring is an ERC-4337 v0.7 account-abstraction stack built on the ZeroDev Kernel v3.3 account\n(ERC-7579 modules). A user does not hold a private key for an externally owned account. Instead they hold a\nsigner, a passkey or an EOA, that authorizes operations against a smart-contract account deployed for them.\n\nThe mental model has three pieces. The account contract (`CitrateWallet`) is one deployed implementation;\nevery user account is a minimal proxy that delegate-calls into it. The account factory\n(`CitrateWalletFactory`) deploys those proxies at an address derived from the user's Citrate identity, so\nthe address is known before any deployment and is the same on every device. Validator modules decide which\nsignatures authorize an operation: an ECDSA validator for EOA signers and a WebAuthn-P256 validator for\npasskeys. A paymaster can pay gas under per-account budgets, and a guardian module lets a quorum of trusted\naddresses rotate the signer if the user loses it.\n\n| Contract | Tier | Role |\n|---|---|---|\n| `CitrateWallet` | commercial | The Kernel v3.3 account implementation behind every proxy |\n| `CitrateWalletFactory` | commercial | Identity-keyed CREATE2 deploy of account proxies |\n| `CitratePaymaster` | commercial | Per-account, per-day budgeted gas sponsorship |\n| `CitrateECDSAValidator` | commercial | secp256k1 EOA-signer validator module |\n| `WebAuthnP256Validator` | commercial | Passkey (P-256, WebAuthn) validator module |\n| `GuardianRecoveryModule` | commercial | M-of-N guardian recovery validator module |\n| `Forwarder` (EIP-2771) | commercial | Sponsored meta-transaction forwarder, education surface |\n\nThis whole surface is pre-audit. The contracts carry inline ADR and remediation references and an\nend-to-end Forge test, but they have not completed a final third-party audit, and addresses are not yet\nlisted. The status is honest per contract in [source and verification](#source-and-verification) below.\n\n## How to use it\n\nThe lifecycle a surface drives, from signup to a first sponsored operation:\n\n1. Derive the account address offline from the user's Citrate identity, before anything is deployed, with\n `CitrateWalletFactory.predictAddress(userId)`.\n2. Obtain a deploy permit from the identity authority, then call `deployFor(...)` on the factory. The first\n operation carries this as its ERC-4337 `initCode`, so the account deploys itself on first use.\n3. Register the new account with the paymaster (`registerWallet`), called by the registrar, so the\n paymaster will sponsor it.\n4. Submit operations through the bundler. The account's installed validator checks the signature; the\n paymaster pays gas under the budget for the category the operation is tagged with.\n\nThe [JavaScript SDK](/sdks/js) builds the calldata for every step. The\n[sign in with a passkey](/aa/tutorials/sign-in-with-a-passkey) tutorial walks the full path.\n\n## Reference\n\n### CitrateWallet\n\n`contracts/src/aa/wallet/CitrateWallet.sol`. A Citrate-named adapter over ZeroDev's Kernel v3.3 account\n(MIT). The factory deploys ERC-1967 minimal proxies that delegate-call into one deployment of this\ncontract; each user account is one such proxy. The Kernel v3 surface is preserved unchanged, so the\nvalidators and the recovery module install and execute through the standard ERC-7579 module ABI. The\nconstructor takes the EntryPoint v0.7 address as an immutable and passes it to Kernel. The adapter does not\noverride the EIP-712 domain: signatures are already separated by the proxy address and chain id. It exists\nto carry a Citrate-named symbol in artifacts and logs, and to hold any future Citrate-specific account\nstate in its own assembly storage slot rather than touching the vendored submodule.\n\n### CitrateWalletFactory\n\n`contracts/src/aa/factory/CitrateWalletFactory.sol`. Per `ADR-2026-06-05-ew-surface-interop`, the account\naddress must be stable across signer changes, so the CREATE2 salt is derived from the Citrate `userId`\nalone, `keccak256(abi.encodePacked(userId))`, and init data is deliberately not mixed into the salt. The\ntrade-off, that a third party could otherwise deploy someone's `userId` with hostile init data, is closed\nby requiring every deploy to carry an EIP-191 signature from a configured `identitySigner` operated by the\nidentity authority. The signature commits to `(this contract, chainId, userId, keccak256(initData),\nexpiresAt)`, so a leaked permit cannot be reused across users, deploys, or networks.\n\n| Function | Visibility | Purpose |\n|---|---|---|\n| `predictAddress(userId)` | view | The deterministic account address; offline-computable |\n| `deployFor(userId, initialValidator, initData, expiresAt, signature)` | payable | Permit-gated deploy; idempotent, returns the existing account if already deployed |\n| `permitDigest(userId, initData, expiresAt)` | view | The digest the identity signer signs |\n| `setIdentitySigner(newSigner)` | owner | Rotate the permit signer |\n| `transferOwnership(newOwner)` | owner | Transfer ownership |\n\n`initialValidator` is informational, recorded in the event for dashboards; `initData` is the source of\ntruth for which validator the account installs. The deploy uses Solady `LibClone.createDeterministicERC1967`\nover a 95-byte minimal proxy. Immutable: `implementation`. State: `identitySigner`, `owner`. Events:\n`AccountDeployed`, `IdentitySignerRotated`, `OwnerTransferred`. Errors: `ImplementationNotDeployed`,\n`PermitExpired`, `InvalidSigner`, `InitializeFailed`, `ZeroAddress`, `NotOwner`.\n\n### CitratePaymaster\n\n`contracts/src/aa/paymaster/CitratePaymaster.sol`, extending `@account-abstraction` `BasePaymaster`. Per\n`ADR-2026-06-05-ew-paymaster-policy` it sponsors gas in three categories, and every sponsored operation\ncarries a signed suffix on `paymasterAndData` after the ERC-4337 v0.7 prefix of paymaster address and two\npacked gas limits: a one-byte category at offset 52 (`0x00` standard, `0x01` recovery, `0x02` first-op), a\n`[validAfter, validUntil]` window, and a 65-byte ECDSA signature from the paymaster's `sponsorSigner`. The\nsignature binds the chain id, this paymaster, the sender, the category, the window, and the operation nonce,\nso it is single-use for one operation. Standard and recovery operations additionally require a registered\naccount; first-op is authorized by the signature alone, so a counterfactual account's first operation is\nsponsorable before registration. All budgets are denominated in WEI.\n\n| Surface | Members |\n|---|---|\n| Hooks (override) | `_validatePaymasterUserOp`, `_postOp` |\n| Admin (owner) | `setRegistrar`, `setSponsorSigner`, `setPaused`, `setDailyCap`, `setRecoveryEventCap`, `setFirstOpCap`, `setRecoveryDailyCountCap`, `setMaxFeePerGasCeiling`, `setGlobalDailyCap` |\n| Registrar only | `registerWallet(account)`, `unregisterWallet(account)` |\n| Views | `todayKey()`, `remainingStandard(account)`, `sponsorDigest(...)`, plus `dailyUsage`, `isRegistered`, `hasUsedFirstOp`, `registrar`, `sponsorSigner`, `paused`, `dailyCap`, `recoveryEventCap`, `firstOpCap`, `recoveryDailyCountCap`, `maxFeePerGasCeiling`, `globalDailyCap` |\n\n`_validatePaymasterUserOp` fails closed: it reverts when paused, when the sponsor signature is missing or\ndoes not recover to `sponsorSigner`, when the current time is outside the signed window, when the operation's\n`maxFeePerGas` exceeds `maxFeePerGasCeiling`, when the day's aggregate spend would exceed `globalDailyCap`,\nwhen a standard or recovery account is not registered, when the category tag is missing or unknown, or when\nthe relevant per-account budget cannot cover the EntryPoint-reported `maxCost`. Every budget is reserved\nduring validation so that same-bundle sibling operations cannot each pass against a stale counter. A standard\noperation draws from a per-account daily WEI allowance that resets at the next UTC day; recovery draws a\nper-event budget, bounded by a per-account daily recovery-op count, that never touches the daily counter; the\nfirst operation is sponsored once per account under a per-call cap. `_postOp` trues the reserved cost up to\nthe actual gas cost and flips the first-op flag. Events: `WalletRegistered`, `WalletUnregistered`,\n`RegistrarSet`, `SponsorSignerSet`, `SponsorshipUsed`, `PausedSet`, `DailyCapSet`, `RecoveryEventCapSet`,\n`FirstOpCapSet`, `RecoveryDailyCountCapSet`, `MaxFeePerGasCeilingSet`, `GlobalDailyCapSet`. The full policy\nand the bundler topology are on [paymaster](/aa/paymaster).\n\n### CitrateECDSAValidator\n\n`contracts/src/aa/validators/CitrateECDSAValidator.sol`, an `IValidator` and `IHook` module that binds one\nowner EOA per install. This is the path a surface uses to enroll an existing local EOA as an authorized\nsigner on the account without importing the EOA's private key; the EOA simply signs operation hashes.\nInstall data is 21 bytes, `address owner | uint8 source`, where `source` is metadata only (`Unknown`,\n`GuiNative`, `WalletExtension`, `Other`) for dashboard display. `validateUserOp` accepts a raw 65-byte\nECDSA signature over the operation hash or the EIP-191 prefixed variant. EIP-1271 is served by\n`isValidSignatureWithSender`. Lifecycle: `onInstall`, `onUninstall`, `isModuleType`, `isInitialized`; the\nhooks `preCheck` and `postCheck` are no-ops. View: `ownerOf(smartAccount)`. Events: `OwnerRegistered`,\n`OwnerUninstalled`. Errors: `AlreadyInstalled`, `InvalidInstallData`, `InvalidOwner`.\n\n### WebAuthnP256Validator\n\n`contracts/src/aa/validators/WebAuthnP256Validator.sol`, an `IValidator` and `IHook` passkey module that\nstores one P-256 passkey per install: a public key `(x, y)`, a `credentialIdHash`, and a\n`requireUserVerification` flag. Install data is 97 bytes, `bytes32 credentialIdHash | uint256 x | uint256 y\n| uint8 requireUserVerification`; the contract reverts on any other length and on a zero key.\n`validateUserOp` ABI-decodes `(authenticatorData, clientDataJSON, challengeLocation, responseTypeLocation,\nr, s)` and delegates to the vendored Daimo WebAuthn library, which checks the authenticator flags (user\npresence, and user verification if required), that the client-data type is `webauthn.get`, that the\nchallenge equals the operation hash, and the P-256 signature itself. EIP-1271 is served by\n`isValidSignatureWithSender`. View: `passkeyOf(smartAccount)`. Events: `PasskeyRegistered`,\n`PasskeyUninstalled`. Errors: `AlreadyInstalled`, `InvalidInstallData`, `PreCheckSenderMismatch`. The\nverification helpers live under `contracts/src/aa/lib/webauthn/` (`WebAuthn.sol`, `P256.sol`,\n`Base64URL.sol`). See [passkeys](/aa/passkeys).\n\n### GuardianRecoveryModule\n\n`contracts/src/aa/recovery/GuardianRecoveryModule.sol`, an `IValidator` and `IHook` module for M-of-N\nrecovery. Per `ADR-2026-06-05-ew-recovery` the user nominates N guardians at install (minimum 2, maximum 7)\nand a threshold M; install data is `uint8 threshold | uint8 count | address[count]`, and duplicate or zero\nguardians revert. Citrate is never a guardian. `validateUserOp` expects a signature blob of exactly\n`threshold × 65` concatenated ECDSA signatures over the digest `keccak256(userOpHash || account)`, bound to\nboth the operation and the account so a recovery signature cannot be replayed on another account that\nshares a guardian. Each guardian counts once, deduplicated by a bitmap, and both raw and EIP-191 signature\nshapes are tried. EIP-1271 returns `ERC1271_INVALID`, because recovery is an operation-only path. Lifecycle:\n`onInstall`, `onUninstall`. View: `configOf(smartAccount)` returns `(threshold, guardians[])`. Events:\n`GuardiansRegistered`, `GuardiansUninstalled`. Errors: `AlreadyInstalled`, `InvalidInstallData`,\n`InvalidGuardianCount`, `InvalidThreshold`, `DuplicateGuardian`, `ZeroGuardian`, `MalformedSignatureBlob`.\nThe chain enforces only M-of-N; the SDK constrains the action to a signer rotation. See\n[guardians](/aa/guardians).\n\n### Forwarder, EIP-2771, cross-reference\n\n`contracts/src/edu/Forwarder.sol`. The EIP-2771 meta-transaction forwarder for sponsored student actions\nlives in the education stack, not under `aa/`. It is the relayer path for the classroom surface, distinct\nfrom the ERC-4337 stack above, and is documented here only as a cross-reference; it is not part of Citrate\nKeyring and is not relocated.\n\n```solidity\n// 1. Deploy the account for a Citrate user (permit signed off-chain by the identity authority).\naddress account = factory.deployFor(userId, ecdsaValidator, initData, expiresAt, sig);\n\n// 2. Register it with the paymaster (called by the registrar / factory).\npaymaster.registerWallet(account);\n\n// 3. The account then submits sponsored operations through the bundler with the\n// category tag: 0x00 standard, 0x01 recovery, 0x02 first-op.\n```\n\n## Design rationale\n\nThe address is derived from the Citrate identity alone, not from the initial signer, because a user should\nsee one account address on every device and keep it when they rotate a signer or add a passkey. That choice\nopens a deploy-squatting risk, which the required identity-signer permit closes: a deploy is only valid if\nthe identity authority signed off on the exact init data and an expiry. Sponsorship is budgeted and fails\nclosed rather than open, so a misconfigured or exhausted budget refuses an operation at validation rather\nthan silently draining the paymaster. Recovery binds each guardian signature to both the operation and the\naccount, so guardians shared across accounts cannot be turned into a cross-account replay. The account\nitself is a thin adapter over an audited upstream account, which keeps Citrate-specific code in one file and\nlets upstream patches arrive through the submodule.\n\n## Failure modes\n\n- A deploy with an expired or wrong-signer permit reverts (`PermitExpired`, `InvalidSigner`); the account\n is never created with hostile init data.\n- An unregistered account, a missing or unknown category tag, or a budget too small for `maxCost` reverts\n at `_validatePaymasterUserOp`; the operation is refused, not sponsored on credit.\n- The first-op category is single-use per account (`FirstOpAlreadyUsed`), so it cannot be replayed to dodge\n the daily cap.\n- A WebAuthn install with the wrong length or a zero key reverts (`InvalidInstallData`); a high-`s`\n signature is rejected by the P-256 verifier, which the SDK pre-empts by normalizing `s`.\n- A recovery blob of the wrong length, a non-guardian signer, or a repeated guardian fails validation; the\n signer rotation does not execute.\n\n## Access and canon\n\nCommercial tier. This is the implementation depth a competitor would want to clone, identity-keyed deploy,\nfail-closed sponsorship, per-surface validators, and recovery, so it is gated to contracted builders. No\nsecrets appear here: `identitySigner`, `registrar`, and `owner` are roles, not keys, and no private keys,\nmnemonics, or internal endpoints are present. The identity authority is named as an operator role. Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity.\n\n## Source and verification\n\nSource repo `citrate-chain`, files under `contracts/src/aa/`: `wallet/CitrateWallet.sol`,\n`factory/CitrateWalletFactory.sol`, `paymaster/CitratePaymaster.sol`,\n`validators/CitrateECDSAValidator.sol`, `validators/WebAuthnP256Validator.sol`,\n`recovery/GuardianRecoveryModule.sol`, and `lib/webauthn/{WebAuthn,P256,Base64URL}.sol`. The EIP-2771\nforwarder is `contracts/src/edu/Forwarder.sol`. Audited against SHA `9d5959e`.\n\nStatus: Implemented, pre-audit. The contracts exist and pass an end-to-end Forge test under\n`contracts/test/aa/`, but have not had a final external audit and are not yet deployed at listed addresses.\nDo not custody material value on this surface until the audit closes.\n"},"/aa/guardians":{"slug":"/aa/guardians","title":"Guardians and social recovery","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-identity/src/aa (guardians.ts, guardian-routes.ts, install-data.ts) + contracts/src/aa/recovery/GuardianRecoveryModule.sol","syncedSha":"9664fa8","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"Nomination rules","anchor":"nomination-rules"},{"depth":3,"text":"HTTP routes","anchor":"http-routes"},{"depth":3,"text":"GuardianRecoveryModule","anchor":"guardianrecoverymodule"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Social recovery is how a person gets back into their Citrate Keyring after losing their signing device,\nwithout a seed phrase and without trusting Citrate to hold a spare key. The person names a small set of\npeople they trust, their guardians, and a threshold of those guardians can together approve a recovery. If\nyou build the recovery experience, this is the page you build against.\n\n## What it is\n\nA person nominates between two and seven guardians, each an address they control or trust, and an M-of-N\nthreshold. If they lose their device, M of their N guardians sign a recovery operation that rotates the\naccount's signing key to a new one. The on-chain `GuardianRecoveryModule` enforces the threshold; the\nidentity service holds the nomination until it can ride on-chain with the account's first deploy.\n\nOne rule sits above all of this, and it is enforced in code, not just stated as policy: **Citrate is never\na guardian.** A person chooses their own guardians. The nomination service stores only the addresses the\nperson chose, and it defensively refuses the authority's own signer address if it is ever submitted, with a\nclear error citing `ADR-2026-06-05-ew-recovery`. The recovery contract grants no role to the deployer or to\nanyone other than the account. Recovery is non-custodial; there is no key Citrate could hand over or be\ncompelled to hand over.\n\n## How to use it\n\nThe flow has three steps: nominate, install, recover.\n\n1. **Nominate.** From the page shown just after sign-in, the person posts their chosen guardians and\n threshold to `POST /auth/guardians`. The request is gated by the live sign-in interaction cookie, the\n same gate the password and passkey routes use, so the nomination binds to the authenticated account.\n\n ```ts\n await fetch('https://auth.citrate.ai/auth/guardians', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n guardians: ['0xGuardianA', '0xGuardianB', '0xGuardianC'],\n threshold: 2, // two of three\n }),\n });\n ```\n\n2. **Install.** The SDK reads the nomination back from `GET /aa/guardians`, gated by the caller's own\n access token. When the recovery-module address is configured, the response carries a ready-to-use\n `initConfig` entry: a Kernel `installModule` call for the `GuardianRecoveryModule`. The SDK appends that\n entry to the account's `initialize` calldata, so guardians are installed at the account's first deploy\n with no extra transaction.\n\n ```ts\n const res = await fetch('https://auth.citrate.ai/aa/guardians', {\n headers: { authorization: `Bearer ${accessToken}` },\n });\n const { nominated, guardians, threshold, initConfig } = await res.json();\n // append initConfig (when present) to the account's initialize() calldata\n ```\n\n3. **Recover.** If the device is lost, the person builds a recovery operation whose call data rotates the\n account to a fresh signing key. M guardians sign the recovery digest, and their signatures are\n concatenated into the operation's signature field. The module verifies them on-chain and, if at least M\n distinct guardians signed, the rotation succeeds. Recovery operations are sponsored from a separate\n budget so they work even when a person's daily sponsorship is spent; see [Paymaster](/aa/paymaster).\n\n## Reference\n\n### Nomination rules\n\nOff-chain, `normalizeNomination` in `citrate-identity/src/aa/guardians.ts` validates a nomination before it\nis stored. The same bounds are enforced again on-chain so a person sees a failure at nomination time, not as\na revert at deploy.\n\n| Rule | Value |\n|---|---|\n| Guardian count | between 2 and 7 |\n| Threshold M | an integer in `[1, N]`, where N is the guardian count |\n| Duplicates | rejected |\n| Address form | normalized to lowercase, each a valid address |\n| Forbidden | the authority's own signer address is refused, \"the Citrate authority cannot be a guardian\" |\n\nThe install payload is packed by `guardianInstallData` in `src/aa/install-data.ts` as `uint8 threshold |\nuint8 count | address[count] guardians`, two bytes followed by twenty bytes per guardian. The same 2-to-7\nand `[1, N]` bounds are enforced there too.\n\n### HTTP routes\n\n| Route | Method | Gate | Source |\n|---|---|---|---|\n| `/auth/guardians` | POST | sign-in interaction cookie | `src/aa/guardian-routes.ts` |\n| `/aa/guardians` | GET | Bearer access token, own subject | `src/aa/guardian-routes.ts` |\n\n### GuardianRecoveryModule\n\nThe contract is at `contracts/src/aa/recovery/GuardianRecoveryModule.sol`. It is a Kernel module that acts\nas both a validator and a hook.\n\n| Function | Purpose |\n|---|---|\n| `onInstall(bytes data)` | reads `threshold \\| count \\| guardians`, checks count in `[2, 7]` and threshold in `[1, N]`, stores the config, emits `GuardiansRegistered` |\n| `onUninstall(bytes)` | clears the config, emits `GuardiansUninstalled` |\n| `configOf(address account)` | returns the threshold and guardian list in install order |\n| `isInitialized(address account)` | true when a config is stored |\n| `isModuleType(uint256 typeID)` | true for the validator and hook module types |\n| `validateUserOp(PackedUserOperation op, bytes32 opHash)` | the recovery check: succeeds when at least M distinct guardians signed |\n| `isValidSignatureWithSender(...)` | always rejects; recovery is an operation-only path, not a sign-anything path |\n\nThe threshold model is stored as `uint8 threshold` (M) and `uint8 count` (N) with a fixed `address[7]`\nguardian slot. In `validateUserOp` the module computes a recovery digest over the operation hash bound to\nthe account, expects M concatenated 65-byte signatures, recovers each one, and matches it against the\nguardian set. Both plain key signatures and EIP-1271 contract signatures are honored, so a guardian can be\na person's key or another smart-contract account. A bitmap tracks which guardians have signed, so a repeated\nsignature from the same guardian does not count twice. Validation succeeds only when the count of distinct\nconfirming guardians reaches M. There is no timelock; the check is synchronous within the operation.\n\n## Design rationale\n\nA seed phrase is a single point of failure that a person carries alone. Social recovery spreads that trust\nacross people the person already knows, with a threshold so that no single guardian can move the account and\nlosing one guardian does not lock the person out. We cap guardians between two and seven because the lower\nbound rules out a one-guardian setup that is no better than a single key, and the upper bound keeps the\non-chain signature check cheap, M signatures of 65 bytes each, with a one-byte bitmap big enough for seven.\nWe forbid Citrate from being a guardian, in code, because the moment the authority could approve a recovery\nit would become a custodian and a target; keeping that impossible is the point of the design. Installing the\nmodule at first deploy means guardians cost the person no extra transaction.\n\n## Failure modes\n\n- **Below threshold.** If fewer than M guardians sign, `validateUserOp` returns failure and the rotation\n does not happen. Recovery fails closed.\n- **A guardian signs twice.** The signing bitmap counts each guardian once, so duplicate signatures cannot\n reach the threshold on their own.\n- **A submitted guardian is the authority.** The nomination is rejected with a clear error before it is\n stored, and again at install if it somehow reached the chain.\n- **Bounds at the edge.** Counts outside 2 to 7, or a threshold outside `[1, N]`, are rejected both\n off-chain and on-chain, so a person sees the error at nomination rather than at deploy.\n- **Secrets.** The nomination service stores only the addresses the person chose. No key or credential\n appears in Citrate Almanac.\n\n## Access and canon\n\nCommercial. The recovery experience and the install seam are the depth a contracted builder needs. The\npublic, conceptual account of recovery lives alongside [Passkeys](/aa/passkeys), and the on-chain account\nmodel is in [contracts](/aa/contracts). Recovery is non-custodial by construction: Citrate holds no\nguardian role and no spare key.\n\n## Source and verification\n\n| Surface | Source | Status |\n|---|---|---|\n| Nomination rules and store | `citrate-identity/src/aa/guardians.ts` | Implemented (pre-audit) |\n| HTTP routes | `citrate-identity/src/aa/guardian-routes.ts` | Implemented (pre-audit) |\n| Install payload encoder | `citrate-identity/src/aa/install-data.ts` | Implemented (pre-audit) |\n| Recovery module | `contracts/src/aa/recovery/GuardianRecoveryModule.sol` | Implemented (pre-audit) |\n| End-to-end recovery | `test/aa/GuardianRecoveryE2E.t.sol` | Verified (testnet 40204) |\n\nOff-chain surfaces verified against `citrate-identity` at `9664fa8`; the recovery contract and its\nend-to-end test verified against the `citrate-chain` contracts repo at `9d5959e`. The \"Citrate is never a guardian\"\ninvariant is enforced in `guardians.ts` and in the contract install. The stack has shipped and is exercised\nend to end, deploy through M-of-N recovery through a fresh-key operation, but has not had an external audit.\nRe-verify against the SHAs before relying on this page.\n"},"/aa/identity":{"slug":"/aa/identity","title":"Citrate Identity, the OIDC issuer","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-identity/src (server.ts, config.ts, siwe.ts, kyc.ts, kyc-engine.ts, entitlements.ts, identity-registry.ts, aa/, auth/)","syncedSha":"9664fa8","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"Verification status, Implemented","anchor":"verification-status-implemented"},{"depth":3,"text":"Entitlement claim, Implemented","anchor":"entitlement-claim-implemented"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Citrate Identity is the sign-in authority for the network. It is an ordinary OpenID Connect provider: a\nperson signs in once, with a passkey, an email and password, a Google account, or by signing a message\nwith their own key, and the service issues standard ID and access tokens that carry their canonical\nCitrate Keyring address. If you build a relying party that needs Citrate sign-in, this is the page you\nintegrate against.\n\n## What it is\n\nCitrate Identity is a generic OpenID Connect issuer built on the panva `oidc-provider` library\n(`src/server.ts`, `src/config.ts`). You talk to it the way you talk to any OIDC provider: read the\ndiscovery document at `/.well-known/openid-configuration`, fetch the signing keys at `/jwks`, and run the\nAuthorization Code flow with PKCE. The reference relying party is CitrateScan, the network explorer, which\nruns as a public client with refresh-token rotation enabled.\n\nThe service knows a person by one of two subject shapes, resolved in `findAccount` (`src/config.ts`):\n\n- A **UUID**, for accounts created by passkey, email and password, or Google sign-in. The Citrate Keyring\n for that person exists as a prediction, a CREATE2 address derived from the user id, until they first\n transact. See [Passkeys](/aa/passkeys).\n- An **EIP-55 address**, for accounts that sign in by proving control of a key, the EIP-4361 flow we call\n SIWE. Here the key is the identity, and a VERI verification result is keyed on the address.\n\nThe service stores almost nothing about a person. It holds sign-in records, the set of addresses a person\nhas linked, and a VERI verification result that is a status and two dates, never the documents behind it.\nVerification is done in-house by VERI, Citrate's own check (`src/kyc-engine.ts`). It is server-side\nprocessing: the server decrypts the identity, document and face evidence to decide the case. Evidence is\nsealed per case at rest, the biometric is destroyed once the decision is reached, and no outside vendor\nholds the personal data. The OIDC service persists only the closed\n`{status, verified_at, expires_at}` record and an opaque case reference. This follows the on-premise default\nthat holds across the network: the public ledger, and the authority in front of it, see only what they must.\n\n## How to use it\n\nYou integrate Citrate Identity as a relying party.\n\n1. Register your client and a redirect URI with the authority.\n2. Read the discovery document and cache the JWKS:\n\n ```bash\n curl -s https://auth.citrate.ai/.well-known/openid-configuration\n curl -s https://auth.citrate.ai/jwks\n ```\n\n3. Send the person to the authorization endpoint with PKCE and the scopes you need. Ask for `wallet` when\n you need the person's Citrate Keyring address, and `kyc` when you need their verification status. The\n access-tier claim rides under `openid`, so you do not request a scope for it:\n\n ```\n GET https://auth.citrate.ai/auth\n ?response_type=code\n &client_id=\n &redirect_uri=\n &scope=openid%20wallet%20kyc\n &code_challenge=&code_challenge_method=S256\n &state=&nonce=\n ```\n\n4. Exchange the returned code at `/token` for an ID token and an access token.\n5. Read claims from the ID token, or call `/userinfo` with the access token. Claims are recomputed on every\n `/userinfo` call, so a verification that was revoked or that expired after the token was minted shows up\n on the next read, not stale at mint time.\n\n## Reference\n\nScopes and the claims they release, defined in `citrate-identity/src/config.ts`:\n\n| Scope | Claims |\n|---|---|\n| `openid` | `sub`, `https://citrate.ai/entitlement` |\n| `profile` | `name`, `email`, `email_verified` |\n| `wallet` | `wallet_address`, `wallet_bound`, `wallets`, `signing_method` |\n| `kyc` | `kyc_status`, `kyc_verified_at`, `kyc_expires_at` |\n| `offline_access` | (enables refresh tokens) |\n\nThe `https://citrate.ai/entitlement` claim rides under `openid`, always granted, rather than behind its own\nscope, so every relying party receives the access tier without asking for it (`src/config.ts`, `claims`\nblock). It is minted only when the principal is on the entitlements roster; when absent the relying party\nfalls back to the public tier.\n\nClaim shapes, derived in `findAccount` (`src/config.ts`) and `src/aa/wallet-claims.ts`:\n\n| Claim | Meaning |\n|---|---|\n| `sub` | the subject: a lowercase UUID, or an EIP-55 address for a SIWE sign-in |\n| `email` | present for accounts that carry an email (email and password, Google) |\n| `email_verified` | whether that email has been proven, gating any entitlement keyed on it |\n| `wallet_address` | the person's one canonical Citrate Keyring address; for a UUID account this is a bound primary address if set, otherwise the CREATE2 prediction; omitted when the account-abstraction environment is unconfigured |\n| `wallet_bound` | `true` when `wallet_address` is a bound primary the person committed to, `false` when it is only the CREATE2 prediction; a relying party that pays this address must require `true` |\n| `wallets` | every address the person has linked, primary first, capped at ten per identity |\n| `signing_method` | the most recent successful sign-in method (`siwe`, `passkey`, `email-pw`, `google`) |\n| `https://citrate.ai/entitlement` | the access-tier grant, `{ tier, orgId, citrateRole?, milestone?, expiresAt? }`, resolved by `resolveEntitlementClaim` (`src/entitlements.ts`); present only for a principal on the roster |\n\nSign-in routes mounted in `src/server.ts`:\n\n| Route | Method | What it does |\n|---|---|---|\n| `/siwe/challenge` | GET | issues a fresh nonce for a message-signing sign-in (`src/siwe-routes.ts`) |\n| `/siwe/verify` | POST | verifies an EIP-4361 message and signature |\n| `/auth/password/register`, `/auth/password/login` | POST | email and password, Argon2id hashing (`src/auth/password-routes.ts`) |\n| `/auth/webauthn/*` | POST | passkey enrollment and sign-in (`src/auth/webauthn-routes.ts`) |\n| `/auth/google/start`, `/auth/google/callback` | GET | Google sign-in, mounted only when both `CITRATE_AA_GOOGLE_CLIENT_ID` and the matching secret are set (`src/auth/google-routes.ts`) |\n| `/identity/:sub/wallets*` | GET, POST, DELETE | link, list, and unlink addresses for an identity, gated to the caller's own subject (`src/identity-registry.ts`) |\n| `/aa/address`, `/aa/enroll-validator`, `/aa/validators` | GET, POST | Citrate Keyring address prediction and validator enrollment (`src/aa/aa-routes.ts`) |\n| `/kyc/_set`, `/kyc/_revoke` | POST | the VERI decision webhook that records or revokes a verification, guarded by a shared secret (`src/kyc-routes.ts`) |\n| `/logout`, `/sessions/events` | POST, GET | revoke a session and stream logout events |\n\n### Verification status, Implemented\n\nThe `kyc` scope releases `kyc_status`, with `kyc_verified_at` and `kyc_expires_at`. The stored status is one\nof `verified`, `pending`, or `revoked` (`KycStatus` in `src/kyc.ts`); `/userinfo` computes two more from the\nrecord, so a relying party can also read `expired` (a verified record whose `expires_at` has passed) or\n`none` (no record at all). A person reaches `verified` after a VERI check; once `expires_at` passes the same\nrecord reads as `expired` and prompts a re-check. The stored record is a closed type: a status, two dates,\nand an opaque case reference, and nothing else. There is no field where a name, a document, or an identifier\ncould be added.\n\nVERI is Citrate's in-house verification. It processes evidence server-side and keeps it sealed at\nrest. The engine (`src/kyc-engine.ts`) decides a captured case with\nno outside call: it unseals the per-case evidence, runs the liveness and 1:1 face-match\nanalyzers, the document OCR and MRZ check, and the in-house sanctions screener, then destroys the biometric\nimmediately and records only the decision. It fails closed: with no model backend a case routes to human\nreview, never to an auto-`verified`. The engine wiring and operator runbook are gated to operators and are\nnot on this page. A relying party consumes only the claim shapes above. See\n[compliance](/enterprise/compliance) for the verification posture.\n\n### Entitlement claim, Implemented\n\nCitrate Almanac decides which gated pages a request may read from the `https://citrate.ai/entitlement` claim,\nwhich names a caller's access tier. The identity service mints it: `resolveEntitlementClaim`\n(`src/entitlements.ts`) looks the principal up in the entitlements roster (a Postgres table keyed on `sub`,\n`wallet`, or a verified `email`) and returns `{ tier, orgId, citrateRole?, milestone?, expiresAt? }`, which\nrides in the token under `openid`. The tiers are `public`, `commercial`, `commercial.kyc`, `academic`, and\n`confidential`. Resolution is fail-safe and KYC-gated: an absent or expired grant mints no claim and the\nrelying party falls back to public; a role-bearing principal (admin, auditor, exec) is authorized by the\nroster without a KYC check; an unverified consumer keeps `public` and `commercial`, a `commercial.kyc` grant\ncollapses to `commercial`, and the higher `academic` and `confidential` tiers require a verified VERI check.\nPassing VERI auto-grants the `commercial.kyc` baseline if the principal has no grant yet\n(`grantKycBaseline`). When no database is configured the service mints no entitlement claim at all. No secret\never rides in any tier.\n\n## Design rationale\n\nWe made the identity authority a plain OIDC provider so that any team that has integrated OIDC before can\nintegrate Citrate sign-in without learning a Citrate-specific protocol. The two subject shapes exist\nbecause two kinds of people arrive: one brings a key and wants the key to be the identity, the other brings\nan email and wants a Citrate Keyring created for them. Holding only a verification status and never the\npersonal data behind it keeps the authority outside the scope of the heaviest data-protection duties, and\nit is the same discipline the rest of the network follows. Recomputing claims on every read, rather than\nfreezing them at mint time, means a revoked verification takes effect promptly instead of lingering for the\nlife of a token.\n\n## Failure modes\n\n- **A stale token after revocation.** Claims are recomputed on each `/userinfo` call, so a relying party\n that re-reads `/userinfo` sees a revocation or expiry promptly. A relying party that trusts only the\n original ID token for the token's full lifetime will lag; re-read for anything verification-sensitive.\n- **Unsafe production configuration.** At boot the service runs `assertProductionConfig` (`src/config.ts`).\n In production it refuses to start if the cookie keys are the development default or shorter than 32\n characters, if the issuer or origins point at localhost, or if the database or session store is unset. It\n fails closed rather than starting in a weak state.\n- **Account-abstraction environment unset.** When the account-abstraction environment is not configured,\n `wallet_address` is simply omitted rather than guessed. A relying party should treat the claim as\n optional.\n- **Secrets.** No client secret, cookie key, signing key, webhook secret, or vendor credential appears in\n Citrate Almanac. They live in operator environment only.\n\n## Access and canon\n\nPublic. The OIDC issuer and the claim shapes are what a relying party needs to integrate, and they are\nstandard. The verification internals are gated to operators. The `https://citrate.ai/entitlement` claim is\nminted by the authority and the tier meanings are public, while the roster of who holds which tier is not.\nVERI is Citrate's in-house, server-side verification. Node and consensus code do not check identity.\nAfter a decision Citrate keeps the verification result, and any retained evidence stays sealed at rest.\n\n## Source and verification\n\n| Surface | Source | Status |\n|---|---|---|\n| OIDC issuer, routes, boot checks | `citrate-identity/src/server.ts`, `src/config.ts` | Implemented (pre-audit) |\n| SIWE sign-in | `citrate-identity/src/siwe.ts`, `src/siwe-routes.ts` | Implemented (pre-audit) |\n| Keyring address-claim derivation | `citrate-identity/src/aa/wallet-claims.ts` | Implemented (pre-audit) |\n| Verification status and record | `citrate-identity/src/kyc.ts`, `src/kyc-pg.ts`, `src/kyc-routes.ts` | Implemented (pre-audit) |\n| VERI verification engine | `citrate-identity/src/kyc-engine.ts` | Implemented (pre-audit) |\n| Identity to address registry | `citrate-identity/src/identity-registry.ts` | Implemented (pre-audit) |\n| `https://citrate.ai/entitlement` claim | `citrate-identity/src/entitlements.ts`, `src/config.ts` | Implemented (pre-audit) |\n\nVerified against `citrate-identity` at `9664fa8`, package version 0.1.0. The service has shipped and runs;\nit has not had an external audit, so the implemented surfaces are pre-audit. The entitlement claim is now\nminted by the authority under `openid` and is KYC-gated. Re-verify against the SHA before relying on this\npage.\n"},"/aa/passkeys":{"slug":"/aa/passkeys","title":"Passkeys and Kernel operations","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-sdk-js/src/aa/ + citrate-chain/contracts/src/aa/","syncedSha":"9d5959e","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"Counterfactual address","anchor":"counterfactual-address"},{"depth":3,"text":"WebAuthnP256Validator, how a passkey authorizes an operation","anchor":"webauthnp256validator-how-a-passkey-authorizes-an-operation"},{"depth":3,"text":"CitrateECDSAValidator, the EOA path","anchor":"citrateecdsavalidator-the-eoa-path"},{"depth":3,"text":"Kernel v3 account and the first operation","anchor":"kernel-v3-account-and-the-first-operation"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"> **Status: passkey-only accounts are not yet available on chain 40204.** Use these pages to build and test against a local chain.\n\nHow a Citrate user signs in with a passkey and sends transactions with no seed phrase, end to end, plus the\nEOA path for users who already hold a signer. This is the builder reference for Citrate Keyring: the\nWebAuthn-P256 validator on chain, the Kernel v3 account, the address that is known before deployment, and\nthe first operation that deploys the account. For the on-chain contract reference see\n[account-abstraction contracts](/aa/contracts); for the runnable walkthrough see\n[sign in with a passkey](/aa/tutorials/sign-in-with-a-passkey).\n\n## What it is\n\nCitrate Keyring is an ERC-4337 v0.7 smart-contract account built on the Kernel v3 account (ERC-7579\nmodules). The model has three parts.\n\n- One user, one account address, derivable offline. A Citrate user id, a UUID or an EOA address for SIWE\n sign-ins, maps deterministically to a single CREATE2 account address. The account is counterfactual: it\n exists at a known address from signup and deploys itself lazily on the first operation.\n- The sign-in is the key. Instead of a seed phrase the user holds a passkey, a WebAuthn P-256 credential\n bound to their device authenticator, or an EOA, a browser, extension, or hardware secp256k1 key. Either\n one installs as a Kernel validator module that authorizes operations.\n- Gas can be sponsored. The [paymaster](/aa/paymaster) can pay gas under per-account daily budgets, so a\n user with a zero balance can still transact.\n\nThe on-chain object is a smart-contract account, never a key the user has to back up. Losing a device is a\nrecovery event, handled by [guardians](/aa/guardians), not a lost-funds event.\n\nThe pieces, and where the truth for each lives:\n\n| Concern | Code |\n|---|---|\n| Address derivation, UUID to userId to CREATE2 | `citrate-sdk-js/src/aa/address.ts` |\n| Passkey (WebAuthn P-256) signing | `citrate-sdk-js/src/aa/webauthn.ts` |\n| EOA (secp256k1) signing | `citrate-sdk-js/src/aa/eoa.ts` |\n| Kernel v3 nonce, execute, install encoders | `citrate-sdk-js/src/aa/kernel.ts` |\n| Operation build, hash, wire conversion | `citrate-sdk-js/src/aa/userop.ts` |\n| Bundler JSON-RPC client | `citrate-sdk-js/src/aa/bundler.ts` |\n| On-chain validators | `contracts/src/aa/validators/{WebAuthnP256Validator,CitrateECDSAValidator}.sol` |\n| Account factory | `contracts/src/aa/factory/CitrateWalletFactory.sol` |\n\n## How to use it\n\nInstall the SDK. The account-abstraction helpers are re-exported from the package root as the `aa`\nnamespace.\n\n```bash\nnpm install @citratelabs/sdk\n```\n\n```typescript\nimport { aa } from '@citratelabs/sdk';\nconst {\n uuidToUserId,\n predictWalletAddress,\n encodeDeployFor,\n packInitCode,\n encodeExecuteSingle,\n buildPackedUserOp,\n getUserOpHash,\n signUserOpWithPasskey,\n signUserOpWithEoa,\n packCitratePaymasterAndData,\n PaymasterCategory,\n BundlerClient,\n} = aa;\n```\n\nYou will need chain 40204 (Citrate), the Citrate identity authority that issues the deploy permit (see\n[identity](/aa/identity)), the Citrate bundler, and, for passkeys, a secure context (HTTPS or `localhost`)\nwhere `navigator.credentials` is available. The end-to-end flow, with the symbol that implements each step,\nall in `citrate-sdk-js/src/aa/`:\n\n1. Derive the userId. `uuidToUserId(citrateUserId)` returns the 32-byte userId,\n `keccak256(utf8(lowercase uuid))` (`address.ts`). For other identity shapes, `accountIdToAaUserId`\n resolves a 32-byte hex, a 20-byte EOA, or a UUID to the userId.\n2. Predict the address. `predictWalletAddress(factory, implementation, userId)` returns the one CREATE2\n address this user has on every surface (`address.ts`); it is the same value the factory's\n `predictAddress` returns on chain.\n3. Build the first operation, which deploys the account. Fetch the permit from the identity authority, then\n `encodeDeployFor({ userId, initialValidator, initData, expiresAt, signature })` and\n `packInitCode(factory, factoryData)` build the `initCode` (`userop.ts`). Later operations use\n `initCode = '0x'`.\n4. Build the call. `encodeExecuteSingle({ to, value, data })` or `encodeExecuteBatch(calls)` (`kernel.ts`).\n5. Set the nonce. Read it from `EntryPoint.getNonce(sender, key)`. For the account's root validator use the\n sequence directly (`rootValidatorNonce`); for an installed validator build the key with\n `validatorNonceKey(validator)` (`kernel.ts`).\n6. Build and hash. `buildPackedUserOp(args)` then `getUserOpHash(op, entryPoint, 40204n)` (`userop.ts`).\n7. Sign. `signUserOpWithPasskey(userOpHash, opts)` in the browser, which drives\n `navigator.credentials.get()`, or `signUserOpWithEoa(signer, userOpHash)` with any ethers signer.\n8. Submit. `new BundlerClient().sendUserOperation(op, entryPoint)`, then\n `waitForUserOperationReceipt(hash)` (`bundler.ts`).\n\n## Reference\n\n### Counterfactual address\n\n`predictWalletAddress` computes the CREATE2 address the factory deploys to, without any chain read. The\nsalt is `keccak256(userId)`; the init code is Solady's 95-byte minimal ERC-1967 proxy with the account\nimplementation embedded, and the address is `keccak256(0xff ++ factory ++ salt ++ initCodeHash)[12..]`\n(`address.ts`, `predictWalletAddress` and `erc1967MinimalInitCodeHash`). The SDK pins this against the live\nfactory on chain 40204 in its unit tests, and it matches the on-chain `predictAddress` byte for byte. The\npractical effect: a user has a stable address from the moment they sign up, before any transaction exists.\n\n### WebAuthnP256Validator, how a passkey authorizes an operation\n\nThe validator verifies a passkey assertion on chain through the vendored Daimo WebAuthn library. The SDK\nencodes `userOp.signature` as `abi.encode(authenticatorData, clientDataJSON, challengeLocation,\nresponseTypeLocation, r, s)` and normalizes `s` into the lower half of the P-256 group order, because the\nverifier rejects malleable high-`s` signatures (`webauthn.ts`, `normalizeP256S` and\n`encodeWebauthnValidatorSignature`). `challengeLocation` is the byte index in `clientDataJSON` where the\nexact substring `\"challenge\":\"\"` begins, and `responseTypeLocation` likewise for\n`\"type\":\"webauthn.get\"`. On chain the operation hash is the WebAuthn challenge, so a passkey assertion is\nonly valid for the one operation it signed. The install payload is the 97-byte\n`credentialIdHash | x | y | requireUserVerification` (`kernel.ts`, `webauthnInstallData`).\n\n`signUserOpWithPasskey` is the browser path: it asks the platform authenticator (Face ID, Touch ID, Windows\nHello) to sign the operation hash, parses the DER ECDSA signature the assertion carries\n(`parseDerEcdsaSignature`), and returns the encoded blob. The pure encoding and parsing helpers are\nexported separately so they can be unit-tested without a browser.\n\n### CitrateECDSAValidator, the EOA path\n\nThe ECDSA validator verifies a 65-byte secp256k1 signature over the operation hash, accepting both the raw\nshape and the EIP-191 personal-sign shape. The SDK's `signUserOpWithEoa` emits the EIP-191 shape via an\nethers `signMessage`, which is what browser signers produce by default (`eoa.ts`). The install payload is\nthe 21-byte `owner | source` (`kernel.ts`, `ecdsaInstallData`, where `source` is the `EcdsaValidatorSource`\nlabel `GuiNative`, `WalletExtension`, or `Other`). This path enrolls an existing local EOA as an authorized\nsigner on the account without importing its private key. One account can hold both a passkey and an EOA\nvalidator; adding guardians is covered in [guardians](/aa/guardians).\n\n### Kernel v3 account and the first operation\n\nThe account is a Kernel v3 modular account (ERC-7579). The factory's `deployFor` runs the account's\n`initialize(bytes21 rootValidator, address hook, bytes validatorData, bytes hookData, bytes[] initConfig)`\non the fresh proxy, installing the chosen validator as the root (`kernel.ts`, `kernelInitializeCalldata`,\n`packValidationId`). Calls are made through Kernel's `execute(bytes32 execMode, bytes executionCalldata)`,\nsingle or batch. The Kernel nonce layout is `1B mode | 1B validator-type | 20B validator | 2B nonceKey | 8B\nsequence`; `validatorNonceKey` builds the 192-bit key that routes an operation to an installed validator,\nand the EntryPoint appends the 8-byte sequence. The first operation carries the factory `initCode`, so the\naccount deploys itself, installs its root validator, and runs its first call in one operation; every later\noperation sets `initCode = '0x'`.\n\n## Design rationale\n\nA passkey is the right default because the private material never leaves the device's secure element, the\nuser authenticates with a fingerprint or face rather than a phrase to copy down, and the same credential\nworks across that user's platform. Verifying P-256 on chain is more work than secp256k1, which is why the\naccount delegates to an audited WebAuthn library and the SDK normalizes `s` rather than asking the verifier\nto accept malleable signatures. The address is derived from the identity rather than the first signer so it\nsurvives signer changes, and the account deploys on first use rather than at signup so an account that is\nnever used costs nothing to create.\n\n## Failure modes\n\n- Outside a secure context, or with no authenticator, `signUserOpWithPasskey` throws\n `WebAuthnSigningError`; run it in a browser served over HTTPS or `localhost`.\n- A passkey assertion whose `clientDataJSON` lacks the expected challenge or is not a `webauthn.get` type is\n rejected by the encoder before it ever reaches the chain.\n- A high-`s` signature would be rejected on chain; the SDK normalizes `s` so a correctly built operation\n does not hit that path.\n- A first operation without the factory `initCode` will fail at the EntryPoint for an undeployed account;\n the deploy and the first call must travel together.\n- The bundler enforces chain 40204; an operation built for another chain id will not verify, because the\n operation hash commits to the chain id.\n\n## Access and canon\n\nPublic tier. This is the open builder reference for Citrate Keyring; a developer needs it to build, and\nnothing here is a secret or a competitive moat. No private keys, mnemonics, credentials, or private\nendpoints appear on this page. The deploy permit is fetched at runtime from the identity authority, whose\nsigning key never leaves it, and passkey private material never leaves the user's authenticator. The\npaymaster policy, caps, and the bundler authentication topology are commercial tier, on\n[paymaster](/aa/paymaster).\n\n## Source and verification\n\n- SDK: `citrate-sdk-js/src/aa/` at SHA `bc5a830`.\n- Contracts: `citrate-chain/contracts/src/aa/` at SHA `9d5959e`.\n- End to end: `citrate-chain/contracts/test/aa/` (operation-hash, WebAuthn, and guardian vectors); the\n SDK pins the address and operation-hash helpers against the live factory and EntryPoint v0.7 on chain\n 40204 in `citrate-sdk-js/tests/unit/`.\n\nStatus: Specified on chain 40204, where passkey-only accounts are not yet available. The validators, factory, paymaster, recovery module, bundler, and the SDK\nencoders shipped in the EW-S1 sprint and have not had a final external audit. Treat the surface as\nexperimental and do not custody material value on it until the audit closes. Re-verify symbols against the\nsource SHAs before relying on this page.\n"},"/aa/paymaster":{"slug":"/aa/paymaster","title":"Paymaster and bundler topology","tier":"public","orgId":null,"sourceKind":"authored","source":"contracts/src/aa/paymaster/CitratePaymaster.sol + citrate-bundler (gate/src, README, Caddyfile)","syncedSha":"9d5959e","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"Categories","anchor":"categories"},{"depth":3,"text":"Caps and accounting","anchor":"caps-and-accounting"},{"depth":3,"text":"Eligibility, the registrar gate","anchor":"eligibility-the-registrar-gate"},{"depth":3,"text":"Bundler topology","anchor":"bundler-topology"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"A paymaster sponsors the gas for an operation so a person can transact with no SALT in hand, which is what\nlets someone use a Citrate Keyring on their first visit. Citrate sponsors gas with a contract paymaster\nunder per-account budgets, fronted by a bundler that authorizes and pre-checks operations at the edge. If\nyou build or operate against sponsored operations, this is the page you work from.\n\n## What it is\n\nThere is no native gas-sponsorship operation on the network; sponsorship is done with an ERC-4337 paymaster\ncontract. Sponsorship has two layers.\n\nThe authoritative layer is `CitratePaymaster`, an ERC-4337 v0.7 paymaster that extends `BasePaymaster`. It\nenforces per-account WEI budgets: every sponsored operation carries a sponsor-signer signature and a\ncategory in `paymasterAndData`, passes its `_validatePaymasterUserOp` check, and settles in `_postOp`. The\nedge layer is the bundler: a self-hosted eth-infinitism reference bundler behind Caddy, fronted by a thin\nCitrate gate that does key authorization, rate limiting, and a paymaster pre-check, so an operation that is\ncertain to fail is refused at the edge instead of taking a bundle slot. The edge is an optimization; the\ncontract re-validates everything.\n\nEvery sponsored operation is authorized by an ECDSA signature from the paymaster's own `sponsorSigner`, not\nby registration alone. The signature covers a digest that binds the chain id, this paymaster, the sender,\nthe category, a `[validAfter, validUntil]` window, and the operation `nonce` (`sponsorDigest`), so a\nsignature is single-use for exactly one operation and cannot be replayed across accounts, chains, paymasters,\ncategories, or operations. Because verifying it touches only the paymaster's own storage and `ecrecover`, a\ncounterfactual account's first operation is mempool-legal under ERC-7562.\n\nA related but separate mechanism serves the education stack. There, sponsored student actions go through an\nEIP-2771 forwarder (`contracts/src/edu/Forwarder.sol`), a meta-transaction relay where a relayer pays gas\non behalf of a device-bound signer. That is a different path from the ERC-4337 paymaster described here; we\nnote it so the two are not confused.\n\n## How to use it\n\nFor an integrator the steps are: tag the operation, send it, watch the budget.\n\n1. **Tag.** Build the operation with a one-byte category in `paymasterAndData`. The SDK does this for you;\n the category names the budget the operation should draw from.\n2. **Send.** Post the operation to the bundler at `https://bundler.citrate.ai/rpc`. When you have a `bk_`\n key, send it as a Bearer token for the higher rate limit.\n3. **Watch.** Read `remainingStandard(account)` on the paymaster to show a person how much of their daily\n sponsorship is left.\n\nA new account's first operation is sponsored under the first-op budget on the strength of the sponsor\nsignature, before the account is registered, so a person can deploy and act without SALT. After that,\nordinary operations draw from a daily allowance, and recovery operations draw from their own budget so\nrecovery is never blocked by a spent daily allowance.\n\n## Reference\n\n### Categories\n\nThe bundler assembles a signed suffix on `paymasterAndData` after the ERC-4337 v0.7 prefix of\n`paymaster(20) | verificationGasLimit(16) | postOpGasLimit(16)`. The full layout the contract reads:\n\n```\n[0:20] address paymaster\n[20:36] uint128 paymasterVerificationGasLimit\n[36:52] uint128 paymasterPostOpGasLimit\n[52] uint8 category (0 standard / 1 recovery / 2 first-op)\n[53:59] uint48 validUntil\n[59:65] uint48 validAfter\n[65:130] bytes65 sponsorSigner ECDSA signature (r || s || v)\n```\n\nThe category byte lives at `PMD_TAG_OFFSET` (52); the sponsor signature is the 65-byte tail. All three\ncategory budgets are denominated in WEI (spend, not gas units), since `requiredPreFund` is `requiredGas ×\nmaxFeePerGas`.\n\n| Tag | Category | Budget behavior |\n|---|---|---|\n| `0x00` | Standard | draws from the per-account daily WEI allowance, `dailyCap`; the counter resets at the first sponsored operation of a new UTC day. Requires the account to be registered |\n| `0x01` | Recovery | draws from a per-event WEI budget, `recoveryEventCap`, that does not touch the daily counter, so recovery works even when the daily allowance is spent, bounded by a per-account daily recovery-op count cap. Requires the account to be registered |\n| `0x02` | First-op | one sponsorship for an account's first operation, bounded by `firstOpCap`; authorized by the sponsor signature rather than registration, so a counterfactual account can spend it; the `hasUsedFirstOp` flag then flips so it cannot be reused |\n\n### Caps and accounting\n\nThe caps live in `CitratePaymaster` as owner-settable WEI values. The as-deployed defaults, set in\n`script/aa/DeployAA.s.sol`, are:\n\n| Cap | Default | Set with |\n|---|---|---|\n| `dailyCap` | 0.01 ether | `setDailyCap` |\n| `recoveryEventCap` | 0.01 ether | `setRecoveryEventCap` |\n| `firstOpCap` | 0.02 ether | `setFirstOpCap` |\n| `globalDailyCap` | 5 ether | `setGlobalDailyCap` |\n| `maxFeePerGasCeiling` | 20 gwei | `setMaxFeePerGasCeiling` |\n\nSetting any per-account cap to `0` disables that category. `globalDailyCap` is an aggregate deposit-spend\nbackstop across every account, a drain guard; `maxFeePerGasCeiling` bounds the fee a single operation may\nclaim, so a generous WEI cap cannot be drained by one inflated-fee operation. Every budget is reserved\nduring validation, not only in `_postOp`, so that several operations from one sender in the same bundle\ncannot each validate against a stale counter; `_postOp` then trues the reservation from `maxCost` up to the\nactual gas cost. The day key is `block.timestamp / 86400`, so counters reset at the UTC day boundary.\n`remainingStandard(account)` returns what is left of the daily allowance for dashboards and the SDK. An\noperator may change any of these on-chain, so treat the numbers above as the shipped defaults, not\nguarantees.\n\nIf the signature, the fee ceiling, or the relevant budget check fails, validation reverts with a precise\nerror rather than sponsoring anyway. It fails closed.\n\n| Error | Reverts when |\n|---|---|\n| `Paused()` | sponsorship is paused |\n| `InvalidSponsorSignature()` | the sponsor-signer signature is missing or does not recover to `sponsorSigner` |\n| `SponsorshipExpired()` | the current time is outside the signed `[validAfter, validUntil]` window |\n| `MaxFeePerGasCeilingExceeded(maxFeePerGas, ceiling)` | the operation's `maxFeePerGas` exceeds `maxFeePerGasCeiling` |\n| `GlobalDailyCapExceeded(spentToday, cap, wouldSpend)` | the day's aggregate spend would exceed `globalDailyCap` |\n| `NotARegisteredCitrateWallet(account)` | a standard or recovery operation is from an unregistered account |\n| `StandardCapExceeded(account, used, cap, wouldUse)` | a standard operation would exceed `dailyCap`, or `dailyCap` is 0 |\n| `RecoveryCapExceeded(account, cap, wouldUse)` | a recovery operation would exceed `recoveryEventCap`, or it is 0 |\n| `RecoveryDailyCountExceeded(account, usedToday, maxPerDay)` | the account has spent its per-day recovery-op count |\n| `FirstOpAlreadyUsed(account)` | the account already used its first-op sponsorship |\n| `FirstOpCapExceeded(cap, wouldUse)` | a first operation would exceed `firstOpCap`, or it is 0 |\n| `UnknownCategory(tag)` | the category byte is greater than 2 |\n| `MissingCategoryTag()` | `paymasterAndData` is shorter than the signed layout requires |\n\n### Eligibility, the registrar gate\n\nStandard and recovery operations require a registered account. A single `registrar` address, typically the\naccount factory, calls `registerWallet(account)`; `unregisterWallet(account)` reverses it. A standard or\nrecovery operation from an unregistered account reverts `NotARegisteredCitrateWallet`. First-op is the\nexception: it is authorized by the sponsor signature rather than registration, so a counterfactual account's\nvery first operation is sponsorable before it is registered and with no cross-entity storage write. The\nowner can rotate the registrar with `setRegistrar`, rotate the sponsor signer with `setSponsorSigner`, and\nhalt all sponsorship with `setPaused(true)` for incident response.\n\nRegistration now happens outside the validation phase (an owner passthrough on the factory), not inside\n`deployFor`, so a strict ERC-7562 tracer sees the paymaster touch only its own storage during validation.\n\n### Bundler topology\n\nThe bundler runs on its own host, so a bundler outage cannot take down the identity authority or the\ngateway. The path an operation takes:\n\n```\nclient (browser, SDK, native app)\n | HTTPS JSON-RPC\n v\nCaddy at bundler.citrate.ai, TLS, per-IP rate limit\n v\nCitrate gate (Node), bk_ Bearer auth, rate limiting, paymaster pre-check\n v\neth-infinitism bundler v0.7, standard ERC-4337 JSON-RPC\n v\nnetwork RPC, EntryPoint v0.7 on chain 40204\n```\n\nThe gate (`citrate-bundler/gate/src`) authenticates `bk_` keys by SHA-256 hash held in Redis, so a dump of\nthe store cannot be replayed as a credential. It rate-limits anonymous traffic per IP and authenticated\ntraffic per key, with the limits as operator configuration. Its pre-check (`gate/src/precheck.ts`), for an\noperation naming the Citrate paymaster, calls `CitratePaymaster.isRegistered(sender)` and\n`EntryPoint.balanceOf(paymaster)` and validates the category byte. Self-paid operations, those naming no\npaymaster, pass through untouched. The pre-check fails open if the chain is unreachable, since the\nEntryPoint re-validates on-chain; the pre-check is an optimization, not a security boundary.\n\nThe bundler exposes the standard ERC-4337 v0.7 methods, `eth_sendUserOperation`,\n`eth_estimateUserOperationGas`, `eth_getUserOperationReceipt`, `eth_supportedEntryPoints`, and\n`eth_chainId`, plus the Citrate extension `citrate_getUserAddress(userId)`, which predicts a Citrate Keyring\naddress and mirrors the on-chain factory. The SDK's bundler client defaults to\n`https://bundler.citrate.ai/rpc`. See the [bundler SDK](/sdks/bundler) for the client.\n\n## Design rationale\n\nWe made sponsorship contract-based because the network has no built-in way to sponsor gas, and a contract\npaymaster is the standard ERC-4337 answer that existing tooling already understands. Per-account budgets,\nrather than a single shared pool, mean one account cannot drain sponsorship for everyone, and the three\ncategories exist so the budgets that must never fail, a person's first operation and an account recovery,\ndraw from separate allowances than ordinary daily use. The registrar gate keeps sponsorship to accounts the\nnetwork actually issued, so an arbitrary contract cannot spend the paymaster's deposit. The edge gate is\nthere to save bundle slots and to rate-limit abuse, but we kept it strictly an optimization: it fails open,\nand the contract is the one place that decides whether an operation is sponsored. The cost is that operators\nmust keep the paymaster funded and the registrar correctly wired; we think a clear on-chain budget is worth\nthat. The economics of who funds sponsorship are in [network economics](/chain/economics).\n\n## Failure modes\n\n- **Over budget.** A standard operation past the daily cap, a recovery past the event cap, or a reused\n first operation reverts with the matching error above. The contract never sponsors past a budget.\n- **Unregistered account.** Sponsorship reverts `NotARegisteredCitrateWallet`. If the factory is not wired\n to register on deploy, new accounts cannot be sponsored until they are registered.\n- **Missing or unknown tag.** An operation with no category byte, or a byte greater than 2, reverts at\n validation rather than being sponsored under a guessed category.\n- **Bundler outage.** The bundler is on its own host; if it is down, sponsored operations cannot be\n submitted, but the identity authority and gateway keep running. Self-paid operations are unaffected.\n- **Edge fails open.** If the chain is unreachable the pre-check is skipped and the operation goes to the\n bundler, where the EntryPoint and the paymaster contract re-validate. The edge skipping a check never\n causes an over-budget sponsorship.\n- **Secrets.** No `bk_` key, multisig address, deposit balance, private RPC endpoint, or host credential\n appears in Citrate Almanac. Those live only in operator configuration.\n\n## Access and canon\n\nCommercial. The budget model and the edge topology are operator and integrator depth; publishing the full\npolicy and topology to anyone aids an abuse actor more than it helps a public developer. The public,\ndeveloper-facing piece, how to tag and send a sponsored operation, sits on [Passkeys](/aa/passkeys). SALT\nsettles the work the network performs, including the gas a paymaster fronts; it is the unit of account, not\na product to hold.\n\n## Source and verification\n\n| Surface | Source | Status |\n|---|---|---|\n| Paymaster policy, caps, errors | `contracts/src/aa/paymaster/CitratePaymaster.sol` | Implemented (pre-audit) |\n| Deployed cap defaults | `contracts/script/aa/DeployAA.s.sol` | Implemented (pre-audit) |\n| EIP-2771 forwarder (education stack) | `contracts/src/edu/Forwarder.sol` | Implemented (pre-audit) |\n| Bundler gate, pre-check, routing | `citrate-bundler/gate/src`, `README.md`, `Caddyfile` | Implemented (pre-audit) |\n\nPaymaster and forwarder verified against the contracts repo at `9d5959e`; the bundler verified against\n`citrate-bundler` at `a3287de`. The caps shown are the as-deployed WEI defaults and an operator may change\nthem on-chain. The stack has shipped and runs on testnet 40204; it has not had an external audit. Re-verify\nthe on-chain cap values and the source symbols against the SHAs before relying on this page.\n"},"/aa/tutorials/sign-in-with-a-passkey":{"slug":"/aa/tutorials/sign-in-with-a-passkey","title":"Sign in with a passkey","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-sdk-js/src/aa/{address,userop,kernel,webauthn,bundler}.ts","syncedSha":"bc5a830","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Step 1, derive the account address","anchor":"step-1-derive-the-account-address"},{"depth":3,"text":"Step 2, build the first operation with the deploy","anchor":"step-2-build-the-first-operation-with-the-deploy"},{"depth":3,"text":"Step 3, hash and sign with the passkey","anchor":"step-3-hash-and-sign-with-the-passkey"},{"depth":3,"text":"Step 4, submit to the bundler and wait","anchor":"step-4-submit-to-the-bundler-and-wait"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"What just happened","anchor":"what-just-happened"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"> **Status: passkey-only accounts are not yet available on chain 40204.** Use these pages to build and test against a local chain.\n\nCreate a passkey-backed Citrate Keyring account and send your first sponsored transaction, with no seed\nphrase, using `@citratelabs/sdk`. You will derive the account address before it exists, then deploy and use it in\na single operation. For the concepts behind each step see [passkeys](/aa/passkeys); for the contracts see\n[account-abstraction contracts](/aa/contracts).\n\n## What it is\n\nA runnable walkthrough of the four moves a surface makes to onboard a user: derive the counterfactual\naddress, build a first operation that carries the deploy, sign it with the device authenticator, and submit\nit to the bundler. Every call below is a real export of `citrate-sdk-js/src/aa/` at SHA `bc5a830`. Passkeys\nneed `navigator.credentials`, which only runs in a secure browser context, so run these steps in a browser\napp, a Vite or Next page, not a plain Node script.\n\n## How to use it\n\nYou will need Node 18 or newer with `npm install @citratelabs/sdk`; a secure context (HTTPS or `localhost`); chain\n40204 access to the Citrate identity authority and the Citrate bundler; and the deployed addresses for the\nstack (factory, account implementation, EntryPoint, paymaster, validators), read from the chain's deployed\naddresses file. The account-abstraction helpers are the `aa` namespace: `import { aa } from '@citratelabs/sdk'`.\n\n### Step 1, derive the account address\n\n```typescript\nimport { aa } from '@citratelabs/sdk';\nconst { uuidToUserId, predictWalletAddress } = aa;\n\nconst userId = uuidToUserId(citrateUserId); // keccak256(utf8(lowercase uuid))\nconst sender = predictWalletAddress(FACTORY, IMPLEMENTATION, userId);\n// `sender` is this user's one account address on every surface, counterfactual.\n```\n\n`predictWalletAddress` is pure: it computes the CREATE2 address with no chain read, so you can show the user\ntheir address before anything is deployed. It returns the same value the factory's `predictAddress` returns\non chain.\n\n### Step 2, build the first operation with the deploy\n\nFetch the deploy permit from the identity authority, then assemble the `initCode` and the call. The first\noperation carries the deploy, so the account creates itself on first use.\n\n```typescript\nimport { aa } from '@citratelabs/sdk';\nconst {\n encodeDeployFor, packInitCode, encodeExecuteSingle,\n buildPackedUserOp, packCitratePaymasterAndData, PaymasterCategory,\n} = aa;\n\n// The identity signer authorizes the deploy, see /aa/identity.\nconst permit = await fetch('https://auth.citrate.ai/aa/enroll-validator', {\n method: 'POST',\n headers: { 'content-type': 'application/json', authorization: `Bearer ${accessToken}` },\n body: JSON.stringify({ /* userId, initialValidator, initData ... */ }),\n}).then((r) => r.json());\n\nconst factoryData = encodeDeployFor({\n userId,\n initialValidator: WEBAUTHN_VALIDATOR,\n initData: permit.initData,\n expiresAt: BigInt(permit.expiresAt),\n signature: permit.signature,\n});\n\nconst op = buildPackedUserOp({\n sender,\n nonce, // EntryPoint.getNonce(sender, key)\n initCode: packInitCode(FACTORY, factoryData),\n callData: encodeExecuteSingle({ to: recipient, value: 0n, data: '0x' }),\n callGasLimit, verificationGasLimit, preVerificationGas,\n maxFeePerGas, maxPriorityFeePerGas,\n // First-ever op, sponsored under the first-op budget:\n paymasterAndData: packCitratePaymasterAndData({\n paymaster: PAYMASTER,\n paymasterVerificationGasLimit, paymasterPostOpGasLimit,\n category: PaymasterCategory.FirstOp,\n }),\n});\n```\n\nThe `nonce` and the gas fields come from a chain read and the bundler's gas estimate; `buildPackedUserOp`\ntakes them as inputs so the builder stays pure. The endpoint path and request body shape belong to the\nidentity authority, not to the SDK; the SDK exports the encoders (`encodeDeployFor`, `packInitCode`), so the\npermit fetch is described generically here.\n\n### Step 3, hash and sign with the passkey\n\n```typescript\nimport { aa } from '@citratelabs/sdk';\nconst { getUserOpHash, signUserOpWithPasskey } = aa;\n\nconst hash = getUserOpHash(op, ENTRYPOINT, 40204n);\n\n// Triggers the platform authenticator; encodes the assertion for the\n// WebAuthn validator and normalizes `s` to the lower half-order.\nconst signature = await signUserOpWithPasskey(hash);\nconst signedOp = { ...op, signature };\n```\n\n`getUserOpHash` commits to every field of the operation, including the chain id, so the signature is valid\nonly for this operation on chain 40204. `signUserOpWithPasskey` drives `navigator.credentials.get()` and\nthrows `WebAuthnSigningError` outside a secure context.\n\n### Step 4, submit to the bundler and wait\n\n```typescript\nimport { aa } from '@citratelabs/sdk';\nconst { BundlerClient } = aa;\n\nconst bundler = new BundlerClient(); // defaults to https://bundler.citrate.ai/rpc\n\n// Sanity check, the bundler must be on 40204:\nif ((await bundler.chainId()) !== 40204n) throw new Error('wrong chain');\n\nconst userOpHash = await bundler.sendUserOperation(signedOp, ENTRYPOINT);\nconst receipt = await bundler.waitForUserOperationReceipt(userOpHash);\nconsole.log('mined:', receipt);\n```\n\nOn a revert the client throws `BundlerRpcError` carrying the JSON-RPC payload, so you can surface ERC-4337\ncodes (for example `AA31`, paymaster deposit too low) directly. `waitForUserOperationReceipt` polls every\ntwo seconds for up to sixty seconds by default, which covers a normal inclusion plus a bundle interval.\n\n## Reference\n\nThe exports used above, all in `citrate-sdk-js/src/aa/`:\n\n| Symbol | Returns | Source |\n|---|---|---|\n| `uuidToUserId(uuid)` | the 32-byte userId | `address.ts` |\n| `predictWalletAddress(factory, impl, userId)` | the counterfactual account address | `address.ts` |\n| `encodeDeployFor(args)` | factory `deployFor` calldata | `userop.ts` |\n| `packInitCode(factory, factoryData)` | ERC-4337 `initCode` | `userop.ts` |\n| `encodeExecuteSingle(call)` | Kernel `execute` calldata | `kernel.ts` |\n| `buildPackedUserOp(args)` | the packed operation struct | `userop.ts` |\n| `packCitratePaymasterAndData(args)` | `paymasterAndData` with the category tag | `userop.ts` |\n| `PaymasterCategory` | `Standard`, `Recovery`, `FirstOp` | `types.ts` |\n| `getUserOpHash(op, entryPoint, chainId)` | the operation hash | `userop.ts` |\n| `signUserOpWithPasskey(hash, opts)` | the WebAuthn signature blob | `webauthn.ts` |\n| `BundlerClient` | the bundler JSON-RPC client | `bundler.ts` |\n\n## Design rationale\n\nThe deploy travels with the first operation rather than as a separate transaction, so onboarding is one\nsignature and an account that is never used costs nothing. The operation hash commits to the chain id and\nevery field, so a passkey signs exactly one operation on exactly one chain. The builder functions are pure\nand take chain reads as inputs, so the same code runs in the browser against the live bundler and in tests\nagainst pinned vectors.\n\n## What just happened\n\n- The account deployed itself on its first operation, through the `initCode`, and the factory registered it\n with the paymaster so sponsorship was allowed.\n- The passkey authorized the operation through `WebAuthnP256Validator`; no seed phrase ever existed.\n- The paymaster paid gas under the first-op budget.\n\nNext, add a recovery method in [guardians](/aa/guardians), or read the full [passkeys](/aa/passkeys)\nreference.\n\n## Access and canon\n\nPublic tier. A runnable builder tutorial; nothing here is secret. No private keys, mnemonics, or\ncredentials appear: the access token is the user's own identity token, the deploy permit is fetched at\nruntime, and passkey private material never leaves the authenticator. Use testnet values on chain 40204.\n\n## Source and verification\n\n- `citrate-sdk-js/src/aa/{address,userop,kernel,webauthn,bundler}.ts` at SHA `bc5a830`.\n- Validators and factory: `citrate-chain/contracts/src/aa/` at SHA `9d5959e`.\n\nStatus: Specified on chain 40204 (passkey-only accounts are not yet available there); the SDK helpers are implemented, pre-audit. Build against a local chain; do not custody material value. Re-verify the exports against\nthe SHAs before relying on this tutorial.\n"},"/apps/buyer":{"slug":"/apps/buyer","title":"Citrate Market (buyer)","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-buyer-webapp","syncedSha":"7d44b29","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"Verification tiers","anchor":"verification-tiers"},{"depth":3,"text":"Job lifecycle","anchor":"job-lifecycle"},{"depth":3,"text":"x402 payment bounds","anchor":"x402-payment-bounds"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The buyer side of Citrate Market, where you find a model provider, post a job, and pay for each\nrequest as it runs. It is for teams that want to buy compute and inference on the open marketplace\nand settle the cost per call rather than holding a balance.\n\n## What it is\n\nCitrate Market is where compute is bought and sold on the Citrate Network. This app\n(`citrate-buyer-webapp`) is the buyer-facing front end of that market. You browse the catalog of\nmodels, providers, and compute pools, post a job at a price you set, and the work settles back to\nyou when a provider has run it and the result has been checked.\n\nTwo purchase paths run side by side, and the app picks one for you depending on how you start. The\ngateway path posts a chat completion to the Citrate gateway and pays for each request with x402, a\nper-request payment protocol covered under [the x402 contract](/contracts/x402). The direct path\nposts the job on the public ledger itself, locking escrow at your maximum price and opening an\nauction that providers bid into. Both settle in wSALT on chain 40204. The gateway path is the\nquicker route; the direct path keeps the whole transaction on the public ledger, where you can read\nit back yourself.\n\nPayment is bounded by design. The x402 client signs for at most a fixed amount per round, defaulting\nto one SALT, and it will only ever pay in a single allowed token on a single chain. A payment\nrequest naming any other token or chain is rejected before it is signed. You can find the wider\nmarket in [the compute overview](/compute/pool) and the client library in\n[the marketplace SDK](/sdks/marketplace).\n\n## How to use it\n\n1. Open the app and browse the marketplace. Compare models by their per-1K pricing, providers by\n reputation, stake, region, load, and the verification tiers they support, and compute pools by\n mode, GPU count, throughput, and price.\n2. Open a provider to read its reputation, current capacity and load, supported models, and\n verification tiers in one place.\n3. Post a job. Pick a model, choose a verification tier, set a maximum price, and submit. The gateway\n path runs the request and auto-pays with x402; the direct path posts the job on the public ledger\n and opens the auction.\n4. Approve the payment in your account when prompted. Auto-pay stays inside the per-round ceiling and\n pays only in wSALT on chain 40204.\n5. Track the job through its states. On a bad outcome you are refunded, and a provider that misses its\n deadline is slashed.\n\n## Reference\n\nThe buyer journey is built from the screens below. Each cites the code that backs it.\n\n| Area | What you do | Source |\n|---|---|---|\n| Marketplace browse | Compare models, providers, and compute pools by price, reputation, stake, region, load, throughput, and supported verification tiers. | `app/design/DesignApp.jsx` |\n| Provider detail | Inspect one provider's reputation, capacity and load, supported models, and verification tiers. | `app/design/DesignApp.jsx` |\n| Post a job | Choose a model and a verification tier, set a maximum price, and submit through the gateway or direct path. | `lib/submitJob.ts`, `lib/submitDirectJob.ts`, `lib/submitTrainingJob.ts` |\n| Track results | Follow a job through its lifecycle, including the terminal outcomes. | `app/design/DesignApp.jsx` |\n| Pay with x402 | Pay per request with bounded auto-pay in wSALT; the client enforces the ceiling and the allowed token and chain. | `lib/submitJob.ts`, `lib/buyCredits.ts`, `lib/creditsClient.ts` |\n| Copilot | Ask marketplace and network questions in an in-app assistant that streams from the Citrate gateway. | `app/api/chat/route.ts` |\n\n### Verification tiers\n\nWhen you post a job you choose how the result is checked. The tier sets a price multiplier, defined\nin `app/design/DesignApp.jsx`.\n\n| Tier | Technique | Price multiplier |\n|---|---|---|\n| Standard | commitment | 1.0x |\n| Cryptographic proof | ZK proof, Groth16 | 1.5x |\n| Secure enclave | TEE attestation | 2.0x |\n\n### Job lifecycle\n\nA job moves through a defined set of states. The happy path is documented in `DESIGN_HANDOFF.md` and\nbacked by on-chain events such as `JobPosted`, `JobAssigned`, and `JobCompleted` parsed through the\nSDK.\n\n```text\nPosted -> Bidding -> Assigned -> Executing -> Verifying -> Completed\n```\n\nThe terminal outcomes are explicit. Completed settles to the provider. Expired refunds you when no\nbid arrives. Timeout slashes a provider that misses its deadline and refunds you. Failed refunds you\nwhen verification does not pass. Disputed is resolved by the contract.\n\n### x402 payment bounds\n\nThe x402 client enforces two limits, defined in `lib/submitJob.ts`.\n\n```ts\nexport const DEFAULT_MAX_PAY_WEI = 1_000_000_000_000_000_000n; // 1 SALT\nexport const ALLOWED_PAY_TOKENS: Address[] = [\n WRAPPED_SALT, // contracts.WrappedSALT from the address book\n];\n```\n\nThe allow-list must hold the live wSALT address from the [address book](/chain/addresses)\n(`contracts.WrappedSALT`). An older build hard-coded a pre-re-roll wSALT address that has no code on chain\n40204; if your copy of `lib/submitJob.ts` still contains a literal address, replace it with the book value\nand confirm it with `cast code`.\n\nThe per-round ceiling defaults to one SALT and the UI may set it lower. The allowed token is wSALT\nand the chain is 40204. Any other token or chain is refused.\n\n## Design rationale\n\nPaying for each request, rather than topping up a balance, keeps the buyer in control of cost at the\nfinest grain the market allows. The bounded auto-pay follows from that: a client that signs payments\non your behalf must never be able to sign an open-ended amount, so a finite per-round ceiling and a\nsingle allowed token and chain are applied to every request before it is signed. Offering the two\npurchase paths is the other deliberate trade. The gateway path is faster and hides the auction; the\ndirect path keeps the transaction on the public ledger where you can audit settlement yourself. We\nlet the buyer choose which property matters more for a given job.\n\n## Failure modes\n\nThis surface moves real funds, so it is built to fail closed.\n\n- The payment client never signs an unbounded x402 amount. A finite per-round ceiling is always\n applied, and a request naming any token other than wSALT or any chain other than 40204 is rejected\n before signing (audited as `RM-F1` and `BUYER_WEBAPP-002`).\n- The copilot route applies an IP rate limit before any gateway or inference call, because every\n request costs real money (tracked under SECREM-01 WEB-3). The default is 20 requests per minute,\n overridable by environment.\n- Outbound gateway targets are restricted by an allowlist in `lib/gatewayAllowlist.ts`, validated\n before a payment is signed.\n- No secrets appear in this page. Gateway and RPC hostnames are public; signer material lives in the\n account and environment, never in documentation.\n\n## Access and canon\n\nCommercial. This is paid marketplace operation: job posting, provider economics, and payment,\nintended for contracted buyers, and gated through the Codex chokepoint (`PLANSET/02_ARCHITECTURE.md`\nsection 4). Market participation settles in SALT, which pays for work and is not treated here as\nanything to hold. The wider network is on-premise by default and identity verification through VERI is part of membership; that envelope is described in\n[what Citrate is](/start/what-is-citrate).\n\n## Source and verification\n\n- Source repo: `citrate-buyer-webapp`, split from the Citrate monorepo on 2026-05-18.\n- Audited against: `7d44b29`.\n- Key paths: `app/page.tsx`, `app/design/DesignApp.jsx`, `app/design/sdkBridge.ts`,\n `app/api/chat/route.ts`, `lib/submitJob.ts`, `lib/submitDirectJob.ts`, `lib/marketplace.ts`,\n `lib/buyCredits.ts`, `lib/gatewayAllowlist.ts`, `lib/chatGuard.ts`, `DESIGN_HANDOFF.md`.\n- Status: **Implemented (pre-audit).** The x402 job submission, credits, server-side\n `MarketplaceClient` reads via `@citratelabs/marketplace-sdk`, and the gateway-backed copilot are\n wired and run against chain 40204. The browse catalog renders live SDK reads when a default model\n hash is configured and otherwise falls back to sample provider data, which the UI labels as\n `source: 'sample'` so the screen stays honest. Treat catalog figures as illustrative until live\n indexing is fully wired. This app has not completed an external audit. The README is monorepo-split\n boilerplate, so the screens here are audited against the app code and `DESIGN_HANDOFF.md`, not the\n README.\n"},"/apps/chatbot":{"slug":"/apps/chatbot","title":"Citrate Chat (gasless chat app)","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chatbot","syncedSha":"e3827c3","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"What gasless means here","anchor":"what-gasless-means-here"},{"depth":3,"text":"Contracts","anchor":"contracts"},{"depth":3,"text":"Inference source","anchor":"inference-source"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"A gasless chat app that runs inference on Citrate and signs in with a Citrate Keyring account, so you\ncan talk to a model on the network without holding any SALT. It is for anyone who wants to try a chat\napp native to Citrate, and for builders who want a reference for gasless sponsorship on the network.\n\n## What it is\n\nCitrate Chat is a public app that lets you talk to a model running on Citrate. Two things make it\ndifferent from an ordinary chat app, and both rest on Citrate being a substrate rather than a service\nyou have to trust.\n\nThe first is the account. You sign in with a Citrate Keyring account rather than a username and a\npassword. The account is created for you on first sign-in, so there is no key to set up by hand. The\n[passkeys page](/aa/passkeys) covers the account model in full.\n\nThe second is that it is gasless. Citrate has no native paymaster, so the app uses an EIP-2771\nmeta-transaction relay: you sign a request, which is free, and a relayer funded by Citrate Inc.\nsubmits the on-chain transaction and pays the fee. You never need SALT in hand to use it.\nThat relay is described under [the paymaster page](/aa/paymaster).\n\nThe mental model is plain. You chat normally. The model reply streams back to you, and when receipt\nanchoring is on, a short on-chain record of the exchange is written to a registry contract, with the\nnetwork covering the fee rather than you. The on-chain inference path, where a model call is itself a\nledger operation through the inference RPC methods on [chain RPC](/chain/rpc), is specified and\npartly built but not yet wired; today the model reply is served over the gateway, and the gasless\non-chain part is the receipt.\n\n## How to use it\n\n1. Open the app. Signed out, you see the hero and a sign-in button.\n2. Sign in. An account is created or linked for you, with no email or key setup required\n (`src/components/auth-provider.tsx`).\n3. Type a message and send it. The reply streams in real time from the configured inference source\n (`POST /api/chat`, `src/app/api/chat/route.ts`).\n4. When receipt anchoring is on, the app builds an EIP-2771 `ForwardRequest`, you sign it for free,\n and it is submitted through the relayer (`src/hooks/use-sponsored-write.ts`, then `POST /api/relay`).\n5. Once signed in, your chats can be saved as threads in the sidebar when a database is configured.\n\n## Reference\n\nThe screens are built from the components below, in `src/components/`.\n\n| Screen | What you see | Source |\n|---|---|---|\n| Hero | The empty state with suggested-prompt chips and the gasless explainer. | `src/components/hero.tsx` |\n| Conversation | Streaming message bubbles, an on-chain receipt chip, copy and regenerate. | `src/components/conversation.tsx` |\n| Composer | An auto-growing input, send and stop, a model badge, Cmd+Enter to send. | `src/components/composer.tsx` |\n| Header | The sign-in button or your account address chip. | `src/components/header.tsx` |\n| Sidebar | Thread history, when signed in and a database is configured. | `src/components/sidebar.tsx` |\n| Settings | Chain info, account address, balance, and a read-back of recorded receipts. | `src/components/settings.tsx` |\n\n### What gasless means here\n\nYou sign a typed-data `ForwardRequest` with your account. The relayer route\n(`src/app/api/relay/route.ts`) checks that your authenticated session owns the `from` address,\nrate-limits the request to protect the Citrate Inc. relayer account, validates the signature on-chain with the\nforwarder's `verify`, then calls the forwarder's `execute` and pays the gas. Only the relayer can call\n`execute`, and it appends your address to the call per the EIP-2771 standard.\n\n### Contracts\n\n| Contract | Role | Source |\n|---|---|---|\n| `CitrateForwarder` | EIP-2771 forwarder; verifies the signed request and executes it on the user's behalf. | `contracts/src/CitrateForwarder.sol` |\n| `ChatRegistry` | Records a content hash per thread in contract state, so receipts can be read back with `eth_call`. | `contracts/src/ChatRegistry.sol` |\n\nBoth are deployed to testnet and covered by 14 passing Foundry tests. `ChatRegistry` stores receipts\nin state rather than only emitting events, because the network's read path uses `eth_call` rather than\nevent indexing.\n\n### Inference source\n\nThe chat route resolves an inference provider and streams the reply. The working modes are a\nself-hosted local endpoint and an OpenAI-compatible gateway; the on-chain mode is present in the\ninterface but throws until it is wired (`src/lib/inference/index.ts`). The gateway reply is streamed,\nnot an on-chain call. The inference RPC methods that the on-chain mode will use are on\n[chain RPC](/chain/rpc).\n\n## Design rationale\n\nThe account and the gas rail both exist to remove the two things that usually stop a newcomer from\ntrying a network app: setting up keys, and acquiring the fee token first. A Citrate Keyring account is\ncreated on sign-in, so there is no key ceremony. The EIP-2771 relay lets the network, not the user,\npay the fee, so there is nothing to acquire before the first message. The cost of sponsoring gas is\nthat the Citrate Inc. relayer account is a target, which is why the relay rate-limits and checks session\nownership before it ever signs. We separated the receipt from the inference deliberately: the on-chain\nreceipt is gasless and live today, independent of whether the model call has moved on-chain yet.\n\n## Failure modes\n\nThe relay spends real funds on a user's behalf, so it is built to fail closed.\n\n- The relay verifies that your authenticated session owns the `from` address before sponsoring any\n transaction. A request to relay for an address you do not own is refused.\n- The relay enforces per-address and per-IP rate limits and a daily budget before it touches the\n chain, so a flood cannot drain the Citrate Inc. relayer account.\n- Only the relayer may call the forwarder's `execute`, and the signature is validated on-chain before\n execution.\n- The on-chain inference path is not wired. Calling it throws rather than silently degrading, so the\n app cannot appear to run a ledger inference when it is actually serving from the gateway.\n- The relayer key, the auth provider secret, the database URL, and any chat encryption key are\n server-only environment values and do not appear in this page. The public RPC at\n `https://rpc.citrate.ai` and the public contract addresses are not secrets.\n\n> Repo hygiene, flagged and not transcribed: the working tree carries a `.env.local` holding a live\n> testnet relayer private key, a chat encryption key, a Neon Postgres connection string, and a Vercel\n> OIDC token. The file is gitignored and not in history, but the values are live and should be rotated.\n> None of them are reproduced here.\n\n## Access and canon\n\nPublic. This is the kind of open, developer-facing app the Codex keeps public: the concepts and\nreference a developer needs to build a gasless app on Citrate. It runs against the Citrate Network\ntestnet at chain id 40204. SALT settles the work the relay performs and the user holds none of it.\n\n## Source and verification\n\n- Source repo: `citrate-chatbot`.\n- Audited against: `e3827c3`.\n- Key paths: `src/app/api/relay/route.ts`, `src/app/api/chat/route.ts`,\n `src/hooks/use-sponsored-write.ts`, `src/components/auth-provider.tsx`,\n `src/lib/inference/index.ts`, `contracts/src/CitrateForwarder.sol`, `contracts/src/ChatRegistry.sol`,\n `README.md`, `.agentile/PRODUCT_SPEC.md`.\n- Status by area:\n - Gas rail (sprint S-2): **Implemented (pre-audit).** `CitrateForwarder` and `ChatRegistry` are\n deployed to testnet, the relay route works, and the gasless receipt write is live.\n - Account sign-in and streaming chat (sprint S-1): **Implemented (pre-audit).**\n - On-chain inference (sprint S-3): **Specified.** The provider interface exists; the on-chain mode\n throws until wired. The live reply is served over a gateway or a local endpoint.\n - Encrypted thread history (sprint S-4): **Implemented (pre-audit)**, partial; persistence works and\n summarization is not fully live.\n - Open dependencies the deployer must supply: a live inference gateway URL, a model registered in\n `ModelRegistry` (the registry is empty today), and account-provider credentials.\n - No external audit has been completed.\n"},"/apps/comms":{"slug":"/apps/comms","title":"Citrate Comms","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-comms/README.md, citrate-comms/crates, citrate-comms/PLANSET","syncedSha":"67557cf","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Citrate Comms is an end-to-end encrypted team workspace, messaging, a customer record, and project\nmanagement in one self-hostable binary, where agents take part as ordinary members of a conversation. It is\nfor any team that needs to collaborate privately, including on-premise or air-gapped, without trusting a\nserver to keep their secrets.\n\n## What it is\n\nCitrate Comms is built on one boundary, and that boundary explains everything else. A relay moves messages\nbetween members, but it never reads them. It is trusted to keep the lights on and to put messages in order;\nit is never trusted with what the messages say.\n\nConcretely, members sign in with a cryptographic handshake tied to their Citrate account, and from then on\nevery message is encrypted on the sending member's machine and decrypted only on the receiving members'\nmachines. The relay stores and forwards ciphertext and routing information, nothing more. All plaintext, all\ngroup secrets, and all customer and project records live only on member clients. An agent reading a channel\nis cryptographically the same as a person reading it: there is no shadow key and no plaintext kept in escrow\nfor the server.\n\nThis server-blind property is not a promise in a policy document, it is held by the way the code is\ncompiled. The relay links the core library with its message-group module switched off, so the only place\ngroup secrets could be handled is simply not present in the relay binary. Code in the relay that tried to\nread a group secret would fail to compile.\n\n## How to use it\n\nCitrate Comms is one binary you run yourself, alongside the rest of your tools. The shape of using it is:\n\n1. Run the relay where your team can reach it, on your own hardware, on-premise, or on an air-gapped network\n beside an on-premise agent.\n2. Open the native client and sign in with the cryptographic handshake against your Citrate account. Your\n account address is your identity in the workspace.\n3. Create channels, forums, and direct messages, and bring your customer records and project tracking into\n the same encrypted space.\n4. Enroll an agent as a member when you want one. The agent holds its own keys and joins the group like any\n other member, reachable over a local socket bridge.\n\nStep by step tutorials for self-hosting the relay, enrolling an agent, and anchoring an audit log to the\nCitrate Network follow as the remaining components land.\n\n## Reference\n\nThe workspace is a set of Rust crates. The cryptographic and transport spine is built and tested; the\nremaining crates fill in on the published plan.\n\n| Crate | Status | Role |\n|---|---|---|\n| `comms-proto` | Implemented | Wire types: envelope, group id, commit, welcome, application message, role assertion, audit record |\n| `comms-core` | Implemented (mls, identity, audit, store) | Group messaging over OpenMLS, sign-in identity, the audit chain, and the ciphertext store; the role and domain modules follow |\n| `comms-relay` | Implemented | The server-blind delivery service: total order per group, the key-package directory, the audit log |\n| `comms-wire` | Implemented | The client-half relay wire protocol, with no MLS present |\n| `comms-session` | Implemented | A member session: sign-in identity, key package, and send and receive |\n| `comms-member-daemon` | Implemented | An account-owned MLS member with an in-process relay over a loopback socket |\n| `comms-agent-bridge` | Implemented | A local socket bridge that lets an agent join as a member holding its own keys |\n| `comms-client` | Implemented (shell, primary channel) | The native client; the shell and the main channel screen are translated from the design handoff |\n| `comms-release` | Implemented | A reproducibility manifest and an Ed25519 release signer (COMMS-S4) |\n| `comms-client-proof` | Implemented | A visual golden-image test harness (COMMS-S5) |\n\nThe cryptography is standard and named:\n\n```text\ngroup messaging MLS (RFC 9420) via OpenMLS\nciphersuite MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519\n X25519 key exchange, AES-128-GCM, Ed25519 signatures\nat rest RocksDB column families encrypted with AES-256-GCM-SIV\n (nonce-misuse-resistant, RFC 8452); classical today, with a\n Kyber-768 + X25519 hybrid key wrapping roadmapped (PLANSET/07)\naudit BLAKE3 hash-chained append-only log,\n optionally anchored to the Citrate Network for tamper-evidence\n```\n\nTo build and test the workspace:\n\n```bash\ncargo build --workspace --release --locked\ncargo test --workspace\ncargo clippy --workspace --all-targets -- -D warnings\n```\n\nFormal invariants are written in TLA+ (commit ordering and audit-chain contiguity), and each capability is\nspecified as a Gherkin feature. Agents reach the workspace through the same conversation surface they reach\nthe rest of the network with, described under [chain RPC](/chain/rpc), and the research that the audit and\nverification design rests on is in [research](/research/learning).\n\n## Design rationale\n\nMost team tools put the server in the middle and trust it to behave: it can read everything, and you are\nasked to believe it will not. For a team working under a compliance regime, or on an air-gapped network, that\ntrust is the thing they cannot grant. Citrate Comms removes the question by removing the server's ability to\nread, and it does so where it cannot quietly be undone, in the build graph rather than in configuration. The\nsame decision is what lets an agent be a full member rather than a privileged listener: if the server cannot\nread the channel, an agent that reads it must be a member with keys, exactly like a person. The cost is that\nthe relay cannot offer server-side features that depend on reading content, such as server-side search; that\nwork moves to the clients, which is where the plaintext already is.\n\n## Access and canon\n\nCommercial. Citrate Comms is a paid-seat product, and this page documents the public architecture and crate\nmap at a pinned commit. It carries no secrets: no keys, no tokens, and no private endpoints. The relay's\nloopback administration and bearer-token operational details, and any operator deployment credentials, stay\nout of every tier. The system's security rests on the protocol and the build-graph-enforced server-blind\nrelay, not on keeping this page vague. It runs on-premise and air-gapped alongside the on-premise compliance\nagent in [the air-gapped agent sidecar](/apps/nist-agent).\n\n## Source and verification\n\n- Source repo: `citrate-comms`, `README.md` and `PLANSET/`.\n- Audited against SHA: `0a4989e`.\n- Key paths: `crates/comms-proto`, `crates/comms-core` (`mls`, `identity`, `audit`), `crates/comms-relay`,\n `crates/comms-agent-bridge`, `crates/comms-client`.\n- Status: Implemented, accepted into the federation on 2026-06-14. The native Rust workspace has shipped\n through the cryptographic and transport spine and is in interface hardening (COMMS-S5 active, S0 through\n S4 complete), with 303 tests passing across the workspace (121 Rust + 182 TypeScript). It is pre-1.0: the agent bridge is built (the\n socket IPC and the account-owned MLS member), while the privileged agent runtime is not yet wired, and\n the at-rest encryption is classical with a post-quantum hybrid roadmapped. Only an internal self-audit has\n run; there is no external audit yet.\n"},"/apps/dashboard":{"slug":"/apps/dashboard","title":"Learning dashboard","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-dashboard/{app/, lib/daemon-api.ts}","syncedSha":"727e62d","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The learning dashboard is the live window onto Citrate Orchard, the federated-learning surface where models\ntrain across nodes without the training data leaving them. It shows each learning cycle as it runs, who\ncontributed, and which mentor pairings were committed, all read from a public, read-only view of the\nnetwork.\n\n## What it is\n\nCitrate Orchard runs a federated-learning loop: one round roughly every 25 seconds, in which nodes submit\nembeddings, an on-chain Belnap-FOUR aggregator picks a canonical signal, the routing model retrains, and\nmentor pairings are committed on chain. The dashboard is the observability layer over that loop. It is a\nNext.js application, and it holds no canonical state of its own. Live cycle state, embeddings, mentor\npairings, and contribution scores are read through from the network: from chain RPC and from a read-only\ndaemon API. The only thing it persists locally is account-facing convenience, profiles, invite tokens, and\ncycle subscriptions. The chain is the source of truth, and the dashboard does not mirror it.\n\nThe loop matters because of what it does not move. Embeddings are the only thing nodes publish; the data\nthose embeddings were computed from stays on the node. The dashboard makes that loop legible to the people\nwatching it without becoming a second copy of it.\n\n## How to use it\n\nThe dashboard is read-mostly. Anyone can follow the cycles; participating in a cycle needs an account.\n\n1. Open the dashboard and pick Cycles to see what is running now.\n2. Click any cycle to see its contributors and the mentor pairings it committed.\n3. Open Experiments to follow the research hypotheses and their status.\n4. To take part, open Profile, sign in, link your Citrate account, and join a cycle. Your contribution\n score updates as cycles run.\n\n## Reference\n\nThe screens and what they read (`app/`, `lib/daemon-api.ts`).\n\n| Screen | Route | What you see | Code |\n|---|---|---|---|\n| Home | `/` | The loop explained, cards into Cycles, Experiments, and Profile, and the public daemon and RPC endpoints. | `app/page.tsx` |\n| Cycles | `/cycles` | A live list of learning cycles with status, participant count, and start and finalize times. | `app/cycles/page.tsx` |\n| Cycle detail | `/cycles/[id]` | One cycle's embedding contributors and committed mentor pairings. A missing cycle renders a standard 404. | `app/cycles/[id]/page.tsx` |\n| Experiments | `/experiments` | The three research hypotheses, what each measures, and its current status. | `app/experiments/page.tsx` |\n| Profile | `/profile` | Sign in, link your Citrate account, set display name, bio, and timezone, view subscriptions, and join a cycle. | `app/profile/page.tsx` |\n\nA cycle moves through a fixed lifecycle, and the status badges map one-to-one onto it\n(`lib/daemon-api.ts`):\n\n```text\nembeddings_open → embeddings_closed → aggregated → trained → matched → finalized\n```\n\nThe Experiments page tracks three hypotheses from the second research paper. They are spec-locked and\nawaiting testnet measurement, so the page renders the plan, not results\n(`app/experiments/page.tsx`):\n\n| Hypothesis | Question | Status |\n|---|---|---|\n| H1 | Belnap-FOUR aggregation versus a flat mean under injected mislabels, on a 4-node setup. | spec-locked, awaiting testnet |\n| H2 | Whether adapter-composition accuracy follows a power law as adapters are added, to 100. | spec-locked, awaiting testnet |\n| H3 | Routing-model convergence with Byzantine validators below the BFT threshold, to 30 nodes. | spec-locked, awaiting testnet |\n\nData sources are read-only. The daemon API at `federated.citrate.ai/api/{cycles, embeddings, mentors}` is\nGET-only and enforced as such at the source by a tripwire that forbids mutating verbs; chain reads go to\n`rpc.citrate.ai` on chain 40204 (`lib/daemon-api.ts`).\n\n## Design rationale\n\nA dashboard over a live network has one temptation, to cache the network into itself and slowly drift out\nof truth. This one refuses that. Cycle pages render on every request with no caching, because cycle state\nis live, and the dashboard reads through to the chain and the daemon rather than mirroring them, so the\nchain stays canonical. The daemon API it depends on is read-only by construction, which means the\nobservability layer cannot become an accidental control surface. Identity is the same discipline: the\naccount a request acts as is derived server-side from a verified session token, never from an identifier\nsupplied in the URL or body, so one account can never read or write another's profile.\n\n## Failure modes\n\nThe dashboard depends on services it does not own, so it is built to degrade rather than crash or leak.\n\n- When the daemon is unreachable, a cycle page shows a clear \"Daemon API unavailable\" panel instead of\n failing, and a transient error on one of the three reads behind a cycle detail degrades that panel\n rather than the page (`app/cycles/page.tsx`, `app/cycles/[id]/page.tsx`).\n- Upstream daemon error text, status lines, body snippets, connection strings, is never reflected to the\n client. The server logs it and returns a fixed-shape 503 (audit `CITRATE_DASHBOARD-2026-05-31-006`).\n- The profile and invite APIs derive identity from a verified session token server-side and reject any\n client-supplied identifier, closing an account-enumeration path found in audit\n (`CITRATE_DASHBOARD-2026-05-31-001`).\n\n## Access and canon\n\nTier: commercial. The dashboard surfaces operator- and participant-facing learning operations, cycle\ninternals, contribution scoring, and experiment tracking, intended for contracted principals rather than\nanonymous scraping.\n\nThis is the observability window onto Citrate Orchard, not a place where learning data lives. Citrate\nOrchard's premise is that models train across nodes without the training data leaving them: a node\npublishes embeddings, not its underlying data, so the data stays on the node. The dashboard reads only the\npublic, read-only view of that loop and holds no canonical state. No secrets appear in this page; the\nendpoints shown are public hostnames, and configuration lives in environment. See\n[federated learning](/research/learning) for Citrate Orchard and the research the loop rests on, and\n[the compute pool](/compute/pool) for how nodes join.\n\n## Source and verification\n\n- Source repo: `citrate-dashboard`, split from the Citrate monorepo on 2026-05-18, audited against SHA\n `727e62d`.\n- Key paths: `app/page.tsx`, `app/cycles/page.tsx`, `app/cycles/[id]/page.tsx`, `app/experiments/page.tsx`,\n `app/profile/page.tsx`, `app/api/profile/route.ts`, `app/api/invites/route.ts`, `lib/daemon-api.ts`.\n- Stack: Next.js 16, React 19, Prisma on Vercel Postgres for profiles and invites, ethers for chain reads,\n Privy for account sign-in.\n- Status: Implemented (pre-audit), pilot, labelled `RM-FL-5` in the application. The Experiments page\n renders the plan: the three hypotheses are spec-locked and awaiting testnet, so treat experiment results\n as pre-data until the measurement work lands. The screens here are verified against the application code\n at this SHA; the repository README is monorepo-split boilerplate and is not the source of these claims.\n Tier 1 audit applies before a stable release.\n"},"/apps/explorer":{"slug":"/apps/explorer","title":"CitrateScan Explorer","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-explorer/README.md, src/scan/screens, src/app/api","syncedSha":"6faab8a","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"Screens","anchor":"screens"},{"depth":3,"text":"Read API, Etherscan request shape","anchor":"read-api-etherscan-request-shape"},{"depth":3,"text":"MCP server","anchor":"mcp-server"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"CitrateScan is the public explorer for the Citrate Network. It reads the BlockDAG, transactions,\naccounts, contracts, and the model contracts that run on chain, and it is built so you can read it with\nyour eyes, ask it in plain English, or drive it from an agent.\n\n## What it is\n\nCitrateScan is a DAG-native, agentic block explorer for [Citrate Network](/chain/rpc), live on chain id\n40204. Most explorers assume a single line of blocks and count confirmations. Citrate is a BlockDAG under\nGhostDAG, so a block carries a blue score rather than a plain height, has one selected parent and up to ten\nmerge parents, and gains confirmation by depth. CitrateScan shows a depth≥100 flag once the current blue\nscore minus the block's blue score is at least 100. That flag is a display heuristic, not protocol\nfinality: confirmation on the testnet is probabilistic and checkpoint finality is specified, not running. The consensus model behind this is covered under [Citrate Network consensus](/chain/consensus).\n\nIt is agentic in two senses. Every entity page leads with a plain-English summary of what you are looking\nat, and a built-in \"Ask CitrateScan\" capability answers questions using read-only on-chain tool calls,\nlinking the reads behind each answer. The same tools are exposed to outside agents over a Model Context\nProtocol server, so a tool like Claude, ChatGPT, or Cursor can treat CitrateScan as its read surface for\nthe network.\n\nThe product is a client single-page app served from one route, with a hash router and a command palette,\nbacked by a read API. An always-on indexer worker streams new heads into a Postgres database; the app reads\nthat index and falls through to live RPC when the index is not provisioned, so it degrades honestly rather\nthan failing. (Source: `citrate-explorer/README.md`; `src/app/page.tsx`, which loads `@/scan/app`.)\n\n## How to use it\n\n1. Open CitrateScan and type into the search bar: an account address, a transaction hash, a block id, a\n contract, or a plain-English question. Press Enter, or press `⌘K` for the command palette, which\n classifies your input and either routes to the right screen or asks the agent.\n2. On an entity page, read the plain-English summary first, then drill into the detail below it.\n3. To ask a follow-up, open Ask CitrateScan and type your question. The answer cites the on-chain reads it\n rests on.\n4. To verify a contract, open the contract's page and submit its source for recompile-and-diff.\n5. To use it from a script or an agent, get an API key from the developer hub, then call `/api/v1` for\n tooling that already speaks the Etherscan request shape, or point your agent at `/api/mcp`.\n\nFor a guided run, see [explore a transaction](/apps/tutorials/explore-a-transaction).\n\n## Reference\n\n### Screens\n\nThe app is a single page whose hash router swaps between these screens. Code lives in\n`citrate-explorer/src/scan/screens/`.\n\n| Screen | What you see | Source |\n|---|---|---|\n| Home and search | Search bar that accepts an address, transaction hash, block, contract, or a question; live chain status; recent activity. `⌘K` opens a command palette. | `screens/home.tsx`, `src/scan/app.tsx` |\n| Transaction | One transaction with a plain-English explanation, status, value in dual units (SALT and raw grains), and decoded detail. | `screens/tx.tsx` |\n| Block | A DAG block: blue score, selected parent and merge parents, depth (the depth≥100 flag), included transactions. | `screens/entity.tsx` |\n| Address | Balance, transaction history, and activity for an account. | `screens/entity.tsx` |\n| Token | Credit overview and transfers. | `screens/entity.tsx` |\n| Contract | Contract code, ABI, and a read surface; an address is treated as a contract only after `eth_getCode` confirms it carries code. | `screens/contract.tsx` |\n| Verify | Submit source for multi-version `solc` recompile-and-diff verification, run in a sandboxed microVM. | `screens/verify.tsx` |\n| Live DAG | A real-time view of the DAG: multiple tips, selected and merge parents, blue ordering. | `screens/dag.tsx` |\n| Ask CitrateScan | An agent drawer that answers questions with read-only chain tool calls and links its evidence. | `screens/agent.tsx` |\n| Settings and developer hub | Theme and verbosity settings, plus API keys and an endpoint reference. | `screens/settings.tsx` |\n\n### Read API, Etherscan request shape\n\n`GET /api/v1?module=&action=&...&apikey=` returns the `{ status, message, result }` envelope that existing\nEtherscan-shaped tooling expects, so those scripts work against CitrateScan with the base URL changed. The\n`proxy` module is a JSON-RPC passthrough restricted to an allowlist of read methods; `account`,\n`transaction`, `block`, `logs`, `stats`, and `gastracker` modules wrap indexed or RPC reads. Requests are\nAPI-key-gated and rate-limited. Index-dependent actions return an honest \"no data\" or \"pending\" message when\nthe indexer and database are not provisioned, rather than inventing a result.\n(Source: `citrate-explorer/src/app/api/v1/route.ts`; `EXPLORER_SPEC.md` section 3.)\n\nDedicated read endpoints sit under `/api/`:\n\n| Endpoint | Returns | Source |\n|---|---|---|\n| `/api/tx/[hash]` | A transaction and receipt, enriched with the block timestamp, blue score, and the depth≥100 flag (JSON field `finalized`, a display heuristic, not protocol finality), in one fetch. | `src/app/api/tx/[hash]/route.ts` |\n| `/api/blocks`, `/api/blocks/[id]` | Recent blocks and a single block. | `src/app/api/blocks/` |\n| `/api/address/[addr]` | Account balance and activity. | `src/app/api/address/[addr]/route.ts` |\n| `/api/contract/[addr]` | Contract code, ABI, and read surface. | `src/app/api/contract/[addr]/route.ts` |\n| `/api/dag`, `/api/dag/stream` | DAG stats, and a live stream of new heads. | `src/app/api/dag/` |\n| `/api/search` | Classifies a query and resolves it to an entity. | `src/app/api/search/route.ts` |\n| `/api/latest`, `/api/health` | Latest activity and a health check. | `src/app/api/latest/`, `src/app/api/health/` |\n| `/api/verify`, `/api/verify/[guid]` | Submit and poll a contract verification. | `src/app/api/verify/` |\n\n### MCP server\n\n`/api/mcp` is a read-only Model Context Protocol server. It speaks JSON-RPC 2.0 over HTTP POST\n(`initialize`, `tools/list`, `tools/call`, `ping`, and `notifications/initialized`); a `GET` returns a\ndiscovery manifest. The tools are generated from the same `citrateTools()` the in-app agent uses, so the\ntwo surfaces cannot drift, and they include `getBlock`, `getTransaction`, `getAddress`,\n`searchTransactions`, `getContractCode`, `getToken`, `findTransfers`, and `ledger`. Server info advertises\n`readOnly: true` and dual-unit amounts (SALT and raw grains). Calls are rate-limited per IP, or per API key\nwhen one is presented, and audited under the caller's key identity.\n(Source: `citrate-explorer/src/app/api/mcp/route.ts`, `src/lib/ai/tools.ts`.)\n\n## Design rationale\n\nA linear-chain explorer would read Citrate wrongly: it would show a height where the network orders by blue\nscore, and it would offer a confirmation countdown that does not exist here. CitrateScan reads the DAG in\nthe network's own terms so that what you see matches what the network actually decided. The agent and the\nMCP server share one tool set rather than two, which is the reason the in-app answers and the external\nanswers stay consistent: there is no second list to fall out of date. The indexer is kept off the request\npath and falls through to live RPC, so the app still answers when the database is absent, at the cost of\nsome history that only the index can serve.\n\n## Failure modes\n\n- The read API and the MCP server expose read-only surfaces. The MCP server advertises `readOnly: true`,\n and the `proxy` module is held to an allowlist of read methods, so a write method routed through it is\n refused rather than passed along.\n- Write and relay paths, including the gasless relayer, are out of scope for the read surfaces documented\n here.\n- `getblockcountdown` on the Etherscan-shaped surface returns an error on purpose, because confirmation on\n Citrate is measured by depth. Read the DAG stats and the depth rule instead of waiting for a countdown.\n- Index-dependent reads return an explicit \"pending\" or \"no data\" message when the indexer is not\n provisioned. They do not fabricate a result, so a degraded deployment is visible rather than silent.\n\n## Access and canon\n\nPublic. CitrateScan is shared network infrastructure: open, self-hostable, and read-only across the surfaces\ndocumented here. A developer needs it to build, and it exposes no write path, so it stays public. No\nsecrets appear on this page. Endpoints are public routes, and API keys are issued to you inside the app and\nmust never be pasted into shared docs.\n\n## Source and verification\n\n- Source repo: `citrate-explorer` (brand: CitrateScan), Apache-2.0, Citrate Inc.\n- Audited against: `6faab8a`.\n- Key paths: `src/app/page.tsx`, `src/scan/screens/`, `src/app/api/v1/route.ts`,\n `src/app/api/mcp/route.ts`, `src/app/api/tx/[hash]/route.ts`, `src/lib/ai/tools.ts`. Reference specs:\n `README.md`, `EXPLORER_SPEC.md`.\n- Status: Implemented (pre-audit). Per the repo, bootstrap is complete and the indexer and agent\n foundation are in progress; treat indexed and agent features as pre-GA, since they read through to live\n RPC and skip persistence until a database is provisioned. This page describes code at the pinned SHA and\n links to it rather than copying it.\n\nSee also [Citrate-native operations](/apps/native) for the chain operations the explorer reads.\n"},"/apps/landing":{"slug":"/apps/landing","title":"The Citrate marketing site","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-landing/README.md, citrate-landing/src/app, citrate-landing/src/lib","syncedSha":"63adc44","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The public website for the Citrate Network is the first place most people meet us. It explains what the\nnetwork is, who it serves, and how to reach the team, and it holds none of your plaintext: anything you type\ninto a form is encrypted before it is written down.\n\n## What it is\n\nThe marketing site is a plain website with one unusual property. It is built on Next.js 16 with the App\nRouter, backed by Neon Postgres through Drizzle, and runs on Vercel. Around twenty pages describe the\nnetwork and the institutions it is built for, from the core marketing surfaces to the American Learning\nFederation, Groves, membership, join, and desktop-download flows. Three contact forms let you reach us.\n\nThe property worth knowing is that the site never keeps what you type in readable form. Every form\nsubmission is encrypted with AES-256-GCM before it reaches the database, so the stored columns are\nciphertext and nothing else. The only plaintext use is a notification email so the team can write back. The\nsite is a public front door for the network described in [what Citrate is](/start/what-is-citrate); it makes\nno claim of its own beyond that.\n\n## How to use it\n\nYou read the pages, and if you want to talk to us, you submit a form.\n\n1. Browse the pages: the core surfaces are home, solutions, technology, compliance, constitution, about,\n products, enterprise, resources, FAQ, and legal, with further pages for the American Learning Federation,\n Groves, membership, the join flow, open source, and the desktop download. Each is a server-rendered page\n under `src/app/`.\n2. Choose the form that fits. Contact is for general inquiries, host-compute is to apply to run hardware on\n the network, and verification-packet is to request our compliance and security documentation.\n3. Fill in the fields and submit. You receive a confirmation, and the team is notified by email and follows\n up.\n\nThere is no account to create and nothing to install. The site is read and submit.\n\n## Reference\n\nThe core pages and the three form endpoints, each citing its path in `citrate-landing`.\n\n| Page | Route | What it covers |\n|---|---|---|\n| Home | `/` | Hero, the network at a glance, the sectors and public doors |\n| Solutions | `/solutions` | What you can build and run on the network |\n| Technology | `/technology` | The substrate, consensus, and on-premise model |\n| Compliance | `/solutions/compliance` | The compliance posture by deployment context |\n| Constitution | `/constitution` | Network governance |\n| Products | `/products` | The applications and surfaces on the network |\n| Enterprise | `/enterprise` | The on-premise enterprise offering |\n| About | `/about` | The team and the mission |\n| Resources | `/resources` | Documentation and reading |\n| Legal | `/legal` | Terms and policies |\n\nFurther pages cover the American Learning Federation (`/alf`), Groves (`/groves`), membership\n(`/membership`), the join flow (`/join`), open source (`/open-source`), and the desktop download\n(`/download/desktop`, `/download/get`). The host-compute application posts to `/api/host-compute`; it has no\ndedicated page.\n\n| Form endpoint | Method | Purpose |\n|---|---|---|\n| `/api/contact` | `POST` | General contact |\n| `/api/host-compute` | `POST` | Apply to host compute |\n| `/api/verification-packet` | `POST` | Request the verification packet |\n| `/api/challenge` | `GET` | Issues a short-lived, single-use submission token |\n\nSearch and machine readers are served by a sitemap, a `robots.txt` written to welcome agents, JSON-LD for\n`Organization` and `WebSite`, and dynamic Open Graph images from `/api/og`.\n\n## Design rationale\n\nA site that gathers inquiries from schools, hospitals, and contractors is gathering names and email\naddresses, which are exactly the records those institutions are careful with. So the site is built to hold\nnone of it in the clear. Submissions are written as AES-256-GCM ciphertext through envelope encryption\n(`src/lib/encryption.ts`), and email is deduplicated with a keyed HMAC blind index, so even the lookup value\nis not your address in plaintext. Submissions pass an origin check, a hidden honeypot, a Cloudflare Turnstile\nchallenge, a Postgres-backed sliding-window rate limit, and the single-use token from `/api/challenge`. The\ntrade is that a form submission does a little more work before it lands; for the records involved, that is\nthe right trade.\n\n## Access and canon\n\nPublic. A marketing site is public by definition, and this page carries no secrets, keys, or private\nendpoints. None live in the repository either: `.env*` files are ignored by git, only `.env.example` is\ncommitted, and the real values for the encryption key, database URL, and SMTP password live in Vercel project\nenvironment variables. The encryption is the load-bearing fact for a visitor: forms are stored as ciphertext\nonly, and the single plaintext use is the team's reply.\n\n## Source and verification\n\n- Source repo: `citrate-landing`, `README.md`.\n- Audited against SHA: `63adc44`.\n- Key paths: `src/lib/encryption.ts`, `src/lib/schemas.ts`, `src/app/api/contact/route.ts`,\n `src/app/api/host-compute/route.ts`, `src/app/api/verification-packet/route.ts`,\n `src/app/api/challenge/route.ts`, `src/app/` (nine pages).\n- Status: Implemented. The site is in production and actively maintained, with a strict Content Security\n Policy carrying a per-request nonce, HSTS, an OWASP ZAP baseline scan in CI, and Playwright tests across\n five viewports. The posture statements on the site are descriptive; they are not a third-party\n certification.\n"},"/apps/learning-center":{"slug":"/apps/learning-center","title":"Citrate Learning Center","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-learning-center/{README.md, gui/, cli-school-bootstrap/, Cargo.toml}","syncedSha":"a34f976","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Citrate Learning Center is the classroom application for school pilots: students do their coursework,\nteachers run their classrooms, and administrators provision and oversee the school. It is a native desktop\napplication, and it is built around one rule, student and guardian data stays on the school's own hardware,\non Citrate Ground, and never leaves it for the public Citrate Network.\n\n## What it is\n\nLearning Center is a desktop client written in Rust with a Slint interface, so it runs as a native window\nbacked by a local service layer rather than a web page. A school installs it; it is not picked up ad hoc by\nindividuals. The data it works with, rosters, corrections, classroom membership, lives encrypted on the\nmachine it runs on. The public Citrate Network is consulted only for what genuinely belongs there: a\nparticipant's role is read from chain 40204 through a gateway, never asserted by the client, and students\nappear on chain only as pseudonymous identifiers, never by name.\n\nThe application is one of three crates in a Cargo workspace (`Cargo.toml`):\n\n- `gui/citrate_learning_center`, the desktop window and every classroom and administration screen.\n- `gui/citrate_edu_app`, the education backend: encryption, identity, roles, the roster, classroom,\n budget, and institutional services, and the encrypted local store.\n- `cli-school-bootstrap`, a command-line tool that provisions a school before staff ever open the desktop\n application.\n\nIt is role-aware. The same window shows a different left sidebar depending on whether you sign in as a\nstudent, a teaching assistant, a teacher, an administrator, an IT director, or a charter-management\noperator. The role itself comes from an on-chain query, not from the interface, so hiding a sidebar item is\nnever what keeps a user out of an action.\n\n## How to use it\n\nA school is brought online in a deliberate order. An operator provisions it first, then hands the desktop\napplication to staff.\n\n1. An operator runs `cli-school-bootstrap init` to stand up the school, choosing a charter-management\n organization, a standalone district, or a school under an existing CMO. Version 0.4 supports self-host\n mode only; the `--hosted` path reports that it is not yet available (`cli-school-bootstrap/src/cli.rs`).\n2. The bootstrap can run a Docusign Connect receiver (`daemon`) so consent and agreement envelopes update\n the bootstrap state as signers complete them, which can take days and survives restarts idempotently.\n3. The operator generates per-guardian setup packets from an imported roster and distributes them\n (`generate-guardian-packets`). Guardian PII appears only inside that guardian's own packet; logs use\n pseudonymous identifiers.\n4. Build and run the desktop application:\n\n ```bash\n cargo build --release -p citrate-learning-center\n cargo run --release -p citrate-learning-center\n ```\n\n5. Staff and students sign in. A password gate unlocks the local store, the backend decrypts the data on\n the machine, and the role-appropriate screens appear.\n\n## Reference\n\nThe screens are Slint views under `gui/citrate_learning_center/ui/`; the sidebar groups and labels below\nare quoted from `ui/shell/sidebar.slint` and are gated by the on-chain role.\n\n| Role | Sidebar groups and items |\n|---|---|\n| Student, TA | Home, Assignments, Progress |\n| Teacher | CLASSROOM: Home, Students, Assignments. FINANCE: Budget |\n| Admin, SuperAdmin | INSTITUTION: Overview, Classrooms, Staff. FINANCE: Budget, Approvals |\n| IT | ACCOUNTS: User Accounts, Bulk Import. DEVICES: Fleet. INFRASTRUCTURE: Node Status, Security |\n| No role | Getting Started |\n\nSettings is always present, and the shell adds onboarding and a password-gated lock screen. The\n`CMOSuperAdmin` role exists on chain and drives cross-school administration through the CMO portal service,\nbut this build renders no distinct CMO sidebar group; a CMOSuperAdmin sees the administrator views.\n\nThe backend services that stand behind those screens (`gui/citrate_edu_app/src/services/`):\n\n| Service | What it does |\n|---|---|\n| `roster.rs` | Bulk import from SIS exports (Infinite Campus, PowerSchool), as CSV, TSV, or XLSX. |\n| `classroom.rs` | Classrooms, devices, and assignments. |\n| `budget.rs` | Budget allocation and cashout requests. |\n| `institutional.rs` | Vault status and the cashout approval lifecycle. |\n| `cmo_portal.rs` | Cross-school administration for charter-management operators. |\n\nThe provisioning CLI subcommands (`cli-school-bootstrap/src/cli.rs`):\n\n| Command | What it does |\n|---|---|\n| `init` | Start a bootstrap workflow; `--config` skips prompts for scripted runs. |\n| `status` | Show which steps are complete, in progress, or next. |\n| `resume` | Resume from the last checkpoint, idempotently. |\n| `reset` | Delete the local state directory. Does not void already-sent Docusign envelopes. |\n| `daemon` | Run the Docusign Connect receiver (default `127.0.0.1:8091`, behind a TLS-terminating proxy). |\n| `generate-guardian-packets` | Produce and distribute per-guardian setup packets from a roster. |\n| `revoke-guardian-packet` | Revoke a guardian's packet for a right-to-erasure request. |\n\nGlobal flags include `--state-dir` and `--hosted` (reserved for a later release). Guardian delivery over\nSMTP (`--smtp`) and as PDF (`--print-pdf`) are reserved for v1.1; the filesystem channel is the v1 default.\n\n## Design rationale\n\nA school's most sensitive asset is its students' records, and the regulation around them is unforgiving.\nSo Learning Center keeps that data where it already is, on the school's hardware, and treats the public\nnetwork as a place for roles and proofs, not for names. Identity is pseudonymous on chain: a student\nbecomes a keyed hash of their provider identifier, derived with an institution-held secret, so the chain\ncan route and reward learning without ever holding a name. The role a user holds is read from chain and\nchecked in the backend, which is why the sidebar is a convenience and not a control. The provisioning step\nis a separate CLI rather than a button in the application because standing up a school, with consent\nenvelopes and guardian packets, is operator work that can take days and must be auditable.\n\n## Failure modes\n\nThis application handles K-12 student and guardian data, so its boundaries fail closed.\n\n- Local data is encrypted at rest with AES-256-GCM through `citrate-security`, with the associated data\n bound into the GCM tag, so a swapped ciphertext fails verification rather than decrypting\n (`src/encryption.rs`, `src/local_store.rs`). No plaintext correction lands on disk.\n- Role separation is enforced in the backend (`src/role.rs`, `src/it_elevation.rs`), not by hiding\n sidebar items. A locked account resolves to no role. Privileged actions carry dedicated coverage tests\n (`tests/k1_4_privileged_actions_coverage.rs`).\n- Privileged actions require a fresh password reauthentication within a 60-second window\n (`tests/rem_g_02_fresh_password_gate.rs`). The IT-elevation bridge that lets a small-district\n administrator act as IT is time-bounded, audited on entry and exit, and gated on the same reauth.\n- `reset` is destructive and does not cancel sent Docusign envelopes; those must be voided in the Docusign\n tenant separately.\n\n## Access and canon\n\nTier: academic. This is education and institutional material tied to school pilots, not a public consumer\nsurface.\n\nUS K-12 public schools have free Citrate access in perpetuity, and Learning Center is the classroom that\naccess opens onto. The school runs it on its own hardware as part of Citrate Ground; student and guardian\ndata, rosters, corrections, and identity mappings, stay there, encrypted at rest, and never reach the\npublic Citrate Network. Students appear on chain only as pseudonymous identifiers. The compliance floor for\nschools is FERPA, COPPA, and CIPA; the right-to-erasure path for guardian records is built into the\nprovisioning CLI. No secrets are reproduced here: the institution's org secret is loaded from its\nencrypted keystore in production, and Docusign credentials are operator configuration. See\n[enterprise compliance](/enterprise/compliance) for the FERPA, COPPA, and CIPA model and [Citrate Schools](/contracts/edu)\nfor the program.\n\n## Source and verification\n\n- Source repo: `citrate-learning-center`, audited against SHA `a34f976`.\n- Key paths: `README.md`, `Cargo.toml`, `gui/citrate_learning_center/ui/shell/sidebar.slint`,\n `gui/citrate_edu_app/src/` (`role.rs`, `identity.rs`, `it_elevation.rs`, `encryption.rs`,\n `local_store.rs`, `key_rotation.rs`, `services/`), `cli-school-bootstrap/src/cli.rs`,\n `gui/citrate_learning_center/tests/`.\n- Status: Implemented (pre-audit), pre-1.0 at version 0.4.0. Version 1 is self-host only; the hosted\n parent portal (`--hosted`) and the SMTP and PDF guardian-delivery channels are reserved for later\n releases. The repo carries a planning directory (`cli-edu/`) that has no `Cargo.toml` and is excluded\n from the workspace; build only the three real crates. Tier 1 audit applies:\n no stable release ships without a written external attestation against an exact SHA.\n"},"/apps/memories":{"slug":"/apps/memories","title":"Memrizz (agent-memory DAG and MCP)","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-memories","syncedSha":"a616e75","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"The webapp","anchor":"the-webapp"},{"depth":3,"text":"Connecting an agent over MCP","anchor":"connecting-an-agent-over-mcp"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"Webapp surfaces","anchor":"webapp-surfaces"},{"depth":3,"text":"MCP tools","anchor":"mcp-tools"},{"depth":3,"text":"Gateway and engine","anchor":"gateway-and-engine"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Memrizz is the webapp and MCP server for a federated agent-memory DAG, \"git for agents.\" It gives a\nteam fast, provenance-carrying access to its organizational memory across many repositories, and it\ngives an agent durable, auditable memory through the same store over MCP.\n\n## What it is\n\nMemrizz (repo `citrate-memories`) holds a team's memory as a knowledge DAG and presents it through two\nfaces. The webapp renders that memory as a 2.5D constellation you can fly through, with conversational\nrecall, time-travel, and a review center where a person confirms or rejects what the system proposes.\nThe MCP server exposes the same memory to agents as a set of tools, so a model in Claude Desktop,\nClaude Code, Cursor, or a generic client can recall, search, verify, and contribute to it.\n\nUnderneath, the store is two-plane. A Derived plane rebuilds deterministically from git, markdown, and\nmanifests, so it can always be reconstructed from the repositories themselves. An Asserted plane holds\nsigned human and agent claims, append-only, canonical for its own content. The split keeps the\ndistinction between what was reconstructed and what was asserted, and it is the same kind of memory\nsubstrate the [research pages](/research/learning) describe.\n\nEverything is scoped to an Org and isolated per Org. Every call is authorized by a signed capability\ngrant, and every read, write, and denial is recorded to a tamper-evident hash-chained audit log. The\nmemory in an Org is treated as client property; the platform operator role deliberately has no access\nto memory content.\n\n## How to use it\n\n### The webapp\n\n1. Sign in with Citrate over OIDC with PKCE, then pick your Org.\n2. Explore the constellation, or use Ask to query in natural language and get answers with citations\n that light up the nodes they came from.\n3. Use the node inspector to follow a claim's provenance and verify it, and the review center to\n confirm or reject proposed edges, keeping a person in the loop.\n\n### Connecting an agent over MCP\n\nThe MCP server speaks JSON-RPC over stdio as a headless daemon, or Streamable-HTTP through the gateway\nat `POST /mcp/u/:sub`. From the Connect page you mint a short-lived token and copy a config block into\nClaude Desktop, Claude Code, Cursor, or a generic client. The gateway authorizes every call against\nyour Org membership and your capability grant, and audits it. The agentic side, including the MCP\nbridge and the RPC surface, is covered under [chain RPC](/chain/rpc).\n\n## Reference\n\n### Webapp surfaces\n\nDefined in `PLANSET/06_WEBAPP_FRONTEND_SPEC.md`.\n\n| Surface | What you see |\n|---|---|\n| Constellation | A 2.5D DAG explorer with layout modes, an `as_of` time-scrubber, and blast-radius focus. |\n| Ask | Conversational recall with a model picker; citations light up the nodes they draw from. |\n| Node inspector | Identity, plane and trust badges, a source pointer that links rather than copies, the verify verdict, and neighbors. |\n| Review Center | An HIC (Human In Control) inbox of edge proposals, contradictions, supersessions, and self-critic findings. |\n| Org and Audit | A federation overview and the integrity-verified, hash-chained audit log. |\n| Connect | Mints your personal MCP endpoint and a short-lived token, with copy-paste client config. |\n\n### MCP tools\n\nDefined in `crates/mem-mcp/src/lib.rs`. Read tools return content; write tools record signed\nassertions and are quarantined by default for inferred content.\n\n| Kind | Tools |\n|---|---|\n| Read | `memory.recall`, `memory.search`, `memory.neighbors`, `memory.as_of`, `memory.verify`, `memory.critique`, `memory.analogy` |\n| Write | `memory.assert`, `memory.propose_edge`, `memory.confirm_edge`, `memory.merge_diff` |\n\nEvery response carries provenance, a trust tier, and a freshness watermark.\n\n### Gateway and engine\n\nThe system runs as three parts, described in `PLANSET/07_IMPLEMENTATION_AND_HARDENING_PLAN.md`.\n\n| Part | Role | Source |\n|---|---|---|\n| Webapp | The browser front end and OIDC relying party. | `webapp/` |\n| Gateway | Authentication, Org resolution, authorization, the HTTP and JSON API, and the SSE stream. | `crates/mem-gateway/` (`auth.rs`, `control.rs`, `oidc.rs`, `http.rs`) and the `crates/mem-authz/` policy crate |\n| Engine | The memory store and vector index. | the engine crates |\n\n## Design rationale\n\nTwo planes exist because reconstructed knowledge and asserted knowledge carry different guarantees and\nmust not be confused. The Derived plane can always be rebuilt from the repositories, so it never needs\nto be trusted on faith; the Asserted plane is append-only and signed, so a claim's author and time are\nfixed. Org isolation, signed capability grants, and the audit chain follow from treating memory as\nclient property: the operator who runs the platform should be able to keep it healthy without being\nable to read what it holds. Inferred writes are quarantined by default and surfaced in the review\ncenter, so a person decides what becomes canonical rather than the model deciding silently.\n\n## Failure modes\n\nThis surface holds client memory, so it is built to fail closed.\n\n- Authentication is OIDC with the signing algorithm pinned to RS256 and read from configuration rather\n than the token header, with issuer, audience, and expiry enforced. A token that does not satisfy\n these is rejected, and the gateway refuses to start without its OIDC configuration.\n- Every call is checked against Org membership and a signed, resource-scoped capability grant. A call\n outside the grant is denied, and the denial is recorded.\n- The audit log is a hash chain. A break in the chain is detectable, and the Org view shows whether\n the chain is intact.\n- Memory content is encrypted at rest with XChaCha20-Poly1305, with a per-tenant data key sealed under a\n per-Org keyring.\n- The platform operator role has no access to memory content by design.\n- No secrets appear in this page. None of the OIDC, capability, or encryption keys are reproduced here.\n\n## Access and canon\n\nCommercial. This is paid, contracted, multi-Org product depth, and memory is client property isolated\nper Org. The store reuses pieces of the Citrate Network engine, and its audit roots are designed to\nanchor periodically to the public ledger; the federated-learning surface it sits alongside is\n[Citrate Orchard](/research/learning). On-premise sovereignty and in-house identity verification (VERI) hold\nacross Citrate, as described in [what Citrate is](/start/what-is-citrate).\n\n## Source and verification\n\n- Source repo: `citrate-memories`. The product is named Memrizz; an earlier working codename still\n lingers in some spec and crate comments and is not used in Almanac.\n- Audited against: `a616e75`.\n- Key paths: `crates/mem-mcp/src/lib.rs`, `crates/mem-gateway/` (`auth.rs`, `control.rs`, `oidc.rs`,\n `http.rs`), `crates/mem-authz/`, `crates/mem-store/src/shred.rs`, `PLANSET/00` to `07`, `webapp/`,\n `README.md`.\n- Status by area:\n - Security foundation (milestone M0, landed 2026-06-14): **Implemented (pre-audit).** Org\n control-plane and isolation, OIDC verification, capability-grant authorization, the HTTP read and\n write API, the MCP-over-HTTP bridge, and durable control-plane persistence.\n - See, Ask, and Steward MVP (milestone M1): **Specified**, in progress. The constellation,\n conversational recall, the review center, signed assert and confirm, and the SSE audit tail are\n being built. Later milestones add the admin console, model-bring-your-own revoke, operations, and\n crypto-shred forget.\n - Known limits: encryption is classical today, with a post-quantum hybrid (Kyber-768 and X25519)\n roadmapped; crypto-shred is per-Org rather than per-recipient; v1 is exploratory with a v2\n greenfield rebuild planned.\n - A Tier-1 external audit is required before any non-internal exposure, and none has been completed.\n"},"/apps/native":{"slug":"/apps/native","title":"The Citrate Keyring desktop app","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-native/README.md, Cargo.toml, gui/citrate_native/ui/","syncedSha":"bc0e8ba","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The Citrate Keyring desktop app is a native desktop application that holds a Citrate Keyring account and,\nin the same window, gives you a reader for the BlockDAG. It is for anyone who wants the account and the\nnetwork in a real desktop window rather than a browser tab.\n\n## What it is\n\nThe app is built with Slint, a native Rust user-interface toolkit, so it opens as a desktop window with a\nlocal service layer behind it that talks to the chain. It is a Cargo workspace with three crates\n(`Cargo.toml`): `gui/citrate_ui_kit`, the shared interface kit; `gui/citrate_native`, the desktop\napplication and its screens, which is the default build target; and `gui/citrate_desktop_app`, the backend\nservice layer that holds the chain client, the account, the mempool, and the RPC.\n\nThe window is organized around a left sidebar with grouped navigation (`gui/citrate_native/ui/shell/\nsidebar.slint`). A regular account sees the `BLOCKCHAIN`, `AI`, `DEVELOPER`, `LEARNING`, and `OPERATIONS`\ngroups. An\naccount whose identity is a school operator, a CMOSuperAdmin, additionally sees a CMO group for\nadministering a charter or management organization. The mental model is one encrypted local identity that\nopens onto your account, a reader for the network, and an on-ramp to the compute and learning marketplaces.\n\nThe account is local-first. It is encrypted with a password you choose, and the password never leaves the\ndevice. The DAG view here is a local reader against your own node; the full public explorer is\n[CitrateScan](/apps/explorer), and the account abstraction it shares with the browser extension is covered\nunder [passkeys](/aa/passkeys) and [guardians](/aa/guardians).\n\n## How to use it\n\nYou build the app from source and run it. The short version is below; the full walk-through, including the\nprerequisites, is in [run the Citrate Keyring desktop app](/apps/tutorials/run-the-desktop-wallet).\n\n1. Install a stable Rust toolchain. The repository pins `channel = \"stable\"` with `rustfmt` and `clippy`\n in `rust-toolchain.toml`, so rustup picks it up.\n2. Make sure your personal GitHub SSH key can read the sibling repositories `citrate-chain`,\n `citrate-learning-center`, and `citrate-agent-runtime`. The build pulls chain crates over SSH, and\n organization membership grants the access.\n3. Install a C and C++ toolchain and the system libraries Slint and `rocksdb` need for your platform.\n4. Build and run:\n\n```bash\ncargo build --release\ncargo run --release -p citrate-native\n```\n\n`citrate-native` is the workspace default member, so `cargo run --release` without `-p` launches the same\napplication. For a faster iteration loop, omit `--release`.\n\n5. On first launch the onboarding flow opens: a welcome screen, a password of at least eight characters, a\n provisioning step that generates and shows your recovery phrase, and a confirmation that you backed the\n phrase up. After that the app shell opens to the sidebar. If you already have an account, use the import\n option to restore from a recovery phrase or a key.\n\n## Reference\n\nThe screens below are the Slint views under `gui/citrate_native/ui/`. Sidebar labels are quoted from\n`ui/shell/sidebar.slint`.\n\n| Group | Screen | What it does | Source |\n|---|---|---|---|\n| Onboarding | Onboarding | Welcome, password, provisioning with a recovery phrase, confirmation | `ui/onboarding/onboarding.slint` |\n| Shell | Lock screen | Locks the app behind your password between sessions | `ui/shell/lock_screen.slint` |\n| `BLOCKCHAIN` | Dashboard | The account and network overview | `ui/dashboard/dashboard.slint` |\n| `BLOCKCHAIN` | `Wallet` | Balances, transaction history, import | `ui/wallet/wallet.slint` |\n| `BLOCKCHAIN` | DAG Explorer | A local reader of the BlockDAG with a transaction detail modal | `ui/dag/dag_explorer.slint` |\n| AI | Chat | A chat view from the shared interface kit | `ui/app.slint` |\n| AI | Models | Browse and manage models | `ui/models/models.slint` |\n| Developer | Compute | Opt-in compute sharing, detects your hardware, shows provider status | `ui/compute/compute.slint` |\n| Developer | Files | File storage entries | `ui/storage/storage.slint` |\n| Learning | Learn | Contribution pools, your stake, and earnings in SALT | `ui/learning/learning.slint`, `ui/learning/edu_panel.slint` |\n| Operations | Agent Center | An activity trail and an approvals queue for agent operations | `ui/operations/operations_view.slint` |\n| Settings | Settings | Environment, AI config, system health, peers, node control, knowledge graph, integrations, appearance, and a danger zone | `ui/settings/` |\n| CMO | Dashboard, Tenancy, Compliance | School administration, role-gated to CMOSuperAdmin | `ui/cmo/` |\n\nThe send dialog (`ui/wallet/send_dialog.slint`) takes a to address and an amount in SALT, shows a review\nof recipient, amount, and gas, then sends on Confirm & Send. When your account is linked to a Citrate\nKeyring smart account, a sponsored toggle appears; with it on, the send goes from the smart account and\nthe gas line reads \"Sponsored by Citrate\" instead of a gas figure. This is the EW-S1 sponsored-send work\nand it shows only when a linked smart account is available.\n\n## Design rationale\n\nThe app is native rather than a web page so the account, the node reader, and the marketplaces share one\nlocal process and one encrypted identity, with the data staying on the machine. The DAG view is a local\nreader rather than a second public explorer because the device already has a node to read; when you want\nthe shared, queryable view of the network you go to [CitrateScan](/apps/explorer). The marketplace and\nschool-administration screens are honest about reach: they say plainly when a contract is not reachable\nand show empty states rather than inventing numbers, which is why several CMO aggregates are stubbed\nbehind an environment flag until their wiring lands.\n\n## Failure modes\n\n- The account is encrypted with your password, and the password never leaves the device. There is no\n server-side recovery; the recovery phrase shown at provisioning is the backup. Write it down and store\n it offline.\n- The app locks between sessions. Reopening it lands on the lock screen, and you unlock with your password.\n- The Settings danger zone performs destructive actions. Read the in-app warnings before using it.\n- The build pulls chain crates over SSH using per-host aliases, while a few sibling repositories still use\n plain `github.com`. Cargo can then fetch a chain crate such as `citrate-wallet-core` twice and treat the\n copies as different sources. It compiles today; if you hit a type mismatch at a chain-API boundary, this\n double-fetch is the likely cause. A planned follow-up normalizes the URL convention across these repos.\n- The CMO aggregates are demo-stubbed behind the `CITRATE_CMO_DEMO` environment flag in version 1. Treat\n those numbers as illustrative until the wiring lands.\n\n## Access and canon\n\nPublic. This is an end-user guide to the desktop account and its screen map. No keys, recovery phrases,\nprivate endpoints, or credentials appear here. The account password and the recovery phrase are created\nand held on your device. Building from source uses your own GitHub SSH access to the sibling\nrepositories; the CI deploy keys named in the README are operator infrastructure, not user-facing, and\nare not reproduced here. The CMO screens are role-gated to CMOSuperAdmin identities.\n\n## Source and verification\n\n- Source repo: `citrate-native`, audited against SHA `bc0e8ba`.\n- Files read: `README.md`, `Cargo.toml`, `rust-toolchain.toml`,\n `gui/citrate_native/ui/shell/sidebar.slint`, `ui/app.slint`, `ui/onboarding/onboarding.slint`,\n `ui/wallet/wallet.slint`, `ui/wallet/send_dialog.slint`, `ui/dag/dag_explorer.slint`,\n `ui/compute/compute.slint`, `ui/learning/learning.slint`, `ui/cmo/`, `ui/operations/operations_view.slint`,\n `ui/settings/`, `gui/citrate_native/src/main.rs`, `gui/citrate_native/tests/e2e_wallet.rs`.\n- Status: Implemented, version 0.4.0, pre-1.0. Account creation, import from a recovery phrase or key,\n the lock screen, send with the sponsored toggle, and the DAG reader are Implemented and covered by\n end-to-end tests. Some marketplace and CMO aggregates are Specified, scaffolded or demo-stubbed as noted.\n Not externally certified.\n"},"/apps/studio":{"slug":"/apps/studio","title":"Citrate Studio","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-studio (BUSL-1.1)","syncedSha":"39cadf3","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Citrate Studio is the agent-control interface for node operators, a native application for driving the\nCitrate agent runtime and watching its safety machinery work. This page is the overview; the full source is\npublic in the `citrate-studio` repository under BUSL-1.1.\n\n## What it is\n\nCitrate Studio is the native interface that an operator uses to run a compliance-first agent. It puts the\nruntime's safety machinery, the approvals, the role quorum, the pre-flight checks, the tripwires, and the\naudit replay, in front of the operator as the main thing on screen rather than hidden plumbing. It is built\nin Slint, descends from the Citrate Market design system, and renders the agent runtime's own primitives\ndirectly.\n\nThe runtime it drives is the [agent runtime](/compute/agent-runtime), and the people it is for are the\n[node operators](/operators/run-a-node) who run agents on their own hardware and want the safety controls\nto be visible and usable.\n\n## Access and canon\n\nPublic. This page is the overview; the full implementation is public in the `citrate-studio` repository\nunder BUSL-1.1 (source-available, converting to Apache-2.0 on its Change Date). The design specification,\nthe map of what is built against what is modeled, the policy, signer-roster, approval-queue, and\nCapsule-dispatch implementation, and the packaging and release detail all live in that repository. This page\nsummarizes and links to the source rather than reproducing it, and it contains no secrets.\n\n## Source and verification\n\n- Source repo: `citrate-studio` (public, BUSL-1.1). Overview audited against SHA `39cadf3`.\n- Status: Implemented. The application is a hardened release candidate with real authentication, policy\n core, signer roster, and chain reads; the precise built-versus-modeled map and the remaining 1.0 work are\n tracked in the repository.\n"},"/apps/tutorials/explore-a-transaction":{"slug":"/apps/tutorials/explore-a-transaction","title":"Tutorial: Explore a transaction","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-explorer/src/app/api/tx/[hash]/route.ts, src/app/api/v1/route.ts, src/app/api/mcp/route.ts","syncedSha":"6faab8a","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Step 1, find the transaction in the explorer","anchor":"step-1-find-the-transaction-in-the-explorer"},{"depth":3,"text":"Step 2, read the consensus context","anchor":"step-2-read-the-consensus-context"},{"depth":3,"text":"Step 3, fetch the same facts from the API","anchor":"step-3-fetch-the-same-facts-from-the-api"},{"depth":3,"text":"Step 4, use the Etherscan-shaped API, optional","anchor":"step-4-use-the-etherscan-shaped-api-optional"},{"depth":3,"text":"Step 5, ask the agent, optional","anchor":"step-5-ask-the-agent-optional"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"What you learned","anchor":"what-you-learned"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"A short walk-through of looking up one transaction in CitrateScan, reading what it did, and reading its\nconfirmation depth the way the BlockDAG measures it. You can do this with your eyes in the explorer, from the\nread API, or by asking the built-in agent.\n\n## What it is\n\nA guided lookup of a single transaction on the Citrate Network, chain id 40204. You will find the\ntransaction, read its plain-English summary, and read its depth rather than a confirmation count. The reads here are public and write nothing.\n\n## How to use it\n\nYou need a transaction hash on Citrate, a 64-hex value prefixed with `0x`. The API steps use an API key,\nwhich you can get from the explorer's developer hub. Set two variables so the commands stay short; point\n`EXPLORER` at your CitrateScan deployment.\n\n```bash\nexport EXPLORER=\"https://explorer.citrate.ai\" # your CitrateScan base URL\nexport TXHASH=\"0x\"\n```\n\n### Step 1, find the transaction in the explorer\n\n1. Open CitrateScan.\n2. Paste the transaction hash into the search bar, or press `⌘K` to open the command palette and paste it\n there.\n3. CitrateScan classifies the input as a transaction hash and opens the transaction screen.\n\nYou land on a page that leads with a plain-English summary of what the transaction did, followed by its\nstatus, value in dual units, and decoded detail. (Screen: `citrate-explorer/src/scan/screens/tx.tsx`.)\n\n### Step 2, read the consensus context\n\nOn the transaction page, note the block the transaction landed in and that block's blue score. Citrate is a\nBlockDAG under GhostDAG, so confirmation is measured by depth. CitrateScan sets its depth≥100 flag once:\n\n```text\ncurrent_blue_score − block.blue_score ≥ 100\n```\n\nThe flag is a display heuristic, not protocol finality. Confirmation on the testnet is probabilistic and\ncheckpoint finality is specified, not running. If you settle value against a transaction, pick your own confirmation depth as a risk decision. For the underlying concepts, see [read the DAG](/chain/tutorials/read-the-dag).\n\n### Step 3, fetch the same facts from the API\n\nThe transaction detail endpoint returns the transaction and receipt enriched with the block's `timestamp`,\n`blueScore`, and the depth≥100 flag (JSON field `finalized`), in one call:\n\n```bash\ncurl -s \"$EXPLORER/api/tx/$TXHASH\" | jq\n```\n\nThe response carries the core facts the explorer renders, including `methodId`, `isCreate`, and\n`finalized` (the depth≥100 heuristic, not protocol finality), so you can read block depth from a script. If the block lookup fails, the endpoint still\nreturns the transaction core without the consensus fields rather than erroring.\n(Source: `citrate-explorer/src/app/api/tx/[hash]/route.ts`.)\n\n### Step 4, use the Etherscan-shaped API, optional\n\nIf you already have tooling built for the Etherscan request shape, the same lookups work through `/api/v1`\nwith the `{ status, message, result }` envelope. The `proxy` module is a JSON-RPC passthrough over\nallowlisted read methods:\n\n```bash\n# Raw transaction through the JSON-RPC proxy\ncurl -s \"$EXPLORER/api/v1?module=proxy&action=eth_getTransactionByHash&txhash=$TXHASH&apikey=$CITRATE_API_KEY\" | jq\n\n# Receipt status (1 = success, 0 = reverted)\ncurl -s \"$EXPLORER/api/v1?module=transaction&action=gettxreceiptstatus&txhash=$TXHASH&apikey=$CITRATE_API_KEY\" | jq\n```\n\nThe `getblockcountdown` action returns an error on purpose, since confirmation on Citrate is measured by\ndepth, not a countdown. Read the DAG stats and the depth rule from step 2 instead.\n(Source: `citrate-explorer/src/app/api/v1/route.ts`.)\n\n### Step 5, ask the agent, optional\n\nOpen Ask CitrateScan and ask, in plain English:\n\n> Explain transaction `$TXHASH` and tell me whether it is final.\n\nThe agent answers using read-only on-chain tool calls and links the reads behind its answer. The same tools\nare available to outside agents over the read-only MCP server at `/api/mcp`, so you can do this from Claude,\nChatGPT, or Cursor as well. (Source: `citrate-explorer/src/scan/screens/agent.tsx`, `src/app/api/mcp/route.ts`.)\n\n## Reference\n\nThe surfaces this tutorial touches:\n\n| Surface | What it does | Source |\n|---|---|---|\n| `/api/tx/[hash]` | Transaction and receipt with block timestamp, blue score, and the depth≥100 flag (`finalized`). | `src/app/api/tx/[hash]/route.ts` |\n| `/api/v1` (`proxy`, `transaction`) | Etherscan-shaped reads over allowlisted JSON-RPC and receipt status. | `src/app/api/v1/route.ts` |\n| `/api/mcp` | Read-only MCP server exposing the same tools as the in-app agent. | `src/app/api/mcp/route.ts` |\n\n## What you learned\n\n- How to resolve a transaction in CitrateScan through the search bar or `⌘K`.\n- How to read confirmation depth the DAG-native way, blue score plus the depth≥100 flag, instead of confirmations. Confirmation is probabilistic; checkpoint finality is specified, not running.\n- Three ways to get the same facts: the `/api/tx/[hash]` endpoint, the Etherscan-shaped `/api/v1` surface,\n and the agent, in the explorer or over MCP.\n\n## Failure modes\n\n- An invalid hash, anything other than a `0x`-prefixed 64-hex value, is rejected with a 400 before any\n lookup runs.\n- A transaction the node cannot find returns a 404.\n- `getblockcountdown` on `/api/v1` returns an error by design. Use the depth rule, not a countdown.\n\n## Access and canon\n\nPublic and read-only. Nothing here writes state. API keys are issued to you inside the app and must never\nbe pasted into shared docs.\n\n## Source and verification\n\n- Repo: `citrate-explorer` (CitrateScan), audited against `6faab8a`.\n- Endpoints used: `/api/tx/[hash]`, `/api/v1` (`proxy`, `transaction`), `/api/mcp`.\n- Status: Implemented (pre-audit). These are read-only public surfaces.\n\nSee also [read the DAG](/chain/tutorials/read-the-dag) and the [JSON-RPC reference](/chain/rpc).\n"},"/apps/tutorials/install-the-wallet-extension":{"slug":"/apps/tutorials/install-the-wallet-extension","title":"Install the Citrate Keyring extension","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-wallet-extension/README.md, .github/workflows/release.yml, manifest.json","syncedSha":"930594c","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Step 1, download the release","anchor":"step-1-download-the-release"},{"depth":3,"text":"Step 2, verify the checksum","anchor":"step-2-verify-the-checksum"},{"depth":3,"text":"Step 3, unzip to a stable location","anchor":"step-3-unzip-to-a-stable-location"},{"depth":3,"text":"Step 4, open the extensions page","anchor":"step-4-open-the-extensions-page"},{"depth":3,"text":"Step 5, enable developer mode","anchor":"step-5-enable-developer-mode"},{"depth":3,"text":"Step 6, load the unpacked extension","anchor":"step-6-load-the-unpacked-extension"},{"depth":3,"text":"Step 7, create your first account","anchor":"step-7-create-your-first-account"},{"depth":3,"text":"Step 8, confirm it works","anchor":"step-8-confirm-it-works"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This walks you through loading the Citrate Keyring extension into Chrome, Edge, or Brave as an unpacked\nextension and creating your first account. It targets Citrate Network, chain id `40204`. The software is\nprerelease and pre-audit, so use small testnet values only, and note there is no recovery phrase for the\naccount you create here; if you forget the password, the account is gone.\n\n## What it is\n\nThe Citrate Keyring extension ships as a release archive of the Manifest V3 source plus an Argon2\nWebAssembly module that the project builds per release with `wasm-pack` (`wasm/build.md`). You download the\narchive, verify its checksum, unzip it, and load it from disk with the browser's developer mode. There is\nno JavaScript build step on your side.\n\n## How to use it\n\nYou will need a Chromium-based browser, Chrome, Edge, or Brave, and the two release files from the\nproject's GitHub Releases: the archive `citrate-wallet-extension.zip` and its checksum\n`citrate-wallet-extension.zip.sha256`.\n\n### Step 1, download the release\n\nDownload `citrate-wallet-extension.zip` and `citrate-wallet-extension.zip.sha256` from GitHub Releases into\nthe same directory.\n\n### Step 2, verify the checksum\n\n```bash\nshasum -a 256 -c citrate-wallet-extension.zip.sha256\n```\n\nThe output should end in `: OK`. If it does not match, stop and download again.\n\n### Step 3, unzip to a stable location\n\n```bash\nmkdir -p ~/citrate-wallet-extension\nunzip citrate-wallet-extension.zip -d ~/citrate-wallet-extension\n```\n\nKeep this folder. The browser loads the extension from disk, so do not move or delete it afterward.\n\n### Step 4, open the extensions page\n\nOpen the extensions page for your browser:\n\n- Chrome: `chrome://extensions/`\n- Edge: `edge://extensions/`\n- Brave: `brave://extensions/`\n\n### Step 5, enable developer mode\n\nTurn on the Developer mode toggle, usually at the top right of the page.\n\n### Step 6, load the unpacked extension\n\nClick Load unpacked and select the unzipped folder, the one that contains `manifest.json`. The extension\nappears in the list with its icon.\n\n### Step 7, create your first account\n\n1. Open the extension popup.\n2. Choose `Create New Wallet` and set a strong password.\n3. The extension generates your address and stores it encrypted, AES-256-GCM under an Argon2id key\n derivation. There is no recovery phrase in this version, so the password is the only way back in.\n\n### Step 8, confirm it works\n\nThe popup shows your new address and Citrate Network, chain id 40204. Open a Citrate page, for example\n[Citrate Chat](/apps/chatbot). The page discovers the provider and asks to connect; a per-origin\nconnection window opens. Approve it, then confirm that any transaction or message after that opens its own\napproval window. Nothing is signed without your approval.\n\n## Reference\n\n| Item | Detail | Source |\n|---|---|---|\n| Distribution | Release archive plus checksum, loaded unpacked in developer mode | `.github/workflows/release.yml` |\n| Build step | None on your side; the Argon2 WebAssembly is built per release | `wasm/build.md` |\n| Network | Fixed to Citrate Network, chain id 40204 | `manifest.json`, `js/provider.js` |\n| Account storage | AES-256-GCM under Argon2id, in `chrome.storage.local` | `js/crypto.js`, `js/background.js` |\n\nThe full account model, the provider methods, and the linked smart-account path are documented on\n[the Citrate Keyring extension](/apps/wallet-extension).\n\n## Design rationale\n\nThe extension is loaded unpacked from a checksum-verified archive rather than from a store so the exact\nfiles you run are the ones you verified, which matters for a key-holding surface that is still pre-audit.\nThe checksum step is the point of the tutorial, not a formality: it is what lets you trust the archive\nbefore the browser ever reads it.\n\n## Failure modes\n\n- The extension will not load: make sure you selected the folder that contains `manifest.json`, not the\n archive itself or a parent folder.\n- A page does not see the account: reload the page after loading the extension; the provider injects at\n document start, so a page opened earlier will not have it.\n- The checksum does not match: do not load the folder. Download the release files again.\n\nFor deeper troubleshooting, signing-path behaviour, and recovery, see\n[the Citrate Keyring extension](/apps/wallet-extension), [passkeys](/aa/passkeys), and\n[guardians](/aa/guardians). To drive the same account from code, see the [JavaScript SDK](/sdks/js).\n\n## Access and canon\n\nPublic. Nothing here is a secret. The password and the key you create are held on your device. The hosts\nthe extension reaches are public endpoints in its manifest. The software is prerelease and pre-audit; the\naccount created here has no recovery phrase, so keep the password safe.\n\n## Source and verification\n\n- Source repo: `citrate-wallet-extension`, audited against SHA `930594c`.\n- Files read: `README.md`, `.github/workflows/release.yml`, `manifest.json`, `js/background.js`,\n `js/crypto.js`, `js/provider.js`, `wasm/build.md`.\n- Status: Implemented (pre-audit), version 0.2.0. The download, verify, load-unpacked, and account-creation\n steps reflect the release workflow and the source as built. The account created here has no recovery\n phrase.\n"},"/apps/tutorials/run-the-desktop-wallet":{"slug":"/apps/tutorials/run-the-desktop-wallet","title":"Run the Citrate Keyring desktop app","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-native/README.md, Cargo.toml, rust-toolchain.toml, gui/citrate_native/ui/","syncedSha":"bc0e8ba","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Step 1, install the Rust toolchain","anchor":"step-1-install-the-rust-toolchain"},{"depth":3,"text":"Step 2, set up GitHub SSH access","anchor":"step-2-set-up-github-ssh-access"},{"depth":3,"text":"Step 3, install the platform build tools","anchor":"step-3-install-the-platform-build-tools"},{"depth":3,"text":"Step 4, get the repository","anchor":"step-4-get-the-repository"},{"depth":3,"text":"Step 5, build","anchor":"step-5-build"},{"depth":3,"text":"Step 6, run","anchor":"step-6-run"},{"depth":3,"text":"Step 7, create your account on first launch","anchor":"step-7-create-your-account-on-first-launch"},{"depth":3,"text":"Step 8, look around","anchor":"step-8-look-around"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This builds the Citrate Keyring desktop app from source with Cargo and opens it for the first time. It uses\nonly the build steps that exist in the `citrate-native` repository. The commands take a few minutes plus a\nfirst-time Rust compile, which can take a while.\n\n## What it is\n\nThe desktop app is a Cargo workspace; you build it and run the default member. The full screen map is on\n[the Citrate Keyring desktop app](/apps/native). This tutorial gets you from a clean checkout to an open\nwindow with an account.\n\n## How to use it\n\n### Step 1, install the Rust toolchain\n\nThe repository pins a stable toolchain in `rust-toolchain.toml`, so rustup picks it up automatically.\nInstall rustup if you do not have it:\n\n```bash\ncurl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh\n```\n\n### Step 2, set up GitHub SSH access\n\nThe build pulls chain crates over SSH from sibling repositories. Per the README, make sure your personal\nSSH key is on GitHub and you have read access to `citrate-chain`, `citrate-learning-center`, and\n`citrate-agent-runtime`; organization membership grants it. Check the key works:\n\n```bash\nssh -T git@github.com\n```\n\n### Step 3, install the platform build tools\n\nInstall a C and C++ toolchain and the system libraries Slint and `rocksdb` need: `build-essential`,\n`cmake`, and `clang` on Linux, or the Xcode command-line tools on macOS.\n\n### Step 4, get the repository\n\n```bash\ngit clone git@github.com:CitrateNetwork/citrate-native.git\ncd citrate-native\n```\n\n### Step 5, build\n\nThe default build target is `citrate-native`, the workspace default member in `Cargo.toml`:\n\n```bash\ncargo build --release\n```\n\nThe first build compiles the whole dependency tree, the chain crates, Slint, and `rocksdb`, so it takes\nseveral minutes. For a faster, unoptimized iteration build, drop `--release`. If you hit a type-mismatch\nerror at a chain-API boundary, it is the known double-fetch issue documented in the README, a chain crate\npulled through both an SSH host alias and plain `github.com`. It is tracked as a planned follow-up.\n\n### Step 6, run\n\n```bash\ncargo run --release -p citrate-native\n```\n\n`-p citrate-native` is explicit, but since it is the default member, plain `cargo run --release` launches\nthe same application.\n\n### Step 7, create your account on first launch\n\nThe onboarding flow opens (`gui/citrate_native/ui/onboarding/onboarding.slint`):\n\n1. Welcome, continue past the intro.\n2. Create a password of at least eight characters. This encrypts your account locally and never leaves\n your device.\n3. Provisioning, the app generates and shows your recovery phrase. Write it down and store it offline.\n4. Security confirmation, acknowledge that you backed up the phrase.\n\nIf you already have an account, use the import option to restore from a recovery phrase or a key instead.\n\n### Step 8, look around\n\nYou land in the app shell with the sidebar (`ui/shell/sidebar.slint`). Try `Wallet` for your balance and\ntransactions, and `DAG Explorer` to read the BlockDAG and open a transaction. When you reopen the app it\nwill be locked; unlock it with the password from step 7.\n\n## Reference\n\n| Step | Command or file | Source |\n|---|---|---|\n| Toolchain | `channel = \"stable\"` | `rust-toolchain.toml` |\n| Build | `cargo build --release` | `README.md`, `Cargo.toml` |\n| Run | `cargo run --release -p citrate-native` | `README.md`, `Cargo.toml` default member |\n| Onboarding | Welcome, password, provisioning, confirmation | `ui/onboarding/onboarding.slint` |\n| Shell | Sidebar groups and screens | `ui/shell/sidebar.slint` |\n\nThe DAG view here is a local reader; the shared public explorer is [CitrateScan](/apps/explorer). The\naccount abstraction the app shares with the browser extension is covered under [passkeys](/aa/passkeys)\nand [guardians](/aa/guardians), and you can drive the same account from code with the\n[JavaScript SDK](/sdks/js).\n\n## Design rationale\n\nThe app is built from source rather than shipped as a signed binary at this stage because it is pre-1.0 and\nits chain dependencies move with the network. Building it yourself means the account, the node reader, and\nthe marketplaces all run from the same checked-out tree, with nothing fetched at runtime that you did not\ncompile.\n\n## Failure modes\n\n- A native window does not open, or the build fails at link time: confirm the C and C++ toolchain and the\n Slint and `rocksdb` system libraries from step 3 are installed.\n- A type mismatch at a chain-API boundary: this is the README's double-fetch issue from step 5, a chain\n crate pulled twice through different URL conventions. It compiles today and is tracked as a planned\n follow-up.\n- The app opens locked on a later run: that is expected. Unlock with your password. There is no\n server-side recovery, the phrase from onboarding is the only backup.\n\n## Access and canon\n\nPublic. Nothing here is a secret. Your password and recovery phrase are created on your machine; never\npaste your recovery phrase into a website, a chat, or a file you do not control. Building requires your own\nGitHub SSH access to the sibling repositories; no shared credential is needed or embedded here.\n\n## Source and verification\n\n- Source repo: `citrate-native`, audited against SHA `bc0e8ba`.\n- Files read: `README.md` Quick start (the build and run commands), `Cargo.toml` (default member),\n `rust-toolchain.toml` (toolchain), `gui/citrate_native/ui/onboarding/onboarding.slint` (onboarding steps),\n `gui/citrate_native/ui/shell/sidebar.slint` (the shell).\n- Status: Implemented, version 0.4.0, pre-1.0. The build and run commands and the onboarding steps reflect\n the repository as built. The double-fetch caveat is an open, README-documented issue.\n"},"/apps/wallet-extension":{"slug":"/apps/wallet-extension","title":"The Citrate Keyring extension","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-wallet-extension/manifest.json, js/, popup/","syncedSha":"930594c","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The Citrate Keyring extension is a Manifest V3 browser extension that holds a Citrate Keyring account,\nconnects pages to Citrate Network, and signs every transaction and message behind an explicit approval.\nIt is for anyone who wants to use a Citrate page from Chrome, Edge, or Brave, and for developers wiring a\nstandard provider into a page.\n\n## What it is\n\nThe extension is the account surface you reach from a browser tab. It generates and holds a key locally,\nexposes a standard `window.ethereum` provider so a page can talk to it, and gates every signature behind a\nconfirmation window. The chain is fixed to Citrate Network, chain id `40204`, so there is no network to\nchoose and no chain to switch.\n\nThe account model has two layers. The extension creates a local account, an externally owned account whose\nkey is generated in the background service worker and stored encrypted at rest. Separately, you can link\nthat account to your Citrate Keyring identity, the same identity you can hold with a passkey at\n`auth.citrate.ai`, which gives you a smart account whose operations can be sponsored. The passkey root and\nthe recovery path live with the identity service, covered under [passkeys](/aa/passkeys) and\n[guardians](/aa/guardians); the extension is the device-held signer that links to it.\n\nThree properties hold throughout. Keys are generated and decrypted only inside the background worker, never\nin the page. Every signature and every transaction needs an explicit, per-action approval, there is no\nsilent signing. The set of network destinations the extension can reach is a fixed allow-list, mirrored\ninto the manifest content security policy.\n\n## How to use it\n\n1. Install the extension and create a local account, set out step by step in\n [install the Citrate Keyring extension](/apps/tutorials/install-the-wallet-extension).\n2. Open a Citrate page. It discovers the provider through EIP-6963 and through `window.ethereum`, and calls\n `eth_requestAccounts`. A per-origin connection window opens; approve it once for that site.\n3. When the page asks to send a transaction or sign a message, a confirmation window opens showing the\n from address, the to address, the amount in SALT, the gas, and any calldata. Approve or reject. If you\n do neither, it times out and is rejected.\n4. To use a sponsored smart account, open the popup and run `Link this wallet`. This signs you in to your\n Citrate Keyring identity, predicts your smart account address, and, if the account is not yet deployed,\n installs this device's key as its root signer. After linking, a page can call\n `wallet_sendUserOperation` and the operation is sponsored by the Citrate paymaster.\n\n## Reference\n\nThe surfaces below are read from the extension source at the SHA noted in the last section.\n\n| Surface | What it does | Source |\n|---|---|---|\n| Background service worker | Holds keys, signs, gates approvals, forwards RPC | `js/background.js` |\n| Page provider, MAIN world | Defines `window.ethereum` and `window.citrate`, announces over EIP-6963 | `js/provider.js` |\n| Content relay, ISOLATED world | Validates and relays page requests across the trust boundary | `js/content.js` |\n| Account abstraction helpers | Smart account address prediction, EntryPoint v0.7 UserOperation encoding | `js/aa.js` |\n| Popup | Account view, send and receive, settings, `Link this wallet` | `popup/index.html`, `popup/js/app.js` |\n| Connect window | Per-origin connection approval | `popup/connect.html`, `popup/js/connect.js` |\n| Confirm window | Per-action approval, fails closed on timeout | `popup/confirm-tx.html`, `popup/js/confirm-tx.js` |\n\nManifest V3 permissions, from `manifest.json`:\n\n| Permission | Why it is requested |\n|---|---|\n| `storage` | Store the encrypted account and settings in `chrome.storage.local` |\n| `activeTab` | Interact with the page the user is on |\n| `identity` | Run the Citrate Keyring identity sign-in through `chrome.identity.launchWebAuthFlow` |\n| `alarms` | Drive the service-worker lock and session timers |\n\nContent scripts inject only on `https://*/*`, `http://localhost/*`, and `http://127.0.0.1/*`, so the\nprovider never appears on `chrome://` pages or `file://` URLs. The content security policy `connect-src`\nlimits the worker's network reach to the Citrate RPC, faucet, auth, and bundler hosts plus localhost. The\nworker enforces a stricter allow-list in `RPC_URL_ALLOWLIST` (`js/background.js`), and a CI parity check\nholds that allow-list as a subset of the manifest policy.\n\nProvider methods, handled in `js/background.js`:\n\n| Method | Behaviour |\n|---|---|\n| `eth_chainId`, `net_version` | Return `0x9d0c`, that is 40204 |\n| `eth_requestAccounts`, `eth_accounts` | Return the account after a per-origin approval |\n| `eth_sendTransaction` | Opens a confirmation window, then signs and submits |\n| `personal_sign` | Opens a confirmation window, then signs an EIP-191 message |\n| `wallet_sendUserOperation` | Sends a sponsored operation from the linked smart account, requires linking first |\n| `wallet_switchEthereumChain`, `wallet_addEthereumChain` | Handled, but the chain is fixed to 40204 |\n| `eth_call`, `eth_getBalance`, `eth_blockNumber`, and other reads | Forwarded to the allow-listed node for an approved origin |\n| `eth_sign` | Disabled; it is an arbitrary-data signing primitive |\n| `eth_signTypedData`, `eth_signTypedData_v3`, `eth_signTypedData_v4` | Not yet supported (Specified), use `personal_sign` |\n\nAccount creation derives a 32-byte key, computes the address as the keccak256 of the public key in EIP-55\nchecksum form, and stores it encrypted with AES-256-GCM under an Argon2id key derivation (version 2,\nm=64 MiB, t=3, p=1). The Argon2 step runs in a small WebAssembly module from the `citrate-wallet-sdk`\ncrate; an older PBKDF2 path remains read-only so existing accounts still unlock (`js/crypto.js`,\n`wasm/build.md`). The decrypted key is zeroed immediately after each signature.\n\n## Design rationale\n\nThe extension is built so the page can never reach a key. The provider that a page sees runs in the MAIN\nworld and does no cryptography and no extension messaging; it posts narrow, origin-scoped messages to the\nISOLATED-world relay, which forwards them to the worker that holds the keys. This is why `window.ethereum`\nis visible to a page while the key never is. The same caution explains the fixed network and the fixed\nnetwork allow-list: an account that can only ever talk to Citrate Network and a known set of hosts cannot\nbe quietly pointed at a destination that would see every signed transaction. The cost is that the\nextension is single-purpose, it is not a general account for other networks, and we think that is the\nright trade for the account a person uses on Citrate.\n\n## Failure modes\n\nThis is a key-holding surface, so the relevant failures are the ones where it must fail closed.\n\n- A signature or transaction with no approval is rejected. If the confirmation window times out, the\n request is rejected, not held. A CI tripwire fails the build if a signing path stops gating on the\n approval check.\n- `eth_sign` is refused outright, because it signs attacker-chosen bytes. Legitimate message signing goes\n through `personal_sign`, which is gated by its own confirmation.\n- An unapproved origin cannot forward arbitrary methods to the node. Read methods are proxied only for an\n origin you have approved, which closes a confused-deputy path to a private node.\n- A request to point the account at an RPC outside the allow-list is rejected with the allowed set named.\n- The service worker locks the account on suspend, and the in-memory session ticket is lost when the\n worker is killed, so an idle browser does not leave the account unlocked.\n\nThere is no seed-phrase recovery for the local account. If you forget the password you lose that account.\nThe smart-account path is a different recovery story, handled by your Citrate Keyring identity and its\nguardians, see [guardians](/aa/guardians).\n\n## Access and canon\n\nPublic. This is the account a developer or a person uses to reach Citrate Network from a browser, and it is\nopen by design. No keys, credentials, or private endpoints appear here. The only sensitive values, your\npassword and your key, are created and held on your device and never leave it. The hosts in the manifest\ncontent security policy are public endpoints, not secrets.\n\nThe smart-account path is pre-audit. The repository is classified Tier 1, full external audit before the\nfirst stable release, in `AUDIT_TIER.md`. Use small testnet values while the audit is pending.\n\n## Source and verification\n\n- Source repo: `citrate-wallet-extension`, audited against SHA `930594c`.\n- Files read: `manifest.json`, `js/background.js`, `js/provider.js`, `js/content.js`, `js/crypto.js`,\n `js/aa.js`, `popup/`, `.github/workflows/release.yml`, `.github/workflows/ci.yml`, `wasm/build.md`,\n `AUDIT_TIER.md`, `README.md`.\n- Status: Implemented (pre-audit), version 0.2.0. The local-account creation, the EIP-1193 provider, the\n per-action approval gates, the disabled `eth_sign`, and the RPC allow-list are Implemented. The linked\n smart-account path and `wallet_sendUserOperation` are Implemented but pre-audit, small-value only. EIP-712\n typed-data signing is Specified, not yet supported. The Argon2 WebAssembly binary is built per release\n with `wasm-pack` and is not committed to the repository.\n"},"/chain/bridge":{"slug":"/chain/bridge","title":"Cross-chain bridge","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/core/bridge/ (internals gated)","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This is a brief, sober overview of the Citrate cross-chain bridge. The bridge is pre-alpha and its\nimplementation internals are gated; this page states what it is and where it stands, and nothing more.\n\n## What it is\n\nThe bridge is a cross-chain relay between Ethereum and the Citrate Network. It moves value in by watching\nfor events on Ethereum, on the Sepolia testnet today, and crediting the corresponding amount on Citrate once\nthose events are independently confirmed.\n\nConfirmation does not rest on a single observer. An M-of-N oracle set independently verifies each Ethereum\nevent and signs an attestation over it, using ed25519 signatures with a five-minute freshness window so that\nstale attestations are not honoured. The relay acts only once a quorum of attestations has been collected,\nand it rejects duplicate or inconsistent attestations rather than acting on a contested view. Deposit\npricing uses a bonding curve, with per-transaction limits and a hard cap on total deposits.\n\nThe bridge is pre-alpha. It runs only in a development configuration today, and the audited mainnet bridge\nceremony has not been scheduled. Do not treat it as production-ready.\n\n## Access and canon\n\nThis page is public, and intentionally brief. The bridge's implementation internals, its trust model,\noracle and relay design, signature and freshness handling, and the bonding-curve and limit parameters, are\npublic in the `citrate-chain` repository under `core/bridge/` and its `SECURITY.md`; this page summarizes\nand links to them rather than reproducing them. No keys or endpoints appear on this page.\n\n## Source and verification\n\n- Source: `citrate-chain/core/bridge/`, internals gated; this public page does not transclude or summarise\n them.\n- Audited against SHA: `9d5959e`.\n- Status: Specified, pre-alpha. Runs in a development configuration only; no external audit has been\n completed and the mainnet ceremony is not yet scheduled.\n"},"/chain/cli":{"slug":"/chain/cli","title":"The citrate command-line tool","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/cli/src/{main.rs,config.rs,commands/}","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"account (`cli/src/commands/account.rs`)","anchor":"account-clisrccommandsaccountrs"},{"depth":3,"text":"model (`cli/src/commands/model.rs`)","anchor":"model-clisrccommandsmodelrs"},{"depth":3,"text":"contract (`cli/src/commands/contract.rs`)","anchor":"contract-clisrccommandscontractrs"},{"depth":3,"text":"network (`cli/src/commands/network.rs`)","anchor":"network-clisrccommandsnetworkrs"},{"depth":3,"text":"governance (`cli/src/commands/governance.rs`)","anchor":"governance-clisrccommandsgovernancers"},{"depth":3,"text":"advanced (`cli/src/commands/advanced.rs`)","anchor":"advanced-clisrccommandsadvancedrs"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The `citrate` command-line tool that ships with `citrate-chain`. It manages accounts, deploys and calls\nmodels and contracts, queries the network, and submits governance changes, all against a Citrate Node over\nJSON-RPC. This page is for developers and node operators. If you want to read the chain directly instead,\nsee the [JSON-RPC reference](/chain/rpc).\n\n## What it is\n\nOne binary, `citrate`, with a handful of subcommands. Every subcommand talks to a Citrate Node through the\nsame JSON-RPC interface documented under [chain RPC](/chain/rpc), so the tool is a convenience over the\nnetwork, not a separate authority. Defaults come from `cli/src/config.rs`: the RPC endpoint is\n`http://localhost:8545`, the chain id is 40204, and the keystore lives under `~/.citrate/keystore`. A global\n`--rpc ` flag (or `-r`) overrides the endpoint on any command, and `--config ` points at a\ndifferent config file.\n\nThe subcommands, each verified against `cli/src/main.rs`:\n\n| Subcommand | What it does |\n|---|---|\n| `init` | Writes a starter config file. |\n| `account` | Creates, lists, imports, and exports Citrate Keyring accounts; reads balances. |\n| `model` | Deploys and inspects on-chain models, and runs inference against them. |\n| `contract` | Deploys contracts, calls and reads methods, and submits source for verification. |\n| `network` | Read-only chain and network queries: status, blocks, transactions, gas price, peers. |\n| `governance` | Queues and executes governance parameter changes through the governance precompile. |\n| `advanced` | Network monitoring, benchmarking, stress tests, and DAG and mempool introspection. |\n| `wizard` | Interactive, terminal-guided setup and deployment flows. |\n| `devx` | Developer tools: prints the federation contract table and predicts an embedded Keyring address for a user. |\n\nThe testnet faucet is a separate service, not a subcommand. `citrate-faucet` is an HTTP server, configured\nby environment variables, that drips test SALT to an address; you run it or call its endpoint, you do not\nreach it through `citrate`.\n\n## How to use it\n\nBuild the tool from the workspace root, then confirm it points where you expect:\n\n```bash\ncargo build --release -p citrate-cli # produces the `citrate` binary\ncitrate init # writes a starter config\ncitrate network status # net_version, eth_blockNumber, eth_syncing\n```\n\nA typical first session creates an account, checks its balance, and reads a block:\n\n```bash\ncitrate account create\ncitrate account balance 0x00000000000000000000000000000000000000a1\ncitrate network block latest\n```\n\nTo point at a node other than the local default, pass `--rpc` on any command:\n\n```bash\ncitrate --rpc https://rpc.example.invalid network status\n```\n\nTo deploy a contract end to end, follow\n[deploy a contract with the CLI](/chain/tutorials/deploy-a-contract-with-the-cli), which walks through\n`contract deploy`, `contract call`, and `contract read` against a live node.\n\n## Reference\n\nRepresentative subcommands, verified against the files in `cli/src/commands/`. A flag not listed here does\nnot exist in the binary at this SHA.\n\n### account (`cli/src/commands/account.rs`)\n\n| Subcommand | Purpose |\n|---|---|\n| `account create` | Generates a new account, prompting for a password. |\n| `account list` | Lists keystore files. |\n| `account balance
` | Reads a balance via `eth_getBalance`. |\n| `account import` | Imports a key from `--key-stdin`, `--key-file`, or the loud `--insecure-key-from-arg`. |\n| `account export
` | Exports a key to a `0600` file with `--out`, or to stdout only with `--confirm-stdout`. |\n\n### model (`cli/src/commands/model.rs`)\n\n| Subcommand | Purpose |\n|---|---|\n| `model deploy ` | Registers a model on-chain (`citrate_deployModel`). |\n| `model inference` | Runs inference against a model id (`citrate_runInference`). |\n| `model list` | Enumerates registered models (`citrate_listModels`). |\n| `model info ` | Reads model detail (`citrate_getModel`). |\n\n### contract (`cli/src/commands/contract.rs`)\n\n| Subcommand | Purpose |\n|---|---|\n| `contract deploy ` | Deploys a contract (`eth_sendTransaction`). |\n| `contract call
` | Sends a state-changing call. |\n| `contract read
` | Reads a method with no transaction (`eth_call`). |\n| `contract verify
` | Submits source for runtime-bytecode verification. |\n\nThe built-in ABI encoder supports `address`, `bool`, `bytes32`, and `uint{8..256}` only; dynamic `string`\nand `bytes` are not yet encoded.\n\n### network (`cli/src/commands/network.rs`)\n\n| Subcommand | Underlying call |\n|---|---|\n| `network status` | `net_version`, `eth_blockNumber`, `eth_syncing` |\n| `network block [BLOCK]` | `eth_getBlockByNumber` |\n| `network transaction ` | `eth_getTransactionByHash` and receipt |\n| `network gas-price` | `eth_gasPrice` |\n| `network dag-stats` | `citrate_getDagStats` |\n\n### governance (`cli/src/commands/governance.rs`)\n\nEncodes ABI calls to the governance precompile at `0x0000000000000000000000000000000000001003`:\n`set-admin`, `queue-param`, `execute-param`, and the read-only `get-param`.\n\n### advanced (`cli/src/commands/advanced.rs`)\n\nMonitoring and introspection: `advanced monitor` polls height, peers, and gas price (with `--dag` and\n`--mempool` it adds DAG and mempool detail); `advanced benchmark` and `advanced stress-test` drive load;\n`advanced topology` maps peers; `advanced tx-debug ` traces a transaction; `advanced model-stats`\nreads model usage.\n\n## Failure modes\n\nThe tool fails closed at the points that touch keys and the network:\n\n- Key input never defaults to the argument vector. The old `--key` flag was removed; `account import --key`\n no longer parses. Use `--key-stdin`, `--key-file`, or the explicit `--insecure-key-from-arg`, which warns.\n- `account export` refuses to print a secret to the terminal unless you pass `--out ` or\n `--confirm-stdout`; the file it writes is mode `0600` on Unix.\n- A call against an unreachable node surfaces a connection error rather than a partial result. Confirm the\n endpoint with `citrate network status` or override it with `--rpc`.\n- The underlying RPC errors are standard JSON-RPC: `-32601` method not found, `-32602` invalid params,\n `-32600` invalid request.\n\n## Access and canon\n\nPublic. This is the open command surface a developer needs. The tool generates or prompts for keys; nothing\nsensitive is embedded, and no credentials appear on this page. Deeper interpretation of the `advanced`\nintrospection output is gated to the academic tier, since it exposes consensus and mempool internals; the\nflag list above is public, the internals analysis is not.\n\n## Source and verification\n\nVerified against `citrate-chain` at `9d5959e`. The subcommand set is read from `cli/src/main.rs`\n(`enum Commands`); defaults from `cli/src/config.rs` (RPC `http://localhost:8545`, chain id 40204); each\nsubcommand group from `cli/src/commands/{account,model,contract,network,governance,advanced,wizard}.rs`. The\nfaucet is a separate HTTP service in `faucet/`, not a `citrate` subcommand. Status: Implemented (testnet,\npre-audit). The ABI encoder is intentionally a static-types-only subset.\n"},"/chain/consensus":{"slug":"/chain/consensus","title":"Citrate Consensus, GhostDAG","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/core/consensus/src/types.rs, citrate-chain/core/consensus/src/ghostdag.rs, citrate-chain/core/consensus/src/ecvrf.rs, citrate-chain/core/consensus/src/finality.rs, citrate-chain/core/consensus/src/checkpoint.rs","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Current status","anchor":"current-status"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"GhostDAG engine, `src/ghostdag.rs`","anchor":"ghostdag-engine-srcghostdagrs"},{"depth":3,"text":"Parameters, `src/types.rs`","anchor":"parameters-srctypesrs"},{"depth":3,"text":"Proposer election, `src/ecvrf.rs`, `src/vrf.rs`","anchor":"proposer-election-srcecvrfrs-srcvrfrs"},{"depth":3,"text":"Depth-based finality, `src/finality.rs`","anchor":"depth-based-finality-srcfinalityrs"},{"depth":3,"text":"Committee checkpoint finality, `src/checkpoint.rs` (specified, not running)","anchor":"committee-checkpoint-finality-srccheckpointrs-specified-not-running"},{"depth":3,"text":"Example","anchor":"example"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Consensus is how Citrate Network turns many blocks into one agreed history. The ledger is a BlockDAG, so a block may name several parents, and the GhostDAG protocol reads that graph and produces a single order that every honest node computes the same way. This page is for developers and operators who want the mental model first and the audited surface after.\n\n## What it is\n\nA single-parent chain is a line. Citrate Network is a graph: each block names a selected parent and, optionally, a few merge parents, so the ledger grows like a grafted orchard rather than a single stem. GhostDAG sorts that growth into a total order so the ledger reads as one history.\n\nThe protocol divides every block into two sets. The blue set holds the blocks that agree with the honest majority, judged by a k-cluster rule that tolerates a bounded number of blocks seen in parallel. The red set holds the rest. Walking from genesis to the selected tip and interleaving each block's merge set gives the canonical order. The ordering key is blue score, the cumulative count of a block's blue ancestors. The tip with the highest blue score is the head the network builds on, with deterministic tie-breaking when two tips draw level.\n\nOne property matters above all the rest: blue score is recomputed, never trusted from the block header. A block that arrives claiming an inflated `blue_score` is checked against the feasible range derived from its own ancestry, and a value outside that range is rejected at admission. This is enforced in `ghostdag.rs` and covered by regression tests.\n\nThe default parameters are network constants.\n\n| Parameter | Default | Meaning |\n|---|---|---|\n| `k` | 18 | k-cluster width, how many parallel blocks are tolerated as blue |\n| `max_parents` | 10 | the most parents a block may name |\n| `max_blue_score_diff` | 1000 | the blue-score gap a reorg may span |\n| `pruning_window` | 100000 | how far back the DAG retains full detail |\n| `finality_depth` | 100 | the depth parameter for depth-based finality tracking (see [current status](#current-status)) |\n\nThe testnet node configuration (`node/config/testnet.toml`) sets a one-second target, but the measured block interval on the active testnet is about two seconds; see [chain parameters and genesis](/chain/genesis) for the cadence of each. For where these blocks come from, see [the sequencer](/chain/sequencer); for the broader picture, see [the primer](/start/primer).\n\n## How to use it\n\nYou do not run GhostDAG directly; you read its output. To follow the live order against a running node:\n\n1. Ask the node for its current tips, the heads it is building on.\n2. Read a block and note its blue score; the higher the blue score, the closer to the selected tip.\n3. Walk the selected-parent chain back from the tip to see the order the network agreed on.\n4. Check a block's depth behind the selected tip. The deeper it is, the more work a competing branch would need to displace it. There is no protocol finality point today: see [current status](#current-status).\n\nThe runnable steps, with the exact JSON-RPC calls, are in [read the DAG](/chain/tutorials/read-the-dag). To construct the engine in Rust, see the example at the end of this page.\n\n## Current status\n\nConfirmation on the testnet is probabilistic: a block gains weight as later blocks build on it. Checkpoint finality is specified, not running. The node constructs `CheckpointManager`, but no production code path calls `propose()` yet. `FinalityTracker` and `ChainSelector::with_finality` are exercised by tests only. The RPC does not serve the `finalized` block tag.\n\nThe public testnet runs a single block producer operated by Citrate. Stake-gated proposer eligibility is staged: it is off by default and turns on when a validator registry is configured (`CITRATE_VALIDATOR_REGISTRY`). The one-hundred-member committee with a quorum of sixty-seven, described below, is the target design, not the current state.\n\nIf you credit deposits or settle payments against Citrate testnet blocks, choose your own confirmation depth and treat it as a risk decision, not a protocol guarantee. The machine-readable record of these facts is `verification/claims.json` in citrate-chain (`deterministic_checkpoint_finality`, `consensus_ghostdag`).\n\n## Reference\n\nThe consensus stack lives in `core/consensus/`. The audited surface follows, each item with its code path.\n\n### GhostDAG engine, `src/ghostdag.rs`\n\n- `GhostDag::new(params, dag_store)`, construct the engine over a DAG store.\n- `GhostDag::calculate_blue_set(block)`, compute a block's blue set under the k-cluster rule.\n- `GhostDag::calculate_blue_score(block)`, recompute blue score from the blue set, not from the header.\n- `GhostDag::add_block(block)`, admit a block, update relations and tips.\n- `GhostDag::select_tip()` and `GhostDag::get_tips()`, the current best tip and all tips.\n\nA header claiming a blue score outside its feasible range is rejected here (`GhostDagError`, `src/ghostdag.rs:34`).\n\n### Parameters, `src/types.rs`\n\n`GhostDagParams::default()` sets `k = 18`, `max_parents = 10`, `max_blue_score_diff = 1000`, `pruning_window = 100000`, and `finality_depth = 100` (`src/types.rs:175`).\n\n### Proposer election, `src/ecvrf.rs`, `src/vrf.rs`\n\nProposer eligibility uses an elliptic-curve verifiable random function over NIST P-256, specifically ECVRF-P256-SHA256-TAI per RFC 9381 (`src/ecvrf.rs:3`). Each candidate produces an output bound to their secret key and a public input, so the network can check who was entitled to propose without anyone being able to grind the result.\n\n- `ecvrf::prove(secret, alpha)`, RFC 9381 section 5.1.\n- `ecvrf::verify(...)`, RFC 9381 section 5.3.\n- `VrfProposerSelector` (`src/vrf.rs`) can apply stake-weighted eligibility on top of the VRF output. The node leaves stake gating off by default; without a configured validator registry the fallback selector checks the VRF proof and key binding, not stake.\n\n### Depth-based finality, `src/finality.rs`\n\n`FinalityTracker` marks a block final once it sits under enough confirmations, `confirmation_depth = 100` by default (`FinalityConfig`, `src/finality.rs:42`). `FinalityStatus` is `Finalized`, `PendingFinalization`, or `Unfinalized`. When a `ChainSelector` is built `with_finality`, it refuses a reorganization that would rewrite a block the tracker marked final (`src/chain_selection.rs:25`). The node does not build it that way today, so this protection is specified, not running.\n\n### Committee checkpoint finality, `src/checkpoint.rs` (specified, not running)\n\nThe checkpoint layer is designed to add deterministic finality on top of depth tracking. It is implemented and tested as a library, but no production code path proposes checkpoints yet. `CheckpointManager` coordinates a deterministically selected committee that signs over `(height || block_hash)` with ed25519, and the signatures are aggregated. A checkpoint finalizes once a quorum signs (`CheckpointState::has_quorum`). The chain id is bound into the signed message so a vote on one chain cannot replay onto another (`src/checkpoint.rs:90`).\n\n`CheckpointConfig::default()` sets `interval = 50` blocks, `committee_size = 100`, and `quorum_threshold = 67`, which is two-thirds of one hundred plus one (`src/checkpoint.rs:98`).\n\nIn wall-clock terms under current testnet parameters (measured block time about 2 s), a block is included in about 2 s. Once checkpoints run, one would be due every 50 blocks, about 100 s at the measured block time. Until then there is no finality latency to quote.\n\n### Example\n\nConstruct the engine and read the order in Rust.\n\n```rust\nuse citrate_consensus::*;\nuse std::sync::Arc;\n\nlet dag_store = Arc::new(DagStore::new());\nlet params = GhostDagParams::default(); // k=18, max_parents=10, finality_depth=100\nlet ghostdag = GhostDag::new(params, dag_store.clone());\n\ndag_store.store_block(genesis_block).await?;\nghostdag.add_block(&child_block).await?;\n\n// Deterministic total order from genesis to a tip:\nlet ordering = TotalOrdering::new(dag_store.clone(), Arc::new(ghostdag));\nlet order = ordering.get_total_order(tip_hash).await?;\n\n// Depth-based finality (default depth 100):\nlet tracker = FinalityTracker::with_defaults(dag_store.clone());\nlet finalized = tracker.update_finality(&tip_hash, tip_height).await?;\n```\n\n## Design rationale\n\nA graph orders work better than a line under load. When two proposers produce blocks at nearly the same moment, a single-parent chain has to discard one; a BlockDAG keeps both as parents and lets GhostDAG decide their order later. That is why blocks may name up to ten parents and why the target time can sit near one second without the orphan waste a line would suffer.\n\nTwo choices guard the ledger. Blue score is recomputed rather than trusted, so a block cannot lie its way to the front by claiming a large score. And finality is designed in layers: depth tracking over one hundred blocks for every block, and committee checkpoints designed to add a signed, deterministic guarantee every fifty blocks. The cost is a checkpoint committee that must be selected and must sign; the benefit, once it runs, is that a settled block is settled by both depth and signature. Today checkpoint finality is specified, not running (see [current status](#current-status)).\n\n## Failure modes\n\n- A block arriving with a forged `blue_score` is rejected at admission; ordering ignores the header value and uses the recomputed one.\n- As designed, a reorganization that would rewrite a finalized block is refused by a finality-aware `ChainSelector`, which returns a finality error rather than reverting history. That path is specified, not running, on the testnet.\n- In the checkpoint design, a checkpoint cannot finalize without a quorum of sixty-seven of one hundred committee signatures, so a minority of the committee cannot force a checkpoint. Chain-id binding stops a valid vote from one network being replayed onto another. Neither applies on the live testnet until checkpoints run.\n\n## Access and canon\n\nPublic. The GhostDAG model, the parameters, and the audited surface are protocol a developer needs to reason about ordering and finality, and GhostDAG itself is published research. No keys, validator secrets, or private endpoints appear here. ECVRF `prove` takes a secret key as a parameter; no secret value is documented.\n\n## Source and verification\n\n- Source files: `core/consensus/src/types.rs`, `src/ghostdag.rs`, `src/ecvrf.rs`, `src/vrf.rs`, `src/finality.rs`, `src/chain_selection.rs`, `src/checkpoint.rs`.\n- Audited against SHA `9d5959e`.\n- Status: GhostDAG ordering and VRF checks are implemented; checkpoint finality is specified, not running. Pre external audit. The crate is internally tested and TLA+-checked in several areas; it has not completed a third-party audit, so read \"tested\" as tested, not certified.\n"},"/chain/economics":{"slug":"/chain/economics","title":"Network economics","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/core/economics/","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This is the unit of account on the Citrate Network and the rules that move it. SALT settles the work the\nnetwork performs, fees, block rewards, and staking; it is not a product to hold, and this page does not\ntreat it as one. It is for builders and operators who need to reason about what the network charges, what\nit pays, and how supply behaves over the long view.\n\n## What it is\n\nSALT is the credit the network counts in. When a transaction pays a fee, when a node earns a reward for\nsealing a block, or when stake is placed and returned, the amount is denominated in SALT. The supply is\nfixed at genesis: one trillion SALT, never more. The smallest unit is wei-style, so one SALT divides into\n10^18 base units, the same granularity a developer already expects from an account balance.\n\nThe economics live in one crate, `core/economics/`. It holds the token itself (`token.rs`), the per-block\nreward schedule (`enhanced_rewards.rs`), and the constants that bound the whole system (`lib.rs`). The\nreward schedule is the part worth understanding early, because it is what gives a node a reason to keep the\nnetwork running, and it shrinks on a fixed cadence so that early seasons are more generous than late ones.\n\nA block reward is built from a base reward plus four bonus pools, each expressed as a percentage of that\nbase reward. The four pools recognise four kinds of contribution: validator performance, AI contribution,\nnetwork health, and long-term staking. A node that does more of the work the network values earns a larger\nshare of the pools. The base reward halves every 2,100,000 blocks, about 24 days at the one-second\ntestnet cadence, so issuance tapers toward zero over the network's life rather than running flat forever.\n\n## How to use it\n\nYou rarely set these values yourself; you read them, so you can price work and project earnings.\n\n1. **Price a transaction.** Estimate the fee the way you would on any account-based ledger: gas used times\n the prevailing price. SALT carries 18 decimals, so amounts and balances behave like the smallest units\n you are used to.\n2. **Read the live economic state.** Call `citrate_getEconomicState` for current network metrics and\n `citrate_getToken` for token fundamentals over JSON-RPC. Both are documented in the\n [chain RPC reference](/chain/rpc).\n3. **Reason about rewards.** If you operate a node, the reward you can expect for a sealed block is the base\n reward plus your earned share of the four bonus pools, adjusted for where the chain sits in its halving\n schedule. See [run a node](/operators/run-a-node) for the operator path.\n\n## Reference\n\nToken constants, verified in `core/economics/src/lib.rs` and `core/economics/src/token.rs`:\n\n| Constant | Value | Source |\n|---|---|---|\n| Symbol (`TOKEN_SYMBOL`) | `SALT` | `lib.rs` |\n| Name (`TOKEN_NAME`) | `Citrate` | `lib.rs` |\n| Total supply (`TOTAL_SUPPLY`) | 1,000,000,000,000 (one trillion) | `lib.rs` |\n| Decimals (`DECIMALS`) | 18 | `token.rs` |\n\nTotal supply in base units is `1_000_000_000_000 × 10^18`. The token tracks balances, total minted, and total\nburned; circulating supply is minted minus burned, and minting is capped at the one trillion ceiling.\n\nBlock reward schedule, verified in `core/economics/src/enhanced_rewards.rs`:\n\n| Element | Value | Source |\n|---|---|---|\n| Base reward | a configurable base reward | `enhanced_rewards.rs` (`base_block_reward`) |\n| Validator performance pool | percentage of the base reward | `enhanced_rewards.rs` (`performance_bonus_pool`) |\n| AI contribution pool | percentage of the base reward | `enhanced_rewards.rs` (`ai_contribution_pool`) |\n| Network health pool | percentage of the base reward | `enhanced_rewards.rs` (`network_health_pool`) |\n| Long-term staking pool | percentage of the base reward | `enhanced_rewards.rs` (`staking_bonus_pool`) |\n| Halving interval | 2,100,000 blocks (~24 days at the 1 s testnet cadence) | `enhanced_rewards.rs` (`halving_interval`) |\n\nThe base reward and the four pool percentages are defaults in the source; we describe the base as a\nconfigurable base reward rather than asserting a fixed number, since governance can move it. What does not\nmove is the halving cadence and the fixed supply.\n\n```rust\nuse citrate_economics::*;\n\n// Token fundamentals are constants:\nassert_eq!(TOKEN_SYMBOL, \"SALT\");\nassert_eq!(TOTAL_SUPPLY, 1_000_000_000_000); // 18 decimals; base units = value × 10^18\n```\n\n## Design rationale\n\nA fixed supply with a halving base reward keeps the accounting honest: the network can settle the work it\nperforms without an open-ended issuance that quietly dilutes everyone who came before. Splitting the reward\ninto four pools, rather than paying a flat amount per block, lets the network pay for the behaviours it\nactually depends on, uptime, useful compute, a healthy peer set, and committed stake, instead of paying\nthe same for a block whether or not the node contributed anything beyond sealing it. The trade is more\nmoving parts to reason about; the benefit is that incentives point at the work rather than at the clock.\n\n## Failure modes\n\nThe supply cap is enforced at mint: an attempt to mint past one trillion SALT is rejected, so no path\nthrough the reward schedule can inflate beyond the ceiling. Burned credits are tracked separately, so\ncirculating supply stays an honest minted-minus-burned figure rather than drifting. Because the base reward\nand pool percentages are governance-configurable, the load-bearing invariant is the supply cap and the\nhalving cadence, not any single reward number; treat a published base-reward figure as a default, not a\nguarantee.\n\n## Access and canon\n\nPublic. SALT settles the work the network performs; it is not an investment instrument, and Almanac does not\ndescribe it as one. The token fundamentals, the reward structure, and the halving cadence are exactly what a\nbuilder or operator needs to reason about the economy. No keys, balances, or private allocations appear\nhere.\n\n## Source and verification\n\n- Source: `citrate-chain/core/economics/`, constants in `src/lib.rs` and `src/token.rs`, reward schedule in\n `src/enhanced_rewards.rs`.\n- Live state over JSON-RPC: `citrate_getEconomicState` and `citrate_getToken`\n (`core/api/src/economics_rpc.rs`); see [chain RPC](/chain/rpc).\n- Audited against SHA: `9d5959e`.\n- Status: Implemented (testnet), internally tested, pre external audit. The base reward and pool\n percentages are configurable defaults in source, not certified values.\n"},"/chain/genesis":{"slug":"/chain/genesis","title":"Chain parameters and genesis","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/node/config/, citrate-chain/node/src/genesis.rs","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"These are the chain identity and genesis parameters you need to point an account or a node at the Citrate\nNetwork and to reason about how the network agrees on its first block. It is for builders connecting to the\nnetwork and operators bringing up a node.\n\n## What it is\n\nThe Citrate Network ships several configurations that share one chain id and one set of consensus constants\nbut differ in block cadence and committee sizing. The active public network is the testnet, on chain id\n40204. A mainnet configuration exists in the tree on chain id 1, but it is pre-launch and not yet live; its\nbootstrap-node list is still a placeholder to be filled before launch. Use 40204 today.\n\nThe chain id is permanent. It is the number an account and a node check to confirm they are talking to\nCitrate and not some other network, and we do not plan to change it. Over JSON-RPC, `eth_chainId` returns\n`0x9d0c` on testnet, which is 40204 in hex.\n\nGenesis is deterministic. The same configuration produces the same state root and the same first-block hash\non every node, which is what lets independently started nodes agree on block 0 without coordinating. That\nproperty is built from a single shared path (`core/economics/src/genesis.rs`, called by\n`node/src/genesis.rs`) rather than reconstructed separately in each binary.\n\n## How to use it\n\n1. **Connect an account.** Point it at the testnet using chain id 40204 and the currency SALT at 18\n decimals. Fetch the current public endpoint from the testnet docs rather than copying an address out of a\n config file, since operational endpoints change.\n2. **Confirm you reached Citrate.** Ask the node for its chain id; a correct testnet node answers `0x9d0c`.\n\n ```bash\n curl -s http://127.0.0.1:8545 -H 'content-type: application/json' \\\n -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_chainId\",\"params\":[]}'\n # {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x9d0c\"} # 0x9d0c is 40204\n ```\n\n3. **Bring up a node on a network.** Select the matching configuration. The testnet config carries chain id\n 40204, one-second blocks, and strict VRF.\n\n ```bash\n citrate-node --config node/config/testnet.toml\n ```\n\n## Reference\n\nChain id by configuration, verified in `node/config/*.toml`:\n\n| Network | Chain id | Status |\n|---|---|---|\n| Testnet (`node/config/testnet.toml`) | 40204 | active public network |\n| Team testnet (`node/config/team-testnet.toml`) | 40204 | internal |\n| Devnet (`node/config/devnet.toml`) | 40204 | local development |\n| Mainnet (`node/config/mainnet.toml`) | 1 | pre-launch, not yet live |\n\nBlock cadence by configuration, verified in the same files. Consensus constants such as GhostDAG `k = 18`\nare shared across networks; see [Citrate Network consensus](/chain/consensus).\n\n| Network | `block_time` |\n|---|---|\n| Testnet | 1 s configured (about 2 s measured) |\n| Team testnet | 2 s |\n| Devnet | 2 s |\n| Mainnet (pre-launch) | 5 s |\n\nGenesis block, built deterministically from the shared genesis path\n(`core/economics/src/genesis.rs`):\n\n| Field | Value | Source |\n|---|---|---|\n| Canonical timestamp | 2026-01-01T00:00:00Z | `node/src/genesis.rs` (`CANONICAL_GENESIS_TIMESTAMP`) |\n| Base fee per gas | 1 Gwei (1e9 wei) | `core/economics/src/genesis.rs` (`base_fee_per_gas`) |\n| Gas limit | 30,000,000 | `core/economics/src/genesis.rs` (`gas_limit`) |\n\nThe genesis allocation categories sum to the one trillion SALT supply cap; the allocation structure is\ncovered under [network economics](/chain/economics). No private keys or mnemonics appear in any\nconfiguration in the repository, and we do not enumerate specific genesis account addresses in public docs.\n\n## Design rationale\n\nA permanent chain id and a single shared genesis path exist for the same reason: agreement should not depend\non operators coordinating by hand. If every node derives block 0 from the same code and the same config, two\nnodes started a continent apart still land on the same first-block hash, and a misconfigured node fails\nvisibly rather than quietly forking. The cost is that genesis is rigid; changing it is a deliberate,\nnetwork-wide event, not a per-node setting. For a network meant to outlast any single operator, that\nrigidity is the point.\n\n## Failure modes\n\nThe deterministic-genesis property is the load-bearing invariant: a node that computes a different state\nroot from a different config will not agree on block 0, which surfaces the misconfiguration immediately\nrather than letting it linger as a silent fork. The mainnet configuration is pre-launch; its bootstrap list\nis an empty placeholder, so a node pointed at mainnet today has nothing to connect to. Stay on 40204 until\nmainnet is announced on the [roadmap](/start/roadmap).\n\n## Access and canon\n\nPublic. Chain id, block cadence, consensus constants, and genesis block parameters are exactly what a\nbuilder or operator needs to connect and reason about the network. No genesis private keys or mnemonics\nexist in the repository; the code carries only public addresses, and we do not enumerate genesis account\naddresses or paste live bootstrap addresses here, fetch operational endpoints from the testnet docs.\n\n## Source and verification\n\n- Source: `citrate-chain/node/config/*.toml`, `citrate-chain/node/src/genesis.rs`,\n `citrate-chain/core/economics/src/genesis.rs`.\n- Chain id 40204 confirmed in `node/config/testnet.toml`; mainnet chain id 1 in `node/config/mainnet.toml`;\n `eth_chainId` returns `0x9d0c` (`core/api/src/eth_rpc.rs`, `cli/src/commands/advanced.rs`).\n- Audited against SHA: `9d5959e`.\n- Status: Implemented, testnet 40204 is the active public network; mainnet is Specified but pre-launch (the\n config exists, the network is not yet live). The path to mainnet is on the [roadmap](/start/roadmap).\n"},"/chain/lvm":{"slug":"/chain/lvm","title":"LVM, EVM execution and the parallel executor","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/core/execution/src/revm_adapter.rs, citrate-chain/core/execution/src/parallel/, citrate-chain/core/execution/src/mvcc/","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"The MVCC primitives","anchor":"the-mvcc-primitives"},{"depth":3,"text":"Scheduling","anchor":"scheduling"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The LVM is the execution layer of the Citrate Network: a full EVM that runs ordinary Solidity, with an\noptimistic parallel path underneath so that transactions which do not touch the same accounts run at the\nsame time. This page is for contract authors and node operators.\n\n## What it is\n\nThe LVM executes standard EVM bytecode. It does not fork the opcode set, so contracts compiled by Solidity\nor Vyper for Ethereum run unmodified, and the tooling you already use works against it. Underneath, the\nchain embeds REVM behind a thin adapter, `StateDBAdapter`, that bridges REVM's database trait to Citrate's\nown state store. From the outside it behaves like any EVM node; the difference is in how it schedules work.\n\nOn top of byte-for-byte EVM execution, the LVM adds an optimistic parallel path built on multi-version\nconcurrency control, MVCC. Within a block, transactions that do not conflict execute concurrently. Each one\nrecords the accounts it read, and at commit the executor checks whether any of those accounts changed since\nit started. If none did, the writes apply and the transaction commits; if one did, that transaction is the\nloser, and it retries against fresh state. The result is the same state root you would get from running the\nblock one transaction at a time, so single-threaded equivalence is preserved, the work just finishes\nsooner when the block allows it. Think of it as harvesting several rows of an orchard at once, then setting\naside and re-picking only the trees two crews reached for together.\n\nThe consensus that orders blocks before the LVM runs them is GhostDAG, covered under\n[Citrate Network](/chain/consensus).\n\n## How to use it\n\nThere is nothing Citrate-specific to do. Deploy and call contracts the way you would on Ethereum.\n\n1. Point your tooling, Foundry, ethers, or viem, at the Citrate RPC. See [chain RPC](/chain/rpc) for\n connection details.\n2. Set the chain id to 40204. The LVM writes this into the REVM configuration per executor, and\n transactions signed for another chain id are rejected.\n3. Deploy your contract and call it. Events, logs, and `eth_getLogs` behave as on Ethereum, so subgraphs\n and event assertions work without change.\n4. Read state with `eth_call`. State is correct after a node restart, because a cold in-memory cache falls\n through to the persistent store rather than returning empty data.\n\nThe parallel path is internal to the executor. You do not opt in, and you cannot observe it from a\ntransaction; it only affects how quickly a block is processed.\n\n## Reference\n\nThe audited surface, each item with its code path under `citrate-chain`.\n\n| Item | Behavior | Code path |\n|---|---|---|\n| Hardfork spec | `SpecId::CANCUN`, set with `.with_spec_id(SpecId::CANCUN)`. CANCUN enables MCOPY (EIP-5656), which Solidity 0.8.25+ emits for dynamic-bytes return encoding (the `BFR-VM-1` note). | `core/execution/src/revm_adapter.rs` |\n| Chain id | Supplied per executor with `Executor::with_chain_id(..)` and written into the REVM config. The Citrate Network uses 40204. | `core/execution/src/revm_adapter.rs` |\n| Precompiles | The nine standard Ethereum precompiles, ECRECOVER through BLAKE2F at `0x01` to `0x09`, plus Citrate extensions. | see [Precompiles](/chain/precompiles), [verification, inference, and attestation precompiles](/chain/precompiles-zkp) |\n| Logs and events | REVM logs are converted one to one into Citrate receipt logs by `convert_revm_log` (fix PIL-48), so `eth_getLogs` returns real event topics. | `core/execution/src/revm_adapter.rs` |\n| State source | `StateDBAdapter` is backed by an in-memory cache and a persistent store; cache misses fall through to the store (fix PIL-13b). | `core/execution/src/revm_adapter.rs` |\n| BLOCKHASH | Returns the hash for the 256 most recent blocks; older or unknown blocks return zero, per EVM spec. | `core/execution/src/revm_adapter.rs` |\n\n### The MVCC primitives\n\nThe parallel path lives in `core/execution/src/mvcc/` and `core/execution/src/parallel/`. It implements the\nBlock-STM, snapshot-versioning model proven by the TLA+ spec `specs/tla/consensus/ExecutorMVCC.tla`; the\nspec-to-code mapping is tabulated in `core/execution/src/mvcc/mod.rs`. Each concurrent worker holds three\nthings:\n\n- a pinned read version, the state version at the moment it started (`mvcc/read_set.rs`, `ReadVersion`);\n- a read set, the accounts it observed during execution (`mvcc/read_set.rs`, `ReadSet`);\n- a scratch journal, the writes it is holding for the transaction (`mvcc/scratch_journal.rs`,\n `ScratchJournal`).\n\nAt commit, the `CommitCoordinator` (`mvcc/commit.rs`) checks the read set against the current state: if no\naccount in the read set was written since the worker pinned, it applies the journal and advances the\nversion; otherwise the worker aborts and retries against a fresh pin. The `RetryHarness` (`mvcc/retry.rs`)\nbounds this at `DEFAULT_MAX_RETRIES`, which is 8, then falls back to serial execution so a contended\ntransaction always makes progress.\n\n### Scheduling\n\n`ParallelExecutor` (`parallel/executor.rs`) groups transactions into non-conflicting batches using a\n`ConflictScheduler` and a `DefaultAccessSetExtractor`, then runs the groups concurrently as Tokio tasks.\nConflicts are detected by `AccessSet::conflicts_with` (`parallel/conflict.rs`), which flags write-write,\nread-write, and write-read overlaps. The default extractor marks the sender as a writer, for nonce and\nbalance, and derives recipient access from the transaction type.\n\nThe executor microbenchmark (`benches/tps_parallel.rs`) records about a 2.41 times parallel speedup at 8\nworkers over a single worker on a disjoint-senders run. That is a property of the in-memory executor alone,\nnot network throughput, which is bound by RPC, signature checking, and the block gas limit and sits well\nbelow it. On the current testnet, sustained network throughput for simple transfers is about **750 TPS** - an arithmetic, gas-limit bound: the 30M block gas limit divided by 21,000 gas per transfer over ~2s blocks,\nwith no 10,000 headroom today. Sustained **5,000–10,000 TPS is a mainnet target** pursued through proposed\nprotocol upgrades (a higher block gas limit and faster blocks); it has not been reproduced on-chain. Treat\nthe executor microbench as informational, not a network guarantee.\n\n## Design rationale\n\nMost chains execute a block strictly in order, one transaction after another, because that is the simplest\nway to get a single agreed state. The cost is that a block of independent transactions, say a thousand\ntransfers between a thousand distinct pairs of accounts, runs no faster than a block where every\ntransaction touches the same contract. The LVM takes the optimistic bet that most transactions in a block\nare independent, runs them together, and pays the retry cost only for the few that actually collide. The\nbet is safe because the read-set check at commit is exact: if two transactions touched the same account,\nthe loser re-runs, so the committed result is identical to serial execution. The trade is added\ncomplexity in the executor, which is why the commit protocol is pinned to a TLA+ model rather than left to\nreview alone.\n\n## Failure modes\n\n- A transaction that loses the read-set check retries against fresh state. After `DEFAULT_MAX_RETRIES`\n attempts it falls back to serial execution, so a heavily contended account never stalls; it just stops\n benefiting from parallelism.\n- The committed state root is identical to serial execution by construction. If parallel and serial\n execution ever diverged, that would be a consensus fault, which is exactly the property the\n `ExecutorMVCC.tla` spec is written to exclude.\n- On a cold cache after restart, reads fall through to the persistent store rather than returning zero\n (PIL-13b), so `eth_call` does not silently return wrong data.\n\n## Access and canon\n\nPublic. EVM compatibility, the hardfork spec, the chain id, and the parallel-execution model are all things\na developer or operator needs to build and run on the Citrate Network, and none of it is sensitive. The\nMVCC section is research-provenance material; the prose here links to the TLA+ model rather than\nreproducing it. No keys, endpoints, or credentials appear on this page.\n\n## Source and verification\n\n- Source repo: `citrate-chain`\n- Source files: `core/execution/src/revm_adapter.rs`, `core/execution/src/parallel/{executor.rs,conflict.rs}`,\n `core/execution/src/mvcc/{commit.rs,read_set.rs,scratch_journal.rs,retry.rs,mod.rs}`\n- TLA+ spec: `specs/tla/consensus/ExecutorMVCC.tla`\n- Audited against SHA: `9d5959e`\n- Status: Implemented (pre-audit). The EVM path runs on testnet 40204; the MVCC commit protocol is\n Verified against its TLA+ model.\n"},"/chain/network":{"slug":"/chain/network","title":"Peer-to-peer networking","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/core/network/","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This is how Citrate nodes find each other, talk securely, and spread blocks and transactions across the\nnetwork. It is for operators bringing up a node and anyone who needs to understand how a new node reaches\nthe network head.\n\n## What it is\n\nThe networking layer is a peer-to-peer mesh: there is no central server that nodes phone home to, only other\nnodes. A node joins by reaching out to a small set of configured bootstrap nodes, learning about more peers\nfrom them, and then maintaining its own set of connections from there. Every connection is encrypted and\nauthenticated using the Noise protocol over TCP, so a peer both proves who it is and keeps the conversation\nprivate.\n\nThree flows do the real work, and they layer cleanly on top of one another:\n\n- **Discovery and bootstrap.** A fresh node starts from its configured bootstrap nodes\n (`bootnode.rs`), connects to them, and uses peer discovery (`discovery.rs`) to widen its set of known and\n connected peers. Bootstrap nodes are an entry point, not a dependency; once a node has peers, it keeps\n going without them.\n- **Gossip propagation.** New blocks and transactions spread by gossip (`gossip.rs`,\n `block_propagation.rs`): each node forwards what it has not seen before to its peers, and a seen-message\n cache stops the same item from circulating endlessly. A block reaches the whole network in a handful of\n hops without any node needing a global view.\n- **Reaching peers behind NAT.** Many nodes sit behind home or institutional routers, so the layer includes\n NAT traversal (`nat.rs`) to keep those peers reachable rather than stranded.\n\nThe layer also carries message types for the network's AI and learning traffic (`ai_handler.rs`,\n`learning_messages.rs`), so model and training-related messages travel the same authenticated mesh as\nordinary blocks and transactions.\n\n## How to use it\n\nYou configure the networking layer, you do not call it directly; the node drives it for you.\n\n1. **Supply bootstrap nodes.** Point your node at a known set of bootstrap nodes for the network you are\n joining. The node connects to them first, then discovers the rest of the mesh on its own.\n2. **Let the node sync.** Once connected, the node downloads from the network head and applies blocks until\n it is caught up. From then on it stays current through gossip.\n3. **Check peer health.** Confirm your node has peers and is keeping up using the node's status over\n JSON-RPC. See [run a node](/operators/run-a-node) for the operator walkthrough and the\n [consensus reference](/chain/consensus) for how the blocks it receives are ordered.\n\n## Reference\n\nThe components of `core/network/`, each citing its file:\n\n| Component | File | What it does |\n|---|---|---|\n| Encrypted transport | `noise.rs` | Noise handshake and encrypted, authenticated transport over TCP |\n| Peer discovery | `discovery.rs` | finds peers and tracks connected ones |\n| Bootstrap nodes | `bootnode.rs` | the configured entry points a new node starts from |\n| Block gossip | `gossip.rs`, `block_propagation.rs` | spreads blocks with seen-message de-duplication |\n| Transaction gossip | `transaction_gossip.rs` | relays transactions with a seen-transaction cache |\n| Chain sync | `sync.rs` | brings a node up to the network head |\n| NAT traversal | `nat.rs` | keeps peers behind routers reachable |\n| AI and learning messages | `ai_handler.rs`, `learning_messages.rs` | message types for model and training traffic |\n\nWe do not list live bootstrap addresses here. They are deployment configuration, not documentation, and a\nnode operator supplies them for the network being joined; fetch current values from the testnet operator\ndocs.\n\n## Design rationale\n\nA gossip mesh seeded by a few bootstrap nodes is the design that keeps the network from depending on any one\nmachine. There is no coordinator to take down, no single node whose failure stops propagation, and a new\noperator needs only a couple of known entry points to join. Encrypting every link with Noise means a peer is\nauthenticated before it can influence a node's view, which matters on a network where participation is\nidentity-checked rather than anonymous. The trade is that propagation is probabilistic rather than directed;\ngossip accepts a little redundant traffic in exchange for not needing a global map of the network.\n\n## Failure modes\n\nBootstrap nodes are an entry point, not a single point of failure: once a node has discovered peers it no\nlonger needs them, so a bootstrap node going offline does not cut a synced node off. The seen-message caches\nin gossip stop a block or transaction from looping forever, which bounds the traffic a single item can\ngenerate. Because every connection is authenticated through the Noise handshake, an unauthenticated peer\ncannot inject blocks or transactions into a node's view. NAT and sync surfaces are internally tested but not\nyet externally audited; treat them as production-track, pre-certification.\n\n## Access and canon\n\nPublic. Transport, discovery, gossip, and sync are what a node operator needs to join the network and stay\ncurrent. No bootstrap addresses, node keys, or relay credentials appear here; each node generates its own\nNoise identity, and nothing is hardcoded in these docs.\n\n## Source and verification\n\n- Source: `citrate-chain/core/network/` (`noise.rs`, `discovery.rs`, `bootnode.rs`, `gossip.rs`,\n `block_propagation.rs`, `sync.rs`, `nat.rs`, `ai_handler.rs`, `learning_messages.rs`).\n- Operator path: [run a node](/operators/run-a-node); block ordering: [consensus](/chain/consensus).\n- Audited against SHA: `9d5959e`.\n- Status: Implemented (testnet), internally tested, pre external audit.\n"},"/chain/precompiles-zkp":{"slug":"/chain/precompiles-zkp","title":"Verification, inference, and attestation precompiles (summary)","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/core/execution/src/precompiles/","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":3,"text":"Verification, `0x0107` to `0x0109`","anchor":"verification-0x0107-to-0x0109"},{"depth":3,"text":"Hosted inference, `0x0100` to `0x0106`","anchor":"hosted-inference-0x0100-to-0x0106"},{"depth":3,"text":"Deterministic Q16.16 compute, `0x010A` to `0x010F`","anchor":"deterministic-q1616-compute-0x010a-to-0x010f"},{"depth":3,"text":"Attestation gate","anchor":"attestation-gate"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This is a summary of three precompile families on the Citrate Network. It tells you what they do and where\ntheir addresses sit. Their proving internals and circuit design are public in the `citrate-chain`\nrepository and linked below rather than reproduced here. It is for anyone deciding how to build against\nverifiable AI work on the chain.\n\n## What it is\n\nThe Citrate Network ships one precompile family at `0x0100` to `0x010F`, in three groups that let an\non-chain contract trust off-chain AI work without re-running it, plus an attestation gate over\nnon-deterministic inference. The interfaces and addresses are public, and the full implementation is public\nin the `citrate-chain` repository (Apache-2.0); this page summarizes and links to it.\n\n| Group | Addresses | What it does |\n|---|---|---|\n| Hosted inference | `0x0100` to `0x0106` | Model deployment and registration, single and batch inference, metadata, benchmarking, and model encryption. Model-runtime-backed: a hosted-inference call returns a **signed receipt** over the result, gated by hardware attestation - **not** a pure deterministic on-chain proof of correctness. |\n| Proof verification (incl. ZK-verified inference) | `0x0107` to `0x0109` | Commit to tensors and verify claims and proofs with hashes, Merkle paths, and a Halo2-KZG pairing verifier. Deterministic; the result is verifiable on-chain. |\n| Deterministic Q16.16 compute | `0x010A` to `0x010F` | Fixed-point tensor primitives (matmul, dot, softmax, relu, linear, transpose); bit-identical across nodes. |\n| Attestation gate | consulted by `0x0101` and `0x0102` | Decides whether a non-deterministic inference path may run, based on hardware attestation. |\n\nThe cryptography underneath rests on three publicly nameable building blocks: Q16.16 fixed-point math for\ndeterminism, Halo2-KZG proof verification, and TEE attestation. Poseidon over BN254\n(`zkp/poseidon_bn254.rs`) is the commitment hash. The proving-system internals and circuit design are public\nin `citrate-chain` and linked below.\n\n### Verification, `0x0107` to `0x0109`\n\nThese three precompiles let a contract check off-chain AI work without redoing it.\n\n- `0x0107 TENSOR_COMMIT` produces a Poseidon commitment over a canonical-format tensor and returns a 32-byte\n field element.\n- `0x0108 INFERENCE_PROOF_VERIFY` verifies an inference proof with a Halo2-KZG verifier and returns a\n 32-byte boolean.\n- `0x0109 MERKLE_VERIFY_TENSOR` verifies that a tensor element is part of a committed tensor via a Merkle\n path and returns a 32-byte boolean.\n\nAll three are deterministic by construction, hash plus pairing plus integer math, and their byte-level\noutput is frozen: drift would fork the chain and invalidate prior commitments. The tensor wire format these\nconsume is the public version 1 format documented in [Precompiles](/chain/precompiles).\n\n### Hosted inference, `0x0100` to `0x0106`\n\nThis group is the hosted AI inference runtime: model deployment and registration, single and batch\ninference, metadata query, benchmarking, and model-encryption operations. It is model-runtime-backed, so an\ninference call returns a **signed receipt** over the result - an attestable statement about what ran, not a\ncryptographic proof that the output is correct. Whether a non-deterministic floating-point inference path is\npermitted at all is decided by the attestation gate below. When no runtime is hosted, these precompiles\nsurface a discoverable error rather than silently returning fake data.\n\nFor a result that is *verifiable on-chain* rather than merely signed, use the proof-verification group\n(`0x0107`–`0x0109`) - a ZK-verified inference proof checked by the Halo2-KZG verifier - or keep the\ncomputation inside the deterministic Q16.16 compute group (`0x010A`–`0x010F`).\n\n### Deterministic Q16.16 compute, `0x010A` to `0x010F`\n\nSix fixed-point tensor primitives - matmul, dot, softmax, relu, linear, and transpose - computed in\nsaturating Q16.16 integer arithmetic so the result is bit-identical on every node. This is the deterministic\nfloor the proof and commitment machinery rests on; see [Precompiles](/chain/precompiles) for the reference.\n\n### Attestation gate\n\nA trait-based gate (`core/execution/src/precompiles/attestation/`) consulted by `0x0101 MODEL_INFERENCE` and\n`0x0102 BATCH_INFERENCE` before they run. The default on mainnet validator binaries is always-reject, with\nno silent allow-by-omission: in strict inference mode the precompile refuses to run rather than execute an\nunattested non-deterministic path. A later phase adds live verification of hardware attestation, a\ncloud-attestation JWT plus GPU remote-attestation claims, so inference can run against an attested,\nTEE-hosted model. That verifier is not yet enabled.\n\n## Design rationale\n\nThe hard problem these families solve is letting a contract believe a model's output without paying to run\nthe model on-chain. There are two honest answers, and the docs keep them distinct. For work that can be made\ndeterministic, the chain commits to the work and verifies a proof of it (the `0x0107`–`0x0109` group and the\n`0x010A`–`0x010F` compute primitives), so a contract checks a small proof instead of repeating a large\ncomputation - that result is verifiable on-chain. For hosted inference that cannot be made bit-identical\n(`0x0100`–`0x0106`), the chain does not claim a cryptographic proof of correctness: it returns a signed\nreceipt gated by hardware attestation, an attestable statement about what ran and where. Commitments use\nPoseidon, proofs use a fixed Halo2-KZG verifier, and the byte output is frozen. The attestation gate\naddresses the one place determinism cannot reach, floating-point inference on a GPU: rather than trust it\nblindly, the gate defaults to refusing it until hardware attestation proves where it ran. The deliberate\nchoice to fail closed, to reject by default, is the safe trade for a path that touches non-deterministic\ncompute.\n\n## Failure modes\n\n- The inference precompiles fail discoverably: with no runtime hosted, a call returns an error a contract\n can detect, not fabricated output.\n- The attestation gate defaults to always-reject. An unattested non-deterministic inference path does not\n run; there is no allow-by-omission, so a missing or stale attestation fails closed.\n- The verification precompiles have frozen byte output. Any drift in their result would fork the chain and\n invalidate every prior commitment, which is why the format is fixed rather than versioned in place.\n\n## Access and canon\n\nThis page is a summary. It carries the address map, the input and output shapes, for example\n\"returns a 32-byte boolean\", and plain-English behavior.\n\nThe implementation detail of all three families - circuit construction, prover and verifier internals, the\ninference runtime, the attestation-verification logic, and the exact ABIs - is public in the `citrate-chain`\nrepository (Apache-2.0). This page summarizes and links to that source rather than reproducing it. Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity. No keys, ceremony secrets, or\ncredentials appear on this page.\n\nFor the full internals - the circuit specs, verifier code, or attestation-verification design - read the\nprecompile and ZKP sources in `citrate-chain` linked below.\n\n## Source and verification\n\n- Source repo: `citrate-chain` (public, Apache-2.0)\n- Public anchors audited for this summary:\n `core/execution/src/precompiles/{verify.rs,inference.rs,attestation/}`,\n `core/execution/src/zkp/{poseidon_bn254.rs,halo2/}`, dispatch in\n `core/execution/src/precompiles/mod.rs`\n- Audited against SHA: `9d5959e`\n- Status: Implemented (pre-audit) for the deterministic verification path on testnet 40204. The\n attestation gate is Implemented in its always-reject default; live attestation verification is Specified,\n not yet enabled.\n"},"/chain/precompiles":{"slug":"/chain/precompiles","title":"Precompiles, address pages, tensor, x402, q16","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/core/execution/src/precompiles/","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"Tensor primitives","anchor":"tensor-primitives"},{"depth":3,"text":"x402 payment precompiles","anchor":"x402-payment-precompiles"},{"depth":3,"text":"Belnap-q16 lattice aggregation","anchor":"belnap-q16-lattice-aggregation"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The Citrate Network keeps the nine standard Ethereum precompiles and adds its own at higher addresses:\ndeterministic tensor primitives, x402 payment verification, and Belnap-q16 lattice aggregation. This page\nis the reference for the address pages and for the tensor, x402, and q16 surfaces, with each item cited to its\ncode path. It is for contract authors.\n\n## What it is\n\nA precompile is a contract address that runs native code rather than EVM bytecode, so a common operation\nruns faster and cheaper than the equivalent Solidity. The Citrate Network keeps the standard nine at `0x01`\nto `0x09`, ECRECOVER through BLAKE2F, and adds several address pages above them, routed by\n`PrecompileExecutor` in `core/execution/src/precompiles/mod.rs`.\n\n| Page | Range | Purpose |\n|---|---|---|\n| AI, verification, compute | `0x0100` to `0x010F` | inference runtime, proof verification, deterministic Q16 compute |\n| Learning | `0x0110` to `0x011F` | Belnap-q16 aggregation, routing inference |\n| Signature verification | `0x0120` to `0x012F` | Ed25519 signature verification |\n| Recursive-fold verification | `0x0130` to `0x013F` | CommD proof verification (feature-gated) |\n| x402 payments | `0x0200` to `0x0209` | EIP-712 and EIP-3009 payment verification |\n\n`is_precompile()` recognizes an address by matching its leading zero bytes plus the page bytes, and\n`execute()` dispatches by the same prefix. This page documents the tensor, x402, and q16 surfaces. The\n`0x0100` to `0x0109` inference, verification, and attestation surfaces are summarized\non a separate page, see [inference, verification, and attestation precompiles](/chain/precompiles-zkp).\n\n## How to use it\n\nCall a precompile like any EVM precompile: `staticcall` the address with ABI-packed input and read the\nreturn bytes.\n\n1. Pack the input for the precompile you are calling. The exact byte offsets are defined by the source for\n each one; the layout there is the authoritative ABI.\n2. `staticcall` the precompile address. For example, x402 EIP-712 verification is a `staticcall` to\n `0x0000…0200`.\n3. Read the return value. For the x402 verifiers the 32-byte return holds the recovered address in its low\n 20 bytes, mirroring ECRECOVER, with the zero address on failure.\n4. For exact encodings, work from the test vectors in the precompile's source file rather than from prose.\n\nSee [chain tutorials](/chain/tutorials/call-citrate-rpc) for a worked x402 verification, and [chain RPC](/chain/rpc) for\nconnection details.\n\n## Reference\n\n### Tensor primitives\n\nCode: `core/execution/src/tensor/` and the wire format in\n`core/execution/src/precompiles/tensor_format.rs`.\n\nThe in-VM tensor engine (`tensor/engine.rs`, `TensorEngine`) allocates tensor values, an `ArrayD` plus\na shape and an optional gradient, keyed by a `U256` id, with a configurable memory cap; `tensor/ops.rs`\nprovides the operations. Every AI precompile that accepts or returns tensor data does so in the canonical\nbinary tensor format, version 1, which is frozen:\n\n```text\n[ 1 byte rank ] # 0 to 4; rank 5 or more is rejected\n[ rank x 4 bytes shape (u32 BE) ] # each dim at least 1, row-major\n[ 1 byte dtype ] # selector; unknown maps to UnknownDtype\n[ data bytes ] # element_count x dtype.byte_size, big-endian\n```\n\nThat the format is frozen is what keeps the `0x0107 TENSOR_COMMIT` commitments stable across versions. An\nincompatible change requires a new dtype byte or a new precompile address. The deterministic Q16.16 compute\nprecompiles (`0x010A` to `0x010F`) are implemented as six fixed-point tensor primitives - matmul, dot,\nsoftmax, relu, linear, and transpose - and dispatched to `compute::execute` (`compute.rs`); treat the\ntensor-engine API as the documented surface here.\n\n### x402 payment precompiles\n\nCode: `core/execution/src/precompiles/x402.rs`. Addresses `0x0200` to `0x0202`. These accelerate Coinbase\nx402 payment verification at the precompile level, about nine times cheaper than the equivalent Solidity\nper the source. Each is deterministic, ecrecover plus keccak.\n\n| Address | Name | Input | Output | Gas |\n|---|---|---|---|---|\n| `0x0200` | `EIP712_VERIFY` | EIP-712 typed-data digest material and signature (r, s, v) | 32 bytes: 12 zero bytes then the 20-byte recovered address; zero address on failure | 3,450 |\n| `0x0201` | `TRANSFER_AUTH_VERIFY` | EIP-3009 `TransferWithAuthorization` fields and signature | 32 bytes: bytes 12 to 31 hold the recovered signer; verifies signer equals `from` | 4,200 |\n| `0x0202` | `BATCH_PAYMENT_VERIFY` | count then repeated `TransferWithAuthorization` records | per-record recovered addresses | `BATCH_BASE` 2,000 plus `BATCH_PER_PAYMENT` 3,800 times n |\n\nThe EIP-3009 type hash is\n`keccak256(\"TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)\")`.\nConstants live in `x402::addresses` and `x402::gas_costs`. A Level-3 upgrade path, a validator-embedded\nfacilitator with implicit header payments and cross-shard settlement, is described in the source header and\nADR-005; it is not yet implemented.\n\n### Belnap-q16 lattice aggregation\n\nCode: `core/execution/src/precompiles/q16/`. Address `0x0110`, `BELNAP_AGGREGATE`. A second learning slot,\n`0x0111` (`ROUTING_INFERENCE`), is wired in the dispatcher but is future work (RM-FL-2).\n\nThe substrate is a hand-rolled, integer-only, saturating Q16.16 fixed-point library (`q16/mod.rs`, type\n`Q16(i64)`, where the real value is `inner / 2^16`). Every operation uses only `i64` math, with an `i128`\nintermediate only inside multiply and divide, no floats and no `unsafe`, so results are bit-identical on any CPU. Overflow saturates to `Q16::MAX` or\n`Q16::MIN`, division by zero returns `MAX` or `MIN` by the numerator's sign, and nothing panics. `f64`\nconversions exist only behind `#[cfg(test)]`.\n\n`0x0110 BELNAP_AGGREGATE` (`q16/belnap.rs`) aggregates per-validator embedding contributions into a Q16\nweighted-mean value per dimension and a Belnap-FOUR state per dimension drawn from {Neither, True, Both,\nFalse}. The output is bit-deterministic; gas is `2000 + 50 * dim`.\n\n- Input: a header giving `dim` and `n`, the participant count, then for each participant and dimension a Q16\n embedding, a Q16 confidence, and a Q16 weight, plus a Q16 positive threshold and a Q16 negative threshold.\n- Output: per dimension, 8 bytes of Q16 (i64) aggregated value then 1 byte of Belnap state, so `dim * 9`\n bytes in all.\n- The off-chain f32 reference is `core/learning/src/belnap.rs`. The precompile matches it at the semantic\n level, sign and confidence regime, not at byte equality, because the reference uses f32.\n\n## Design rationale\n\nThese precompiles exist because the operations they perform need to be both cheap and exactly reproducible\non every node. Payment verification is signature recovery, which is far cheaper as native code than as\nSolidity, so x402 is a precompile. Aggregation of model contributions has to produce the identical result\non every validator or the chain would fork, and floating-point math does not, so the q16 path is built on\ninteger fixed-point that saturates rather than panicking and is bit-identical across hardware. The frozen\ntensor format follows the same discipline: a stable wire format is what lets a commitment made today still\nverify tomorrow.\n\n## Failure modes\n\n- The x402 verifiers fail closed: on a bad signature they return the zero address rather than reverting, so\n a caller that does not check the return treats a failed verification as an unrecognized signer, not a\n success.\n- Q16.16 arithmetic never panics; overflow saturates and division by zero returns a signed extreme. The\n cost is that a saturated value is a clamped value, not an error, so contracts that care about the\n difference must check ranges themselves.\n- The tensor format rejects rank 5 or higher and unknown dtypes rather than guessing, so malformed tensor\n input does not silently decode into a wrong shape.\n\n## Access and canon\n\nThis page is staged commercial. It documents implementation depth, precompile ABIs and gas economics, that\na contracted, identity-verified builder should have but that we would rather not have vacuumed up\nanonymously. Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity. Nothing here is secret: addresses, public ABIs,\ngas constants, and type hashes are all observable on-chain. The genuinely sensitive precompiles, the\ninference, proof-verification, and attestation internals, are not on this page; see\n[verification, inference, and attestation precompiles](/chain/precompiles-zkp).\n\n## Source and verification\n\n- Source repo: `citrate-chain`\n- Source files: `core/execution/src/precompiles/mod.rs`,\n `core/execution/src/precompiles/{x402.rs,tensor_format.rs}`,\n `core/execution/src/precompiles/q16/{mod.rs,belnap.rs}`, `core/execution/src/precompiles/compute.rs`,\n `core/execution/src/tensor/`, `core/learning/src/belnap.rs`\n- Audited against SHA: `9d5959e`\n- Status: Implemented (pre-audit). The tensor format, the `0x010A` to `0x010F` Q16 compute primitives, and\n the x402 and q16 precompiles run on testnet 40204. `0x0111 ROUTING_INFERENCE` is wired in the dispatcher\n but is future work (RM-FL-2), and the `0x0130` recursive-fold CommD verifier is feature-gated.\n"},"/chain/rpc":{"slug":"/chain/rpc","title":"JSON-RPC reference","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/core/api/src/{eth_rpc.rs,server.rs,ai_rpc.rs,economics_rpc.rs,methods/}","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"Ethereum-compatible (`eth_*`)","anchor":"ethereum-compatible-eth_"},{"depth":3,"text":"Chain and DAG (`chain_*`, `citrate_*`)","anchor":"chain-and-dag-chain_-citrate_"},{"depth":3,"text":"Economics (`citrate_*`)","anchor":"economics-citrate_"},{"depth":3,"text":"AI (`citrate_*`)","anchor":"ai-citrate_"},{"depth":3,"text":"Network and operations (`citrate_*`)","anchor":"network-and-operations-citrate_"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The JSON-RPC interface a Citrate Node serves over HTTP and WebSocket. It speaks the Ethereum-compatible\n`eth_*` methods you already know, plus a `chain_*` and `citrate_*` set for reading the BlockDAG, calling\non-chain models, and reading network economics. This page is for developers building against the Citrate\nNetwork. If you have never made a call, start with [your first 10 minutes](/start/tutorials/your-first-10-minutes).\n\n## What it is\n\nA Citrate Node exposes one JSON-RPC endpoint. A local node serves it at `http://127.0.0.1:8545`. Every\nrequest follows JSON-RPC 2.0, and quantities come back as `0x`-prefixed hex strings, the same convention\nEthereum uses:\n\n```json\n{ \"jsonrpc\": \"2.0\", \"id\": 1, \"method\": \"eth_chainId\", \"params\": [] }\n```\n\nThere are roughly ninety methods, registered across three namespaces:\n\n- `eth_*` is the Ethereum-compatible surface, so existing Solidity tooling and signing libraries work\n unchanged.\n- `chain_*` reads the BlockDAG directly: tips, height, a block, a transaction.\n- `citrate_*` is the Citrate-native surface for DAG statistics, network economics, on-chain inference, and\n operator controls.\n\nThe one fact worth confirming before anything else is that you are pointed at Citrate. Ask the node for its\nchain id; `0x9d0c` is 40204, and 40204 is the testnet:\n\n```bash\ncurl -s http://127.0.0.1:8545 -H 'content-type: application/json' \\\n -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_chainId\",\"params\":[]}'\n# {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x9d0c\"} # 0x9d0c is 40204\n```\n\n## How to use it\n\nEach call is a single HTTP POST. A small shell helper keeps the examples short:\n\n```bash\nexport RPC=http://127.0.0.1:8545\n\nrpc () {\n curl -s \"$RPC\" -H 'content-type: application/json' \\\n -d \"{\\\"jsonrpc\\\":\\\"2.0\\\",\\\"id\\\":1,\\\"method\\\":\\\"$1\\\",\\\"params\\\":${2:-[]}}\"\n}\n```\n\nRead the latest height, then read the DAG:\n\n```bash\nrpc eth_blockNumber\n# {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x3039\"}\n\nrpc citrate_getDagStats\n# { \"tipsCount\": 3, \"maxBlueScore\": 11800, \"height\": 12345,\n# \"ghostdagParams\": { \"k\": 18, \"maxParents\": 10, \"finalityDepth\": 100 } }\n```\n\nRun a model call on the chain. Inference is a chain operation here, not an outside service:\n\n```bash\nrpc citrate_getTextEmbedding '[\"the quick brown fox\"]'\n# result: [[0.0123, -0.045, ...]] (a bge-m3 embedding vector)\n```\n\nA single embedding or search call accepts at most 256 inputs (`MAX_EMBEDDING_INPUTS`); a larger batch\nreturns invalid params. The consensus that orders all of these reads is GhostDAG, covered under\n[Citrate Network consensus](/chain/consensus), and the fee and supply numbers are in\n[economics](/chain/economics).\n\n## Reference\n\nMethods are grouped by what they touch. Each row cites the source file in `citrate-chain` it is registered\nin. Methods that do not appear in the source at this SHA are not listed here.\n\n### Ethereum-compatible (`eth_*`)\n\nRegistered in `core/api/src/eth_rpc.rs` and `core/api/src/server.rs`.\n\n| Method | Params | Returns |\n|---|---|---|\n| `eth_chainId` | none | chain id, hex (`0x9d0c` is 40204) |\n| `eth_blockNumber` | none | latest height, hex |\n| `eth_getBlockByNumber` | `[blockTag\\|hex, includeTxs]` | block object or `null` |\n| `eth_getTransactionByHash` | `[hash]` | transaction object or `null` |\n| `eth_getTransactionReceipt` | `[hash]` | receipt object or `null` |\n| `eth_getBalance` | `[address, blockTag]` | balance, hex |\n| `eth_getCode` | `[address, blockTag]` | contract code, hex |\n| `eth_getStorageAt` | `[address, slot, blockTag]` | storage word, hex |\n| `eth_getTransactionCount` | `[address, blockTag]` | nonce, hex |\n| `eth_accounts` | none | `[]`, the node holds no keys |\n| `eth_call` | `[txObject, blockTag]` | return data, hex |\n| `eth_estimateGas` | `[txObject]` | gas estimate, hex |\n| `eth_gasPrice` | none | gas price, hex |\n| `eth_feeHistory` | `[blockCount, newestBlock, percentiles]` | fee-history object |\n| `eth_getLogs` | `[filterObject]` | matching log entries |\n\nThe standard filter, send, and node-metadata methods are also present (`eth_sendRawTransaction`,\n`eth_newFilter`, `eth_syncing`, `net_version`, `web3_clientVersion`, and so on).\n\n### Chain and DAG (`chain_*`, `citrate_*`)\n\nThe `chain_*` namespace reads the BlockDAG directly; `citrate_getDagStats` summarizes the current tip set.\nRegistered in `core/api/src/server.rs` and `core/api/src/eth_rpc.rs`.\n\n| Method | Params | Returns |\n|---|---|---|\n| `chain_getHeight` | none | current height |\n| `chain_getTips` | none | the current tip hashes |\n| `chain_getBlock` | `[hashOrHeight]` | a block |\n| `chain_getTransaction` | `[hash]` | a transaction |\n| `citrate_getDagStats` | none | tips, blue score, height, GhostDAG params |\n\n### Economics (`citrate_*`)\n\nRegistered in `core/api/src/economics_rpc.rs`. These return method-not-found when the node runs without an\neconomics manager configured.\n\n| Method | Params | Returns |\n|---|---|---|\n| `citrate_getToken` | none | `{name, symbol, decimals, totalSupply, totalMinted}` |\n| `citrate_getEconomicState` | none | supply, gas price, staked amount, treasury, and related fields |\n| `citrate_gasPrice` | none | base gas price, hex |\n| `citrate_getStakedBalance` | `[address]` | staked balance, hex |\n| `citrate_getReputationScore` | `[address]` | reputation score, number |\n\n### AI (`citrate_*`)\n\nEmbedding, search, and chat are registered in `core/api/src/ai_rpc.rs`; model and inference lifecycle\nmethods in `core/api/src/server.rs`.\n\n| Method | Params | Returns |\n|---|---|---|\n| `citrate_getTextEmbedding` | `[text \\| string[]]`, at most 256 inputs | one embedding, or one per input |\n| `citrate_semanticSearch` | `[query, documents[], topK?]`, at most 256 documents | entries ranked by cosine similarity |\n| `citrate_chatCompletion` | `[{request}]` or `[prompt, maxTokens?, temperature?]` | a chat-completion response |\n| `citrate_getModels` | none | registered models |\n| `citrate_getModel` | `[modelId]` | model detail |\n| `citrate_getInferenceResult` | `[id]` | an inference result by id |\n| `citrate_createTrainingJob` | `[{job}]` | a training-job handle |\n| `citrate_getTrainingJob` | `[id]` | training-job status |\n| `citrate_deployModel` | operator-authenticated | registers a model |\n\nThe embedding model is `bge-m3`; the default chat model is `mistral-7b-instruct-v0.3`.\n\n### Network and operations (`citrate_*`)\n\nAggregate reads are open; the snapshot and emergency controls authenticate inside the handler against an\noperator-supplied token, never by seat.\n\n| Method | Params | Returns |\n|---|---|---|\n| `citrate_getMempoolStats` | none | aggregate mempool statistics |\n| `citrate_getMempoolSnapshot` | operator-authenticated | per-transaction mempool detail |\n| `citrate_emergencyStatus` | operator-authenticated | block-production pause state |\n| `citrate_emergencyPause` | operator-authenticated | halts block production |\n| `citrate_emergencyResume` | operator-authenticated | resumes block production |\n\n## Failure modes\n\nThe interface fails closed, and the errors are standard JSON-RPC:\n\n- `-32601`, method not found. A typo, or a method the node does not serve. The economics methods return this\n when no economics manager is configured; the chain, DAG, and core AI methods do not require one.\n- `-32602`, invalid params. The shape or count is wrong. An embedding or search batch over 256 inputs lands\n here.\n- `-32600`, invalid request. The envelope is not valid JSON-RPC 2.0.\n\nA chain id other than `0x9d0c` is not an error code; it means you are pointed at a different network. The\noperator-authenticated methods (`citrate_getMempoolSnapshot`, the `citrate_emergency*` controls,\n`citrate_deployModel`) reject a missing or wrong token rather than acting, so an unprivileged caller cannot\npause production or read per-transaction mempool detail.\n\n## Access and canon\n\nPublic. The `eth_*`, `chain_*`, DAG, and AI methods are the open surface a developer needs to build. No\nkeys, mnemonics, or operator tokens appear here; the operator token is supplied at runtime and is never\ndocumented. The economics reads are open on a configured node, though the deeper reward model narrative\nsits at the commercial tier. Identity on inference is a claim unless signed: anonymous callers reach public\nmodels only, and gated models require the signature binding.\n\n## Source and verification\n\nMethods verified against `citrate-chain` at `9d5959e` by enumerating every `add_sync_method(\"...\")`\nregistration in `core/api/src/eth_rpc.rs`, `server.rs`, `ai_rpc.rs`, `economics_rpc.rs`, and the\n`methods/` module. Chain id `0x9d0c` confirmed in `eth_rpc.rs`; the 256-input cap (`MAX_EMBEDDING_INPUTS`)\nin `ai_rpc.rs`. The network is live on testnet 40204. Status: Implemented (testnet, pre-audit).\n"},"/chain/sequencer":{"slug":"/chain/sequencer","title":"Citrate Sequencer, Mempool and Block Building","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/core/sequencer/src/mempool.rs, citrate-chain/core/sequencer/src/validator.rs, citrate-chain/core/sequencer/src/block_builder.rs","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"Mempool, `src/mempool.rs`","anchor":"mempool-srcmempoolrs"},{"depth":3,"text":"Transaction validator, `src/validator.rs`","anchor":"transaction-validator-srcvalidatorrs"},{"depth":3,"text":"Block builder, `src/block_builder.rs`","anchor":"block-builder-srcblock_builderrs"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The sequencer is the path a transaction takes between arriving at a node and landing in a block. It validates each transaction, holds the valid ones in a priority mempool, and assembles candidate blocks for proposers. This page is for developers who submit transactions and operators who want to understand how a block is put together.\n\n## What it is\n\nThink of the mempool as a sorting shed at harvest. Transactions come in, the ones that do not pass inspection are set aside, and the rest are sorted so the most important work is taken first. The sequencer (`core/sequencer/`) does this in three plain stages.\n\n1. Validate. An incoming transaction passes a validation pipeline: signature, balance, nonce, gas price and limit, data size, rate limit, and an address blacklist.\n2. Stage. A valid transaction enters the priority mempool, which holds up to ten thousand transactions by default, caps each sender, drops duplicates, and orders by priority.\n3. Build. The block builder takes the top-priority transactions, executes them, computes the state root and receipt root and an EIP-1559 base fee, and produces a signed candidate block.\n\nPriority is not gas price alone. Every transaction carries a `TxClass`, and each class has a multiplier so that system and compute traffic can be ordered ahead of ordinary transfers. The effective priority is the gas price multiplied by the class multiplier.\n\n| `TxClass` | Priority multiplier |\n|---|---|\n| `System` | 1000 |\n| `ModelUpdate` | 100 |\n| `Compute` | 80 |\n| `Training` | 50 |\n| `Inference` | 20 |\n| `Storage` | 10 |\n| `Standard` | 1 |\n\nSource: `core/sequencer/src/mempool.rs`, `TxClass::priority_multiplier()` (`src/mempool.rs:66`).\n\n## How to use it\n\nYou submit transactions to the mempool; proposers build from it. In Rust the flow is:\n\n1. Construct a mempool from a config (defaults: ten thousand capacity, one Gwei gas floor, one hundred transactions per sender).\n2. Validate a transaction against the current state before staging it.\n3. Add the validated transaction to the mempool; it is sorted into place by priority.\n4. When it is your turn to propose, build a candidate block from the top of the pool.\n\n```rust\nuse citrate_sequencer::*;\nuse std::sync::Arc;\n\nlet mempool = Arc::new(Mempool::new(MempoolConfig::default())); // 10k cap, 1 Gwei floor\n\n// Validate before staging.\nlet validator = TxValidator::new(ValidationRules::default(), state_provider);\nvalidator.validate(&tx).await?;\nmempool.add_transaction(tx).await?;\n\n// Build a candidate block from the pool.\nlet builder = BlockBuilder::new(builder_config, mempool.clone(), proposer_key).with_executor(executor);\nlet block = builder.build_block(selected_parent, merge_parents, parent_height, parent_blue_score, vrf_proof).await?;\n```\n\nTo watch pending transactions on a live node without Rust, use `mempool_getPending` and `citrate_getMempoolStats` over JSON-RPC; see [chain RPC](/chain/rpc).\n\n## Reference\n\n### Mempool, `src/mempool.rs`\n\n- `Mempool::new(config)`, build a mempool. `MempoolConfig::default()` is ten thousand capacity, one Gwei gas floor, one hundred per sender (`src/mempool.rs:181`).\n- `Mempool::add_transaction(tx)`, validate and insert with priority sorting.\n- `Mempool::get_transactions(limit)`, extract the top-priority batch for building.\n- `Mempool::pending_nonce_for(sender)`, the next pending nonce for a sender.\n- `Mempool::remove_transaction(hash)`, drop a transaction once it is in a block.\n- `Mempool::stats()`, size, gas statistics, and a per-class breakdown.\n\n### Transaction validator, `src/validator.rs`\n\n- `TxValidator::new(rules, state_provider)`, then `validate(tx)` or `validate_batch(txs)`.\n- `validate` runs, in order: address blacklist, rate limit, gas price and limit, data-size limit, signature (ed25519 and ECDSA), then balance and nonce (`src/validator.rs:188`).\n- `ValidationRules`, the configurable floor: minimum gas price, maximum gas limit, maximum data size, rate limits.\n- `ValidationPipeline`, splits a batch into valid and invalid.\n- `TxValidator::blacklist_address(addr)` and `unblacklist_address(addr)`.\n- `StateProvider` trait, async account lookups; `MockStateProvider` is test-only.\n\n### Block builder, `src/block_builder.rs`\n\nThe builder assembles the candidate block, plainly and in order: it takes the selected parent (the tip with the highest blue score) plus up to `max_parents - 1`, which is nine, merge parents from the consensus layer, pulls the top-priority transactions from the mempool, executes them, computes the state and receipt roots, sets the EIP-1559 base fee, and signs the result.\n\n- `BlockBuilder::new(config, mempool, proposer_key)`, with the proposer key held on the builder (`src/block_builder.rs:129`).\n- `BlockBuilder::with_executor(executor)`, attach the execution engine.\n- `BlockBuilder::build_block(selected_parent, merge_parents, parent_height, parent_blue_score, vrf_proof)`, produce the full candidate (`src/block_builder.rs:149`).\n- `BlockBuilderConfig`, maximum block size, gas limits, transaction bounds, and `block_time_target`, whose library default is two seconds (`src/block_builder.rs:77`). The testnet node configuration sets a one-second target, and the measured block interval on the testnet is about two seconds.\n\nParent selection itself belongs to the consensus layer: `ParentSelector` returns `(selected_parent, merge_parents)` for a new block. See [consensus](/chain/consensus) for how blue score picks the selected parent, and [the LVM](/chain/lvm) for how the transactions execute.\n\n## Design rationale\n\nTwo design choices stand out. The mempool sorts by class as well as price so the network does not let a fee war crowd out the work it exists to carry; compute and training traffic carry weight that an ordinary transfer does not. And the builder reuses the consensus layer's parent selection rather than inventing its own, so there is one definition of \"which parents\" across the codebase, not two that can drift apart. A block cadence of a few seconds keeps blocks frequent enough that priority work waits seconds, not minutes, while the BlockDAG absorbs the near-simultaneous blocks that a fast cadence produces.\n\n## Failure modes\n\n- A transaction that fails any validation check is set aside, not staged; an invalid signature, a stale nonce, a gas price below the floor, or a blacklisted sender each stop it at the door of this node's mempool. Signature and transaction hardening is in progress; see [SECURITY.md](https://github.com/CitrateNetwork/.github/blob/main/SECURITY.md).\n- The mempool is bounded. At capacity, low-priority transactions are evicted rather than allowed to exhaust memory, and per-sender caps stop one account from filling the pool. The system fails closed: it sheds the lowest-priority load rather than accepting unbounded work.\n- A built block carries computed state and receipt roots; a proposer cannot substitute arbitrary roots, because the receiving nodes recompute and reject a block whose roots do not match execution.\n\n## Access and canon\n\nPublic. Mempool behavior and validation rules are what a developer needs to submit transactions and reason about ordering and fees. No secrets appear here: proposer keys and VRF proofs are parameters, never documented values, and `MockStateProvider` is test scaffolding, not a production backend.\n\n## Source and verification\n\n- Source files: `core/sequencer/src/mempool.rs`, `src/validator.rs`, `src/block_builder.rs`.\n- Audited against SHA `9d5959e`.\n- Status: Implemented, pre external audit. The crate is internally tested, including property tests; it has not completed a third-party audit. The mempool denial-of-service surface (rate limiting, per-sender caps, eviction) is implemented but should be treated as pre-certification.\n"},"/chain/storage":{"slug":"/chain/storage","title":"Citrate Storage, State, RocksDB, Pruning, IPFS Pinning","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/core/storage/src/state_manager.rs, citrate-chain/core/storage/src/db/, citrate-chain/core/storage/src/chain/, citrate-chain/core/storage/src/state/, citrate-chain/core/storage/src/pruning/, citrate-chain/core/storage/src/ipfs/","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"RocksDB backend, `src/db/`","anchor":"rocksdb-backend-srcdb"},{"depth":3,"text":"Chain store, `src/chain/`","anchor":"chain-store-srcchain"},{"depth":3,"text":"State, `src/state/`, `src/state_manager.rs`","anchor":"state-srcstate-srcstate_managerrs"},{"depth":3,"text":"Pruning, `src/pruning/`","anchor":"pruning-srcpruning"},{"depth":3,"text":"IPFS model storage and pinning, `src/ipfs/`","anchor":"ipfs-model-storage-and-pinning-srcipfs"},{"depth":3,"text":"Caching, `src/cache/`","anchor":"caching-srccache"},{"depth":3,"text":"At-rest encryption","anchor":"at-rest-encryption"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Storage is where Citrate Network keeps what it has agreed on: the BlockDAG, account and AI state, and large model artifacts. This page gives developers and operators the mental model first, then the audited surface. It is the soil under the orchard; everything else grows on it.\n\n## What it is\n\nThe storage crate (`core/storage/`) is the persistence layer, and it coordinates three things. A RocksDB key-value backend is the durable store on disk. An in-process state model holds accounts, contract storage, code, and AI state, and computes a deterministic state root over them. An IPFS integration holds large off-chain model artifacts, with incentives for nodes that keep them pinned.\n\nState is flat, not a tree. Citrate Network does not use a Merkle-Patricia trie for the world state. State lives in flat RocksDB column families, and the state root is a hash over the sorted state. `StateManager::calculate_state_root()` computes an account root (accounts sorted by address), a storage root (storage sorted by address), and an AI root, then combines them as `SHA3-256(account_root || storage_root || ai_root)`. Because the inputs are sorted, every honest node with the same state computes the same root. This is a deliberate simplification, explained under design rationale below.\n\nThe top-level coordinator is `StorageManager` (`src/lib.rs`), built with `StorageManager::new(path, pruning_config)` for the default, no at-rest encryption, or `StorageManager::with_config(path, config)` for the full configuration including at-rest encryption. It owns the RocksDB handle, the block and transaction stores, the state store, the block and state caches, and the pruner. IPFS is a separate service, not a field of `StorageManager`.\n\n## How to use it\n\nFor an operator, storage is mostly configuration and then it runs itself.\n\n1. Choose a data path and a pruning policy, then construct a `StorageManager`.\n2. Start its background services; this spawns the auto-pruner on the configured interval.\n3. If you need at-rest encryption, supply an `EncryptionAtRestConfig` on the `StorageConfig` you pass to `with_config`; the key material is bound at construction, there is no separate runtime initialize step. Encryption is off by default.\n\n```rust\nuse std::sync::Arc;\nuse citrate_storage::{StorageManager, StorageConfig, PruningConfig};\nuse citrate_storage::crypto::at_rest::EncryptionAtRestConfig;\n\n// Default storage at a path, auto-pruning with defaults.\nlet storage = Arc::new(StorageManager::new(\"./data\", PruningConfig::default())?);\nstorage.clone().start_services().await; // spawns the auto-pruner (async, takes Arc)\n\n// Full config with at-rest encryption:\nlet cfg = StorageConfig::default()\n .with_encryption(EncryptionAtRestConfig::with_password(\"...operator-supplied passphrase...\"));\nlet storage = StorageManager::with_config(\"./data\", cfg)?;\n```\n\nFor storage paths, pruning, and the IPFS daemon in an operational setting, see [run a node](/operators/run-a-node).\n\n## Reference\n\n### RocksDB backend, `src/db/`\n\n- `RocksDB::open(path)` (`src/db/rocks_db.rs`) wraps `rocksdb::DB` and opens every column family with tuned options. Compression is LZ4 in production.\n- Read and write surface: `get_cf`, `put_cf`, `delete_cf`, `iter_cf`, `prefix_iter_cf`; batched writes commit with either `write_batch` (no fsync) or `write_batch_sync` (fsync). Atomic counters back a durability tripwire (`REM-2 / WP-H1.3`) so producer-path writes are verifiably synced.\n- Column families (`src/db/column_families.rs`) are grouped by role: blocks, headers, transactions, receipts; state, accounts, storage, code, account versions; AI models and training; the DAG; checkpoints; and metadata.\n\n### Chain store, `src/chain/`\n\n- `BlockStore` (`src/chain/block_store.rs`), `put_block` writes a block plus its parent-to-child DAG relations, height index, and blue set in one atomic batch. Latest height is cached for fast lookup; truncated or corrupt values are treated as missing (`SECREM-01 CONS-6`).\n- `TransactionStore` (`src/chain/transaction_store.rs`), batched, synced writes with a sender-nonce index, plus receipts.\n\n### State, `src/state/`, `src/state_manager.rs`\n\n- `StateStore` (`src/state/state_store.rs`) over RocksDB: `get/put_account`, `get/put_storage`, `delete_storage`, `get/put_code`, `get_all_accounts`, and `write_state_batch_sync` for finalized state. AI state is first-class: `get/put_model`, `get/put_training_job`.\n- `AIStateTree` (`src/state/ai_state.rs`), an in-memory map of models, training jobs, model-weight CIDs, an inference cache, and LoRA adapters, with `calculate_root()` over the AI sub-state.\n- `StateManager` (`src/state_manager.rs`) composes `StateStore` and `AIStateTree`, and `calculate_state_root()` derives the world-state root as `SHA3-256(account_root || storage_root || ai_root)` (`src/state_manager.rs:34`).\n\n### Pruning, `src/pruning/`\n\n`Pruner` (`src/pruning/pruner.rs`) with `PruningConfig` (`keep_blocks`, `keep_states`, `interval`, `batch_size`, `auto_prune`). `prune()` runs one cycle; `prune_blocks_before(height)` and `prune_states_before(height)` delete historical blocks and old state below a threshold; `compact()` triggers RocksDB compaction afterwards. `start_auto_pruning` runs the loop on the configured interval, spawned by `StorageManager::start_services()`.\n\n### IPFS model storage and pinning, `src/ipfs/`\n\n- `IPFSService` (`src/ipfs/mod.rs`), an HTTP client to an IPFS daemon: `store_model`, `retrieve_model`, `list_pinned_models`, `get_model_metadata`, `fetch_raw`. `IpfsDaemon` (`src/ipfs/daemon.rs`) manages a local kubo daemon.\n- Chunking (`src/ipfs/chunking.rs`): models above a size threshold are split into chunks, each addressed by a BLAKE3 hash; `ChunkManifest` records the per-chunk CIDs for reassembly.\n- Pinning incentives (`src/ipfs/pinning.rs`): `PinningManager` accounts for replica counts and accrued rewards per CID and per pinner; rewards scale by pinned bytes, model type, and duration. `PersistentPinRegistry` persists the registry.\n- `EncryptedIPFSStore` (`src/ipfs/encrypted_store.rs`), optional client-side encryption (AES-256-GCM, per-chunk nonce and tag) with an address-based access list; public metadata stays in the clear.\n\n### Caching, `src/cache/`\n\n`Cache` (`src/cache/lru_cache.rs`) is a thread-safe LRU. `StorageManager` keeps a hot block cache and state cache; `clear_caches()` flushes them.\n\n### At-rest encryption\n\nWhen enabled via `StorageConfig`, the storage layer applies a cipher-agile, post-quantum hybrid envelope (`src/crypto/`): a hybrid key exchange combining ML-KEM (CRYSTALS-Kyber) with X25519, feeding AES-256-GCM data encryption, to resist a harvest-now, decrypt-later attack. Per-column-family keys are derived from an Argon2id master key through HKDF-SHA3, with scheduled rotation. Key commitments, never key material, can be anchored on-chain for auditability (`src/crypto/key_commitment.rs`). It is off by default; enable it by setting `StorageConfig::with_encryption(EncryptionAtRestConfig::with_password(...))` before `StorageManager::with_config`.\n\n## Design rationale\n\nThe state root is computed by hashing sorted state with SHA3-256, not by a Merkle-Patricia trie, and that is a deliberate simplification. A trie buys you compact inclusion proofs, but it costs write concurrency: every write touches a path of internal nodes, and concurrent writers contend on them. Citrate Network carries AI workloads that write state in bulk, so we took the trade the other way. Flat column families let writes proceed in parallel and support multi-version concurrency, and a deterministic hash over sorted state still gives every node the same root to agree on. We do not advertise trie-proof structure, because we do not implement one; the page documents the mechanism that exists.\n\nDurability is the other deliberate choice. Producer-path writes commit with fsync and are checked by a tripwire counter, so a block a node claims to have stored is a block it has actually flushed to disk, not one sitting in a buffer that a crash could lose.\n\n## Failure modes\n\n- Truncated or corrupt stored values are treated as missing rather than parsed into bad state, so a damaged record fails closed at read time instead of poisoning consensus.\n- Producer-path writes are fsync'd and counted; the durability tripwire flags a path that writes without syncing, so a crash does not silently drop a block the node reported as stored.\n- At-rest encryption holds no key material on disk: the master key is operator-supplied at runtime, per-family keys are derived, key material is zeroized after use, and only key commitments, not keys, are ever anchored on-chain.\n\n## Access and canon\n\nPublic. The storage model, the state-root construction, and the reference are what a developer or operator needs to reason about persistence, pruning, and at-rest protection. No secrets appear here: no keys, passphrases, or private endpoints.\n\n## Source and verification\n\n- Source files: `core/storage/src/state_manager.rs`, `src/db/`, `src/chain/`, `src/state/`, `src/pruning/`, `src/ipfs/`, `src/crypto/`.\n- Audited against SHA `9d5959e`.\n- Status: Implemented, pre external audit. The crate is internally tested, including a durability and fsync suite; it has not completed a third-party audit. Several call sites carry remediation markers (for example `REM-2 / WP-H1.3` fsync enforcement, `SECREM-01 CONS-6` corrupt-data handling). Treat it as production-track but pre-certification.\n"},"/chain/tutorials/call-citrate-rpc":{"slug":"/chain/tutorials/call-citrate-rpc","title":"Call the Citrate RPC","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain core/api","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Step 1, confirm you are on Citrate","anchor":"step-1-confirm-you-are-on-citrate"},{"depth":3,"text":"Step 2, read the chain height","anchor":"step-2-read-the-chain-height"},{"depth":3,"text":"Step 3, read the BlockDAG","anchor":"step-3-read-the-blockdag"},{"depth":3,"text":"Step 4, read the network economics","anchor":"step-4-read-the-network-economics"},{"depth":3,"text":"Step 5, read an error on purpose","anchor":"step-5-read-an-error-on-purpose"},{"depth":3,"text":"Step 6, pick your next page","anchor":"step-6-pick-your-next-page"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"A short, copy-paste walkthrough of the JSON-RPC surface. You will confirm you are on Citrate, read the\nBlockDAG, and read the network economics, learning the request and response shape, the id field, and the two\nerror codes you will see most often. Every method here exists in `citrate-chain`, and every step is\nread-only, so it needs no account and no SALT.\n\n## What it is\n\nA tour of four real methods over plain HTTP. You learn the JSON-RPC envelope once, then reuse it for the\nrest of the [JSON-RPC reference](/chain/rpc). Nothing here writes state, so you can run it against any\nCitrate endpoint you can reach without risk.\n\n## How to use it\n\nYou will need a reachable Citrate JSON-RPC endpoint. A local node serves `http://127.0.0.1:8545`. If you do\nnot have one, use the [RPC sandbox](/sandboxes/rpc) instead: the same methods, in the browser. You will also\nwant `curl`, and `jq` for readable output.\n\nSet up a small helper so the steps stay short:\n\n```bash\nexport RPC=http://127.0.0.1:8545\n\nrpc () {\n curl -s \"$RPC\" -H 'content-type: application/json' \\\n -d \"{\\\"jsonrpc\\\":\\\"2.0\\\",\\\"id\\\":1,\\\"method\\\":\\\"$1\\\",\\\"params\\\":${2:-[]}}\"\n}\n```\n\nEvery request carries four fields: `jsonrpc` (always `\"2.0\"`), an `id` you choose to match the reply to the\nrequest, the `method` name, and a `params` array. The helper sets `id` to `1` and defaults `params` to an\nempty array. The reply echoes your `id` and returns either a `result` or an `error`.\n\n### Step 1, confirm you are on Citrate\n\n```bash\nrpc eth_chainId\n# {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x9d0c\"}\nprintf '%d\\n' 0x9d0c # 40204\n```\n\n`0x9d0c` is 40204, and 40204 is Citrate testnet. The reply echoes the `id` you sent and puts the chain id in\n`result` as a hex string. Anything other than `0x9d0c` means you are pointed at a different network.\n\n### Step 2, read the chain height\n\n```bash\nrpc chain_getHeight\n# {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":12345}\n```\n\n`chain_getHeight` returns a plain number, the current height of the selected chain. It takes no parameters,\nso the helper's empty array is correct. Height is one of two clocks on a BlockDAG; the other, blue score,\ncomes next.\n\n### Step 3, read the BlockDAG\n\n```bash\nrpc citrate_getDagStats | jq\n```\n\n```json\n{\n \"totalBlocks\": 12345,\n \"blueBlocks\": 11727,\n \"redBlocks\": 618,\n \"tipsCount\": 3,\n \"maxBlueScore\": 11800,\n \"currentTips\": [\"0x...\", \"0x...\", \"0x...\"],\n \"height\": 12345,\n \"ghostdagParams\": { \"k\": 18, \"maxParents\": 10, \"maxBlueScoreDiff\": 1000, \"pruningWindow\": 100000, \"finalityDepth\": 100 }\n}\n```\n\nTwo things to notice. `maxBlueScore` is the DAG's ordering clock, not `height`. And `tipsCount` above one is\nnormal: several tips can exist at once on a BlockDAG, and GhostDAG merges them into a single order. To go\ndeeper on tips and blocks, read [read the DAG](/chain/tutorials/read-the-dag); for the words themselves, the\n[primer](/start/primer). One honesty note: `currentTips`, `maxBlueScore`, `height`, and `ghostdagParams` are\nread from chain state, while `blueBlocks` and `redBlocks` are estimated from height, so treat the\nblue-and-red split as indicative.\n\n### Step 4, read the network economics\n\n```bash\nrpc citrate_getToken | jq\n# { \"name\": \"Citrate\", \"symbol\": \"SALT\", \"decimals\": 18, \"totalSupply\": \"0x...\", \"totalMinted\": \"0x...\" }\n```\n\nSALT has 18 decimals and a one-trillion cap. It is the unit fees and rewards are counted in, not a product. For\nthe live economic snapshot, blocks height, gas price, staked amount, and treasury, call\n`citrate_getEconomicState`:\n\n```bash\nrpc citrate_getEconomicState | jq\n# { \"blockHeight\": 12345, \"totalSupply\": \"0x...\", \"circulatingSupply\": \"0x...\", \"gasPrice\": \"0x...\",\n# \"stakedAmount\": \"0x...\", \"treasuryBalance\": \"0x...\", ... }\n```\n\n`citrate_getEconomicState` needs an economics manager configured on the node. Without one it returns\n`-32601 Method not found`, the same code you would see for a typo. Detail: [economics](/chain/economics).\n\n### Step 5, read an error on purpose\n\nSend a method that does not exist, then send a real method with the wrong parameter shape:\n\n```bash\nrpc citrate_thisIsNotAMethod\n# {\"jsonrpc\":\"2.0\",\"id\":1,\"error\":{\"code\":-32601,\"message\":\"Method not found\"}}\n\nrpc eth_getCode '[\"not-an-address\"]'\n# {\"jsonrpc\":\"2.0\",\"id\":1,\"error\":{\"code\":-32602,\"message\":\"Invalid params\"}}\n```\n\n`-32601` and `-32602` are the two errors you will meet most. The first says the node does not serve that\nmethod, the second says the method exists but your `params` were wrong. Both are standard JSON-RPC codes.\n\n### Step 6, pick your next page\n\n| If you want to | Go to |\n|---|---|\n| See every RPC method, with params and shapes | [JSON-RPC reference](/chain/rpc) |\n| Walk the tips and fetch a block | [read the DAG](/chain/tutorials/read-the-dag) |\n| Deploy a contract from the command line | [deploy with the CLI](/chain/tutorials/deploy-a-contract-with-the-cli) |\n| Understand the words you just saw | [the primer](/start/primer) |\n\n## Reference\n\nThe methods used above, with their source files in `citrate-chain`:\n\n| Method | What it returns | Source |\n|---|---|---|\n| `eth_chainId` | the chain id, `0x9d0c` | `core/api/src/eth_rpc.rs` |\n| `chain_getHeight` | the current height as a number | `core/api/src/server.rs` |\n| `citrate_getDagStats` | tips, blue score, GhostDAG params | `core/api/src/eth_rpc.rs` |\n| `citrate_getToken` | SALT name, decimals, supply | `core/api/src/economics_rpc.rs` |\n| `citrate_getEconomicState` | live economic snapshot | `core/api/src/economics_rpc.rs` |\n| `eth_getCode` | the code at an address | `core/api/src/eth_rpc.rs` |\n\n## Failure modes\n\n- **`Connection refused`** means no node is listening on `$RPC` (the default is `127.0.0.1:8545`). Use the\n [RPC sandbox](/sandboxes/rpc) instead.\n- **`-32601 Method not found`** is a typo, or a method the node does not serve. The economics methods,\n `citrate_getToken` and `citrate_getEconomicState`, need an economics manager configured; the chain and DAG\n methods do not.\n- **`-32602 Invalid params`** means the method exists but your `params` shape was wrong. Check it against the\n [reference](/chain/rpc).\n- **A chain id other than `0x9d0c`** means you are not on Citrate.\n\n## Access and canon\n\nPublic and read-only. No keys or credentials are needed, and nothing here writes state. Chain id 40204 is\ntestnet. The example outputs are illustrative; exact values depend on the node's current state.\n\n## Source and verification\n\nMethods verified against `citrate-chain` at `9d5959e`: `eth_chainId` and `eth_getCode` in\n`core/api/src/eth_rpc.rs`, `chain_getHeight` in `core/api/src/server.rs`, `citrate_getDagStats` in\n`core/api/src/eth_rpc.rs`, and `citrate_getToken` and `citrate_getEconomicState` in\n`core/api/src/economics_rpc.rs`. The full surface is on the [JSON-RPC reference](/chain/rpc). Status:\nImplemented (testnet 40204), pre external audit.\n"},"/chain/tutorials/deploy-a-contract-with-the-cli":{"slug":"/chain/tutorials/deploy-a-contract-with-the-cli","title":"Deploy a contract with the CLI","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain cli/src/commands/contract.rs","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Step 1, build the tool","anchor":"step-1-build-the-tool"},{"depth":3,"text":"Step 2, initialize the config","anchor":"step-2-initialize-the-config"},{"depth":3,"text":"Step 3, confirm the chain id","anchor":"step-3-confirm-the-chain-id"},{"depth":3,"text":"Step 4, prepare an account and the artifact","anchor":"step-4-prepare-an-account-and-the-artifact"},{"depth":3,"text":"Step 5, deploy the contract","anchor":"step-5-deploy-the-contract"},{"depth":3,"text":"Step 6, verify the deployment with an eth_ call","anchor":"step-6-verify-the-deployment-with-an-eth_-call"},{"depth":3,"text":"Step 7, read and call the contract","anchor":"step-7-read-and-call-the-contract"},{"depth":3,"text":"Step 8, pick your next page","anchor":"step-8-pick-your-next-page"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"A copy-paste walkthrough of deploying compiled bytecode with the `citrate` command-line tool. You will\nconfirm the chain id, point the tool at your endpoint, deploy a contract, then verify the deployment with a\nread-only `eth_` call. The deploy step writes state, so it needs an account with a little SALT; the verify\nstep does not.\n\n## What it is\n\n`citrate` is the command-line tool built from `citrate-chain`. Its contract operations live under the\n`contract` subcommand, which submits an `eth_sendTransaction` to your endpoint, waits for the receipt, and\nprints the new address. Under the surface it speaks the same JSON-RPC you met in\n[call the Citrate RPC](/chain/tutorials/call-citrate-rpc), so anything the tool does you could also do by\nhand. For the wider command set, see the [CLI reference](/chain/cli).\n\n## How to use it\n\nYou will need a clone of `citrate-chain` with a Rust toolchain, a reachable JSON-RPC endpoint (a local node\non `http://localhost:8545`, or a testnet endpoint), and a compiled contract artifact. This tutorial assumes a\nhex bytecode file named `MyToken.bin`; produce one with your usual compiler, for example `solc --bin`.\n\n### Step 1, build the tool\n\n```bash\ncargo build --release -p citrate-cli\n# binary at target/release/citrate\nexport PATH=\"$PWD/target/release:$PATH\"\n```\n\n### Step 2, initialize the config\n\n```bash\ncitrate init\n```\n\nThe defaults, from `cli/src/config.rs`, are RPC `http://localhost:8545`, chain id 40204, keystore\n`~/.citrate/keystore`, gas price 1 gwei, and gas limit 3,000,000. Override the endpoint for any command with\nthe global `--rpc ` flag or the `CITRATE_RPC` environment variable.\n\n### Step 3, confirm the chain id\n\nBefore you write anything, confirm the endpoint is Citrate testnet. The tool stores chain id 40204 by\ndefault; check the live endpoint agrees with the same RPC call as the other tutorials:\n\n```bash\ncurl -s http://localhost:8545 -H 'content-type: application/json' \\\n -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_chainId\",\"params\":[]}'\n# {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x9d0c\"}\nprintf '%d\\n' 0x9d0c # 40204\n```\n\n`0x9d0c` is 40204. Anything else means you are about to deploy to a different network.\n\n### Step 4, prepare an account and the artifact\n\nCreate a deployer account and note the address it prints:\n\n```bash\ncitrate account create\n# Enter a keystore password when prompted.\n# Prints: Address: 0x...\n\ncitrate account list\n```\n\nThe deploy transaction spends gas, so the account needs a little SALT. On a network with a faucet, fund the\naddress through that network's faucet, then confirm the balance:\n\n```bash\ncitrate account balance 0xYOUR_ADDRESS\n```\n\nThe faucet is a network service, not a `citrate` subcommand, so its exact address and request shape vary by\ndeployment; check the endpoint operator's notes. The artifact itself is the `MyToken.bin` hex file from your\ncompiler. A file that does not end in `.wasm` is read as hex bytecode; a `.wasm` file is read as raw bytes.\n\n### Step 5, deploy the contract\n\n```bash\ncitrate contract deploy ./MyToken.bin \\\n --account 0xYOUR_ADDRESS \\\n --value 0\n```\n\nThe tool submits `eth_sendTransaction`, waits for the receipt, and prints the new contract address. Save it:\n\n```bash\nexport CONTRACT=0xDEPLOYED_CONTRACT_ADDRESS\n```\n\nConstructor arguments, if your contract takes any, go in `--args ''`. The tool's encoder is a\nstatic-types subset: it handles `address`, `bool`, `bytes32`, and `uint8` through `uint256`. Dynamic `string`\nand `bytes` arguments are not yet supported (`encode_method_call` in `cli/src/commands/contract.rs`).\n\n### Step 6, verify the deployment with an eth_ call\n\nConfirm the code actually landed at the address. `contract code` wraps `eth_getCode`, which is read-only and\nneeds no account:\n\n```bash\ncitrate contract code \"$CONTRACT\"\n# Contract code retrieved\n# Size: ... bytes\n```\n\nYou can confirm the same thing directly over RPC, which is the call the tool makes:\n\n```bash\ncurl -s http://localhost:8545 -H 'content-type: application/json' \\\n -d \"{\\\"jsonrpc\\\":\\\"2.0\\\",\\\"id\\\":1,\\\"method\\\":\\\"eth_getCode\\\",\\\"params\\\":[\\\"$CONTRACT\\\",\\\"latest\\\"]}\"\n# a non-\"0x\" result means code is present\n```\n\nTo inspect the deploy transaction itself, fetch its receipt. The tool polls `eth_getTransactionReceipt`\ninternally while it waits; you can read the same receipt with `citrate network transaction 0xTX_HASH`.\n\n### Step 7, read and call the contract\n\n`contract read` uses `eth_call`, so it costs nothing and needs no account:\n\n```bash\ncitrate contract read \"$CONTRACT\" \"totalSupply()\"\n\ncitrate contract read \"$CONTRACT\" \"balanceOf(address)\" \\\n --args '[\"0xYOUR_ADDRESS\"]'\n```\n\n`contract call` sends a state-changing transaction, waits for the receipt, and reports gas used and any\nemitted event topics:\n\n```bash\ncitrate contract call \"$CONTRACT\" \"transfer(address,uint256)\" \\\n --args '[\"0x1111111111111111111111111111111111111111\", \"1000\"]' \\\n --account 0xYOUR_ADDRESS\n```\n\n### Step 8, pick your next page\n\n| If you want to | Go to |\n|---|---|\n| The full CLI command set | [CLI reference](/chain/cli) |\n| The RPC the tool speaks underneath | [call the Citrate RPC](/chain/tutorials/call-citrate-rpc) |\n| Every RPC method, with params and shapes | [JSON-RPC reference](/chain/rpc) |\n| The consensus your transaction commits to | [consensus, GhostDAG](/chain/consensus) |\n\n## Reference\n\nThe commands used above, with their source in `citrate-chain`:\n\n| Command | What it does | Source |\n|---|---|---|\n| `citrate init` | write the default config and keystore directory | `cli/src/config.rs` |\n| `citrate account create` / `list` / `balance` | manage and read deployer accounts | `cli/src/commands/account.rs` |\n| `citrate contract deploy --account --value --args` | submit `eth_sendTransaction`, await receipt, print address | `cli/src/commands/contract.rs` |\n| `citrate contract code
` | read code via `eth_getCode` | `cli/src/commands/contract.rs` |\n| `citrate contract read
--args` | read state via `eth_call` | `cli/src/commands/contract.rs` |\n| `citrate contract call
--account --args` | send a state-changing transaction | `cli/src/commands/contract.rs` |\n| `citrate network transaction ` | read a transaction receipt | `cli/src/commands/network.rs` |\n\nThe global `--rpc ` flag and the `CITRATE_RPC` environment variable override the endpoint for any\ncommand (`cli/src/main.rs`). Deploy and call both poll `eth_getTransactionReceipt` while they wait\n(`wait_for_receipt` in `cli/src/commands/contract.rs`).\n\n## Failure modes\n\n- **`No account specified and no default account configured`** means you passed no `--account` and set no\n default. Pass `--account 0x...`.\n- **`Transaction receipt not found after 60 seconds`** means the deploy or call transaction did not confirm\n in the poll window. Check the node is producing blocks and the account has SALT for gas.\n- **`Contract deployment failed`** with no contract address means the transaction reverted or ran out of gas;\n the default gas limit is 3,000,000, raised through the config.\n- **`dynamic types (string, bytes) are not supported`** comes from the CLI encoder. Use a contract method\n whose arguments are the supported static types, or encode the call data yourself and deploy raw.\n- **`Connection refused`** means no node is listening on the endpoint. Point `--rpc` at a reachable one.\n\n## Access and canon\n\nPublic. The read steps (`contract code`, `contract read`, `eth_getCode`) write nothing and need no account.\nDeploying and calling write state and spend gas, so they need an account with SALT. Chain id 40204 is\ntestnet; confirm the live endpoint reports `0x9d0c` before you deploy anything you care about. Keys live in\nthe local keystore at `~/.citrate/keystore`; the Citrate Keyring, not a hosted service, holds them.\n\n## Source and verification\n\nCommands verified against `citrate-chain` at `9d5959e`. Contract operations are in\n`cli/src/commands/contract.rs`, with the subcommands `deploy`, `call`, `read`, `code`, `verify`, `verify-get`,\nand `verify-list`. Config defaults are in `cli/src/config.rs`, the global `--rpc` flag and the top-level\nsubcommands are in `cli/src/main.rs`, account operations are in `cli/src/commands/account.rs`, and the\ntransaction lookup is in `cli/src/commands/network.rs`. The faucet is a network service and has no `citrate`\nsubcommand, so its address and request shape are described generically above. Status: Implemented (testnet\n40204), pre external audit. The CLI ABI encoder is a static-types subset.\n"},"/chain/tutorials/read-the-dag":{"slug":"/chain/tutorials/read-the-dag","title":"Read the DAG","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain core/api","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Step 1, read the whole DAG in one call","anchor":"step-1-read-the-whole-dag-in-one-call"},{"depth":3,"text":"Step 2, blue score versus height","anchor":"step-2-blue-score-versus-height"},{"depth":3,"text":"Step 3, list the tips, then fetch one","anchor":"step-3-list-the-tips-then-fetch-one"},{"depth":3,"text":"Step 4, pick your next page","anchor":"step-4-pick-your-next-page"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"A copy-paste walkthrough that lets you see the BlockDAG instead of reading about it. You will read the whole\nDAG in one call, list the current tips, fetch a single tip block, and learn why blue score, not height, is\nthe clock that orders the ledger. Every method here is read-only, so it needs no account and no SALT.\n\n## What it is\n\nA Citrate ledger is a DAG with more than one leaf, not a single chain. GhostDAG (k = 18, finality depth 100)\ntakes those leaves and merges them into one agreed order, and the number it counts by is the blue score. This\ntutorial reads that state live. If the words are new, read the [primer](/start/primer) first; for the\nprotocol behind the numbers, read [consensus, GhostDAG](/chain/consensus).\n\n## How to use it\n\nYou will need a reachable Citrate JSON-RPC endpoint. A local node serves `http://127.0.0.1:8545`. If you do\nnot have one, use the [RPC sandbox](/sandboxes/rpc) instead. You will also want `curl`, and `jq` for readable\noutput.\n\nSet up the same helper used in [call the Citrate RPC](/chain/tutorials/call-citrate-rpc):\n\n```bash\nexport RPC=http://127.0.0.1:8545\n\nrpc () {\n curl -s \"$RPC\" -H 'content-type: application/json' \\\n -d \"{\\\"jsonrpc\\\":\\\"2.0\\\",\\\"id\\\":1,\\\"method\\\":\\\"$1\\\",\\\"params\\\":${2:-[]}}\"\n}\n```\n\n### Step 1, read the whole DAG in one call\n\n`citrate_getDagStats` returns tips, height, the head's blue score, and the network's GhostDAG parameters in a\nsingle response:\n\n```bash\nrpc citrate_getDagStats | jq\n```\n\n```json\n{\n \"totalBlocks\": 12345,\n \"blueBlocks\": 11727,\n \"redBlocks\": 618,\n \"tipsCount\": 3,\n \"maxBlueScore\": 11800,\n \"currentTips\": [\"0x...\", \"0x...\", \"0x...\"],\n \"height\": 12345,\n \"ghostdagParams\": { \"k\": 18, \"maxParents\": 10, \"maxBlueScoreDiff\": 1000, \"pruningWindow\": 100000, \"finalityDepth\": 100 }\n}\n```\n\nWhat you are looking at:\n\n- `currentTips` and `tipsCount`, the DAG's current leaf blocks. More than one tip at once is normal on a\n BlockDAG; GhostDAG orders them deterministically.\n- `maxBlueScore`, the blue score of the highest tip, the head the network builds on. This is the ordering\n clock, not `height`.\n- `ghostdagParams`, the live consensus constants: `k` is 18, `maxParents` is 10, `finalityDepth` is 100. These\n come from `GhostDagParams::default()` in `core/consensus/src/types.rs`.\n\nOne honesty note: `currentTips`, `maxBlueScore`, `height`, and `ghostdagParams` are read from chain state,\nwhile `blueBlocks` and `redBlocks` are estimated from height (the handler says so in a comment). Treat the\nblue-and-red split as indicative, not exact.\n\n### Step 2, blue score versus height\n\nHeight counts how deep a block sits along the selected chain. Blue score counts how many blue (well-connected,\nhonest-looking) blocks precede a block across the whole DAG. Tip selection follows the highest blue score, so\nwhen you ask which tip the network is building on, `maxBlueScore` is the answer, and the block with the\nhighest blue score is the head. Two tips can sit at the same height yet have different blue scores; the one\nwith the larger blue score wins. That is why we call blue score the ordering clock.\n\n### Step 3, list the tips, then fetch one\n\nGet the tips on their own:\n\n```bash\nrpc chain_getTips | jq\n# [\"0x...\", \"0x...\", \"0x...\"]\n```\n\n`chain_getTips` returns an array of tip hashes. Take the first and fetch its block by hash with\n`chain_getBlock`, whose parameter is a `BlockId` (here the `Hash` form):\n\n```bash\nTIP=$(rpc chain_getTips | jq -r '.result[0]')\n\nrpc chain_getBlock \"[{\\\"Hash\\\":\\\"$TIP\\\"}]\" | jq\n```\n\nThe block's header carries `blue_score`, the same value GhostDAG uses for tip selection, alongside\n`selected_parent_hash` and `merge_parent_hashes`. Those merge parents are the other tips this block absorbed,\nthe act of merging that makes a DAG a DAG rather than a chain. You can also read the height alone:\n\n```bash\nrpc chain_getHeight\n# {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":12345}\n```\n\n### Step 4, pick your next page\n\n| If you want to | Go to |\n|---|---|\n| The protocol behind these numbers | [consensus, GhostDAG](/chain/consensus) |\n| Every RPC method, with params and shapes | [JSON-RPC reference](/chain/rpc) |\n| The request envelope and error codes | [call the Citrate RPC](/chain/tutorials/call-citrate-rpc) |\n| The words: tips, blue score, merge | [the primer](/start/primer) |\n\n## Reference\n\nThe methods used above, with their source files in `citrate-chain`:\n\n| Method | What it returns | Source |\n|---|---|---|\n| `citrate_getDagStats` | tips, blue score, GhostDAG params | `core/api/src/eth_rpc.rs` |\n| `chain_getTips` | an array of tip hashes | `core/api/src/server.rs` |\n| `chain_getBlock` | one block, by `BlockId` (`{\"Hash\":\"0x...\"}`) | `core/api/src/server.rs` |\n| `chain_getHeight` | the current height as a number | `core/api/src/server.rs` |\n\nGhostDAG constants (`k = 18`, `maxParents = 10`, `finalityDepth = 100`) come from `GhostDagParams::default()`\nin `core/consensus/src/types.rs`.\n\n## Failure modes\n\n- **`Connection refused`** means no node is listening on `$RPC` (the default is `127.0.0.1:8545`). Use the\n [RPC sandbox](/sandboxes/rpc) instead.\n- **`chain_getBlock` returns `null`** when no block matches the `BlockId`. Check the hash, and that the\n `params` shape is `[{\"Hash\":\"0x...\"}]` and not a bare string.\n- **`-32602 Invalid params`** means the `BlockId` shape was wrong. The form above is the one the handler\n parses.\n- **A blue-and-red split that looks off** is expected: those two fields are estimated from height, per the\n note in Step 1.\n\n## Access and canon\n\nPublic and read-only. No keys, no write methods, no private endpoints, and nothing here writes state. Chain id\n40204 is testnet, and `http://127.0.0.1:8545` is the conventional local address, not a live network endpoint.\n\n## Source and verification\n\nMethods verified against `citrate-chain` at `9d5959e`: `citrate_getDagStats` in `core/api/src/eth_rpc.rs`,\nand `chain_getTips`, `chain_getBlock`, and `chain_getHeight` in `core/api/src/server.rs`. The GhostDAG\nconstants are `GhostDagParams::default()` in `core/consensus/src/types.rs`. Status: Implemented (testnet\n40204), pre external audit. The `blueBlocks` and `redBlocks` fields of `citrate_getDagStats` are estimates,\nas flagged above.\n"},"/compute/agent-runtime":{"slug":"/compute/agent-runtime","title":"Citrate Agent Runtime","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-agent-runtime (agent/cli/, agent/core/, agent-cron/, agent-chain/)","syncedSha":"f161e69","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"The diagnostic, `citrate-agent doctor`","anchor":"the-diagnostic-citrate-agent-doctor"},{"depth":3,"text":"The recorder, `RecorderClient`","anchor":"the-recorder-recorderclient"},{"depth":3,"text":"The approval gate, `ApprovalQueue`","anchor":"the-approval-gate-approvalqueue"},{"depth":3,"text":"The cron daemon and tripwires","anchor":"the-cron-daemon-and-tripwires"},{"depth":3,"text":"The capsule loader","anchor":"the-capsule-loader"},{"depth":3,"text":"Environment variables","anchor":"environment-variables"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The agent runtime is how an agent runs on an operator's own machine without acting unsupervised.\nIt records every action in a tamper-evident log, holds risky tool calls behind a human approval\ngate, and ships a diagnostic that refuses to clear a node when those safeguards are missing. It\nis for researchers and operators studying how agents can act on the network while staying\naccountable.\n\n## What it is\n\nThe safety posture is the whole point, so it comes first. An agent here does not run free. Three\nthings hold at once. Every action it takes is written to an append-only audit chain, where each\nrecord carries the hash of the one before it, so a deleted or altered record breaks the chain\nand shows. Every tool call the agent wants to make passes through an approval queue, and the\ncalls that matter wait for a human, or a quorum of humans, to sign off before they run. And the\nwhole thing runs on the operator's own hardware, the on-premise default that holds across the\nnetwork; the only writes that reach the public ledger are the ones the runtime is told to anchor.\n\nThe runtime is several crates in the `citrate-agent-runtime` workspace. The pieces that carry\nthe safety story are these.\n\n- A diagnostic, run as `citrate-agent doctor`, that checks the safeguards are intact and emits a\n signed report.\n- A recorder, `RecorderClient`, the single surface that signs and writes to the chain. Nothing\n else holds the key.\n- An approval queue, `ApprovalQueue`, the HIC (Human In Control) gate that risky tool calls pass\n through.\n- A cron daemon that runs recurring checks, the tripwires and standing procedures, on a\n schedule, and records what they find.\n- A capsule loader that verifies an agent's signed package against its manifest before it runs,\n and grants it only the capabilities the manifest declares.\n\n## How to use it\n\n1. Configure the chain dependency before you build. The runtime pulls a chain crate over an SSH\n host alias, `github-citrate-chain`, that must resolve in your `~/.ssh/config`. The simplest\n path is to run `citrate-federation/scripts/bootstrap.sh`, which sets the alias up for you. A\n fresh machine without it fails the build with `Could not resolve hostname\n github-citrate-chain`.\n2. Build the workspace. `cargo build --release`, then `cargo run --release --bin\n citrate-agent -- --help` to confirm the CLI. The package is `citrate-agent-cli`; the binary it\n produces is named `citrate-agent`.\n3. Write a `doctor.toml` naming the agent and pointing at the audit log and the policy files you\n want checked.\n4. Run the diagnostic against a node before you trust it. `citrate-agent doctor` exits 0 on pass\n or warn, 1 on a blocker, and 2 on a configuration or IO error before it could run.\n5. For the recorder and the cron daemon, supply the signing key from your own secret store\n through the environment. The key is never a value on this page or in the tree.\n\n```bash\ncitrate-agent doctor --config doctor.toml --output report.toml\n```\n\n```toml\n# doctor.toml, minimal\n[doctor]\nagent_did = \"did:citrate:agent:0x...\"\n```\n\n## Reference\n\n### The diagnostic, `citrate-agent doctor`\n\nThe command lives in `agent/cli/src/doctor_cmd.rs`; its config schema is in\n`agent/cli/src/config.rs`. It runs eleven checks defined in `agent/core/src/doctor/checks.rs`,\neach returning Pass, Skipped, Warn, or Blocker (`agent/core/src/doctor/report.rs`); a skipped\nrequired check holds the overall result to at least Warn rather than a clean Pass. With a seed\nfile it signs the report. The flags are `--config`, `--output`, `--check ` to run a subset, and\n`--seed` for the signing key.\n\n| Check | Behavior |\n|---|---|\n| `audit-chain-integrity` | Walks the audit hash-chain. Blocker if a record was altered or deleted. |\n| `audit-file-permissions` | Warn if the audit file is not mode 0600 on Unix. |\n| `approval-queue-depth` | Warn when pending approvals pass a threshold, default 100. |\n| `pending-break-glass` | Blocker if a break-glass case is surfaced, Warn if it is unaffirmed. |\n| `runtime-presence` | Warn if no async runtime is present. |\n| `capsule-manifest-reverify` | Blocker if a capsule fails to load, Warn on a bad signature. |\n| `wasm-linker-recheck` | Blocker on a linker or interface mismatch. |\n| `policy-bundle-hash` | Blocker if a policy file's SHA-256 has drifted. |\n| `tla-spec-ci-status` | Blocker on failing specs, Warn if the status is stale past the window. |\n| `retention-age` | Warn if the audit file is older than the retention maximum, default 90 days. |\n| `anchor-reconciliation` | Reports on-chain roots that have not been anchored. |\n\n### The recorder, `RecorderClient`\n\nIn `agent/core/src/audit/recorder.rs`. It is the only surface that signs on-chain writes: it\nowns a secp256k1 key, an RPC client, and the chain id 40204. It loads its key with `from_env`,\nwhich reads `DEPLOYER_PRIVATE_KEY`, or, when that is unset, a key file named by\n`CITRATE_RECORDER_KEY_ENV_FILE`; that path must be absolute and pass a mode-0600 check. The\nearlier gitignored `.env.testnet` fallback was removed (AR-B-010). Its writes go through\n`send_tx` and `wait_for_receipt`. Every approve or reject the runtime makes is written\nas a decision record to the on-chain `AgentDecisionRegistryV2`. The audit chain itself is\n`AuditChain` in `agent/core/src/audit/chain.rs`, which mints a genesis record, appends each new\nrecord with a contiguity check against the previous hash, and can walk the whole chain to verify\nits integrity. Records are written through an `AuditSink`; the filesystem sink and the\nchain-anchor sink are present, the object-store and write-once sinks are not yet shipped.\n\n### The approval gate, `ApprovalQueue`\n\nIn `agent/core/src/hitl/mod.rs`. Tool calls enter the queue and wait. Low-risk calls can be\nauto-granted on a fast path with a time-to-live, default 30 minutes; a pending call that no one\nanswers times out, default 5 minutes. The risk tier of a call decides how many signatures it\nneeds (`agent/core/src/hitl/quorum.rs`): low auto-approves, medium needs one signer from the\nrequired set, high needs two, and critical needs a fixed set of officer roles. Separation of\nduties is enforced in `agent/core/src/hitl/roles.rs`: the Auditor role can never approve, the\nCompliance Officer and Security Officer roles cannot both stand for the same approval, and no\nsigner counts twice. Signatures are verified against a canonical payload and a known signer\nroster (`agent/core/src/hitl/signing.rs`); a production build fails closed if no roster is set.\n\nThere is a break-glass path in `agent/core/src/hitl/break_glass.rs` for emergencies, and it is\nbuilt to be hard to abuse: an invocation notifies all roles, must be affirmed by a quorum within\na 72-hour window, and is blocked outright for ITAR-classed actions. Its state machine and the\naudit chain's integrity property are both checked in TLA+.\n\n### The cron daemon and tripwires\n\nIn `agent-cron/`. A `CronScheduler` (`agent-cron/src/scheduler.rs`) runs recurring jobs on cron\nschedules, each carrying a snapshot of the capabilities it was granted. A standing-procedure\nengine (`agent-cron/src/sop.rs`) runs multi-step procedures on a trigger. The tripwire daemon\n(`agent-cron/src/bin/tripwire_daemon.rs`) runs a set of compliance tripwires that read metrics\nfrom Prometheus and events from the chain, and fire through the recorder when a threshold is\ncrossed. Its environment is below.\n\n### The capsule loader\n\nIn `agent/core/src/capsule/`. An agent ships as a capsule: a signed archive with a manifest. The\nloader (`mod.rs`) verifies the content hash, checks the publisher's signature against a key\nregistry keyed by signing tier (`tiers.rs`), cross-checks the declared capabilities against the\ncomponent's interface (`verify.rs`), and builds a linker that exposes only the capabilities the\nmanifest declares, failing closed when an undeclared import is needed. The manifest\n(`manifest.rs`) declares the capsule's data classes, its risk tier, the roles required to\napprove it, and whether it is break-glass eligible.\n\n### Environment variables\n\n| Variable | Default | Required | Purpose |\n|---|---|---|---|\n| `DEPLOYER_PRIVATE_KEY` | none | tripwire daemon: yes | secp256k1 key for `RecorderClient`. Never commit it. |\n| `CITRATE_RECORDER_KEY_ENV_FILE` | none | no | Absolute path to a mode-0600 key file, read when `DEPLOYER_PRIVATE_KEY` is unset. |\n| `CITRATE_TRIPWIRE_PROM_URL` | `http://127.0.0.1:9090` | no | Prometheus base for tripwire metrics. |\n| `CITRATE_TRIPWIRE_RPC_URL` | `https://rpc.citrate.ai` | no | JSON-RPC for chain queries. |\n| `CITRATE_TRIPWIRE_REGISTRY` and the `_TENANT`, `_ROLE_ESCALATION`, `_MULTISIG` addresses | contract defaults | no | Tripwire contract addresses. |\n| `CITRATE_TRIPWIRE_SCOPE` | `keccak256(\"defense_prime-root\")` | no | bytes32 scope. |\n| `CITRATE_CAPSULE_SIGNING_SEED` | none | no | ed25519 seed for capsule packing. Never commit it. |\n\n## Design rationale\n\nThe runtime puts the key in exactly one place on purpose. `RecorderClient` is the only thing\nthat can sign an on-chain write, so the audit chain and the approval gate cannot be bypassed by\nsome other code path signing its own transaction; if it went to the chain, it went through the\nrecorder, and it is in the log. The audit log is a hash-chain rather than a plain file so that\ntampering is detectable rather than silent, and the diagnostic treats a broken chain as a\nblocker, not a warning. The approval gate scales the number of human signatures to the risk of\nthe call rather than asking for approval on everything, which is what keeps the gate usable\ninstead of ignored. The cost of all this is that the agent is slower and more bounded than one\nthat simply acts; for actions that change state on a shared network, that is the trade we want.\n\n## Failure modes\n\nThe runtime is built to fail closed. A production build with no signer roster will not start the\nrole-aware approval path; it refuses rather than approving on trust. An undeclared capability in\na capsule fails instantiation rather than being granted quietly. A break-glass invocation for an\nITAR-classed action is blocked outright, and any break-glass case that is surfaced turns the\ndiagnostic into a blocker. The recorder's key custody is honest about its limits: loading the\nkey from an environment variable is pilot-grade, fit for testnet and controlled pilots, and is\nthe part most in need of hardening before a stable release. If you ever find a real key in the\ntree, flag it; do not transcribe it.\n\n## Access and canon\n\nTier academic: this surface is oriented to research and formal methods, the TLA+ checks on the\naudit chain and the break-glass machine, capsule capability verification, and the diagnostic\nitself. The runtime is classified for a full external audit before any v1.0.0 tag\n(`AUDIT_TIER.md`); there is no stable release without a written attestation against an exact\ncommit. Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity. No\nsecrets appear here: `DEPLOYER_PRIVATE_KEY` and `CITRATE_CAPSULE_SIGNING_SEED` are named only as\nvariables to set, and any key file named by `CITRATE_RECORDER_KEY_ENV_FILE` must be an absolute\npath at mode 0600, held in your own secret store.\n\nThis page connects to [run a node](/operators/run-a-node) for the operator who hosts the\nruntime, to [research](/research/learning) for the agent-safety work behind it, and to the\n[governance contracts](/contracts/governance), where the recorder writes its decisions to the\n`AgentDecisionRegistryV2`.\n\n## Source and verification\n\n- Source repo: `citrate-agent-runtime`.\n- Files: `agent/cli/src/doctor_cmd.rs`, `agent/cli/src/config.rs`,\n `agent/core/src/doctor/{checks.rs,report.rs}`, `agent/core/src/audit/{recorder.rs,chain.rs}`,\n `agent/core/src/hitl/{mod.rs,quorum.rs,roles.rs,signing.rs,break_glass.rs}`,\n `agent/core/src/capsule/{mod.rs,manifest.rs,tiers.rs,verify.rs}`,\n `agent-cron/src/{scheduler.rs,sop.rs,bin/tripwire_daemon.rs}`, `AUDIT_TIER.md`.\n- Audited against SHA: `f161e69`.\n- Status by component:\n - `doctor` and its eleven checks, `RecorderClient`, `AuditChain`, `ApprovalQueue`, the quorum\n and role rules, break-glass, the cron scheduler, the standing-procedure engine, the tripwire\n daemon, and the capsule loader: Implemented.\n - The audit-chain integrity property, the approval state machine, and the break-glass state\n machine: Verified in TLA+.\n - `RecorderClient` key custody (environment-loaded key, no nonce cache): Implemented but\n pilot-grade, flagged for hardening.\n - Object-store and write-once audit sinks, and hardware-backed signing surfaces: Specified,\n not yet shipped.\n - Full external audit before v1.0.0: required, not yet performed; pre-stable (v0.x).\n"},"/compute/gateway":{"slug":"/compute/gateway","title":"Deploy the inference gateway","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-inference-gateway/gateway/src/, citrate-inference-gateway/crates/x402-axum/src/, citrate-inference-gateway/gateway/RUNBOOK.md","syncedSha":"603fe92","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"Run modes and where they live","anchor":"run-modes-and-where-they-live"},{"depth":3,"text":"Routes the operator runs","anchor":"routes-the-operator-runs"},{"depth":3,"text":"Configuration","anchor":"configuration"},{"depth":3,"text":"The x402 settlement path, from the operator's side","anchor":"the-x402-settlement-path-from-the-operators-side"},{"depth":3,"text":"Pieces that are designed but not complete at this SHA","anchor":"pieces-that-are-designed-but-not-complete-at-this-sha"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This is the operator's side of the Citrate Inference Gateway: how you deploy it on your own hardware, how\nyou configure it, and how a paid request settles. If you are an application developer who wants to call the\ngateway rather than run one, read the [client reference](/sdks/inference-gateway) instead; this page does\nnot repeat the request and response shapes that live there.\n\n> **Status: paid routes are not deployed.** The released gateway binary starts with `build_router`, which\n> serves only the free read routes. The x402-paid and API-key routes below are mounted by\n> `build_router_with_auth`, which today is used only by tests, so no public gateway settles paid calls yet.\n> This page documents the design and the code so operators can review it; it is not a live service.\n\n## What it is\n\nThe gateway is an OpenAI-compatible HTTP service that fronts Citrate Market on the Citrate Network, chain\nid 40204. An operator runs it on their own machine, in front of either the marketplace or a resident\ninference server, and it turns an OpenAI-shaped request into a metered, paid job. The work it performs is\nsettled in SALT per request over the x402 handshake; SALT settles the work, it is not something the gateway\nholds for you.\n\nIt runs in one of two modes, chosen at start by `CITRATE_GATEWAY_MODE`.\n\n- **Marketplace mode**, the default. The gateway reads the on-chain `ModelRegistry`, `ComputePricingOracle`,\n and `InferenceRouter`, selects a provider for the requested model, and gates the paid routes behind x402.\n This is the mode an operator selling into the marketplace runs.\n- **Local-proxy mode.** A leaner build that fronts a resident inference server on a single machine, for\n example a llama-server behind your own reverse proxy. It is gated by a `cgk_` API key rather than x402 and\n makes no chain calls. It exists so a single on-premise node can serve the same OpenAI surface without\n joining the marketplace.\n\nThe gateway is the thin layer between an OpenAI request and the marketplace. It holds no model weights and\nruns no inference itself; the providers it dispatches to are the [operators selling compute](/compute/node-agent),\nand the price it quotes comes from the [x402 pricing contracts](/contracts/x402).\n\n## How to use it\n\nThe deployment shape is the same in both modes: build the binary, set the environment, bind to loopback,\nand front it with your own TLS terminator. The difference is which variables you set.\n\n1. Build the gateway from the `citrate-inference-gateway` workspace, then decide the mode. Marketplace mode\n needs an RPC endpoint and the three contract addresses; local-proxy mode needs an upstream URL.\n2. Set the listen address. The default is loopback, `127.0.0.1:9800`. Do not bind to `0.0.0.0` on a host\n reachable from the public network; terminate TLS and apply rate limits at your own reverse proxy and let\n that proxy reach the loopback port.\n3. In marketplace mode, provision the operator signer. Settlement is a real on-chain transaction, so the\n gateway needs an account to send it from. That material is loaded from a keystore file or a KMS key\n reference, never from a value in your config; see the signer family below.\n4. Start the gateway and confirm liveness with `GET /health`, then point Prometheus at `GET /metrics`.\n5. Confirm the read path. `GET /v1/models` is free and reads the on-chain `ModelRegistry`; if it returns\n your expected models, the chain reads are wired. Once the paid router is wired, a paid `POST /v1/chat/completions`\n without a payment header returns an HTTP `402` challenge. With the current binary the paid routes are not\n mounted: expect a `404`, unless you enabled the development-only open chat mode below, which serves chat\n and batch without any payment gate.\n\n## Reference\n\n### Run modes and where they live\n\nThe mode is read and dispatched at start in `gateway/src/main.rs` (the `CITRATE_GATEWAY_MODE` variable is\nread there); the configuration defaults live in `gateway/src/config.rs`. The full operator reference is\n`gateway/RUNBOOK.md` in the repository.\n\n| Mode | Value | Reads chain | Gating | Use |\n|---|---|---|---|---|\n| Marketplace | `marketplace` (default) | yes | x402 per request | selling into Citrate Market |\n| Local-proxy | `local-proxy` | no | `cgk_` API key | a single on-premise inference server |\n\n### Routes the operator runs\n\nHandlers live under `gateway/src/`. The request and response bodies are documented on the\n[client page](/sdks/inference-gateway); this table is the operator's view of what is exposed and how each\nroute is gated.\n\nThe paid routes and `/v1/usage` are mounted by the router that receives the operator signer and the x402\nsettlement configuration, `build_router_with_auth` in `gateway/src/lib.rs`, which is the intended\nmarketplace path. The gateway binary (`gateway/src/main.rs`) does not call it yet; it calls `build_router`. The default `build_router` without that configuration serves only the free read routes,\n`/health`, `/v1/models`, and `/metrics`. Only when both `CITRATE_GATEWAY_OPEN_CHAT` and\n`CITRATE_GATEWAY_DEV_MODE` are set does it also mount `/v1/chat/completions`, `/v1/batch`, `/v1/batch/{id}` and\n`/v1/batch/{id}/output`, with no x402 gate. That mode is for development only.\n\n| Route | Method | Handler | Gating |\n|---|---|---|---|\n| `/v1/chat/completions` | POST | `gateway/src/chat.rs` | x402, paid (not mounted by the released binary) |\n| `/v1/batch` | POST | `gateway/src/batch.rs` | x402, paid (not mounted by the released binary) |\n| `/v1/batch/{id}`, `/v1/batch/{id}/output` | GET | `gateway/src/batch.rs` | submitter-bound reads; mounted only with the paid router or in development open chat mode |\n| `/v1/models` | GET | `gateway/src/models.rs` | free |\n| `/v1/usage` | GET | `gateway/src/usage.rs` | API-key bearer (not mounted by the released binary) |\n| `/health` | GET | `gateway/src/health.rs` | free liveness |\n| `/metrics` | GET | `gateway/src/metrics.rs` | bearer token, disabled when unset |\n\n### Configuration\n\nThe gateway reads its configuration from environment variables, verified in `gateway/src/config.rs`,\n`gateway/src/main.rs`, and the signer, provider, metrics, and key-vault modules. Names and purposes follow;\nno values are shown, and account material is never set inline. Contract addresses are published on the\n[chain address book](/chain/addresses); do not transcribe them here.\n\n```bash\n# Mode and chain wiring\nCITRATE_GATEWAY_MODE=marketplace # or local-proxy; read in gateway/src/main.rs\nCITRATE_GATEWAY_CHAIN_ID=40204 # default 40204\nCITRATE_GATEWAY_RPC_URL=... # chain JSON-RPC endpoint, default http://127.0.0.1:8545\nCITRATE_GATEWAY_LISTEN_ADDR=127.0.0.1:9800 # default loopback; 0.0.0.0 only behind a proxy\n\n# Marketplace-mode contract addresses (see /chain/addresses)\nCITRATE_GATEWAY_MODEL_REGISTRY=0x...\nCITRATE_GATEWAY_PRICING_ORACLE=0x...\nCITRATE_GATEWAY_INFERENCE_ROUTER=0x...\n\n# Operator signer: keystore file or KMS reference, plus spend caps. Never an inline secret.\nCITRATE_GATEWAY_KEYSTORE_PATH=... # durable store, required in production\nCITRATE_GATEWAY_KMS_KEY_ID=... # KMS key reference, an alternative to the keystore\nCITRATE_GATEWAY_OPERATOR_KEYSTORE_PASSWORD=... # keystore passphrase; or _PASSWORD_FILE\nCITRATE_GATEWAY_OPERATOR_SPEND_CAP_WEI=... # per-epoch settlement spend cap\nCITRATE_GATEWAY_OPERATOR_EPOCH_BLOCKS=... # spend-cap epoch length in blocks\nCITRATE_GATEWAY_ALLOW_LOCAL_SIGNER=... # development only, permits an in-process signer\n\n# At-rest money-store master key (note: not CITRATE_GATEWAY_-prefixed)\nGATEWAY_STORE_KEY=... # or GATEWAY_STORE_KEY_FILE\n\n# Provider dispatch controls\nCITRATE_GATEWAY_REQUIRE_SIGNED_RESULTS=... # require providers to sign their results\nCITRATE_GATEWAY_ALLOW_PRIVATE_PROVIDER_ENDPOINTS=... # development only, permits private provider URLs\n\n# Request ceiling, metrics, dev gates, observability\nCITRATE_GATEWAY_MAX_TOKENS=8192 # ceiling; per-request default is 512\nCITRATE_GATEWAY_METRICS_TOKEN=... # bearer token for /metrics; the route is disabled when unset\nCITRATE_GATEWAY_DEV_MODE=... # development only\nCITRATE_GATEWAY_OPEN_CHAT=... # development only, refuses non-loopback binds\nLOG_FORMAT=... # default pretty\nRUST_LOG=...\n\n# Local-proxy mode upstream\nCITRATE_GATEWAY_UPSTREAM_URL=... # resident inference server, default http://127.0.0.1:8181\n```\n\n### The x402 settlement path, from the operator's side\n\nPaid routes sit behind the `X402Layer` middleware in `crates/x402-axum/src/layer.rs`. The handshake is\nimplemented end to end, and settlement is a real on-chain transaction, not a mock. From the operator's view,\na paid request moves through these steps.\n\n1. A request arrives without an `x-payment` header. The gateway mints a single-use nonce and returns HTTP\n `402` with a payment challenge bound to the configured treasury, the wSALT token, chain id 40204, an\n amount in wei, and a limited validity window.\n2. The client signs the EIP-712 `transferWithAuthorization` digest and resends with the `x-payment` header.\n3. The gateway verifies the signature and the nonce, confirms the recipient binds to your treasury, and\n calls `X402Facilitator.settlePayment` on chain. Settlement is a single transaction, not a retried one.\n4. After the receipt confirms, the gateway runs the handler and dispatches to a selected provider, retrying\n up to `MAX_PROVIDER_ATTEMPTS` (3) providers before it gives up and returns a `503`. When\n `CITRATE_GATEWAY_REQUIRE_SIGNED_RESULTS` is set, it also verifies the provider's signed result. It then\n returns the OpenAI-shaped response. The provider-retry count and the signed-result check are separate from\n the single settlement transaction in step 3.\n\nThe client codec for this handshake lives in the [Marketplace SDK](/sdks/marketplace#x402); an operator does\nnot reimplement it.\n\n### Pieces that are designed but not complete at this SHA\n\nThe gateway is honest about what is not finished. Treat these as the operator-relevant gaps.\n\n- **Pool dispatch returns `503`.** The scoring logic can select a compute pool, but when a pool wins the\n dispatch the gateway returns a `503`; the per-provider path is the one that runs today. Status: Specified.\n- **Usage accounting is in process memory.** `GET /v1/usage` totals are held in memory and do not survive a\n restart. Durable usage storage is a later slice. Status: Specified.\n- **Friendly model-name resolution is best effort.** A pinned 32-byte model hash always resolves; a friendly\n name is resolved by enumerating the registry, because `ModelRegistry` exposes no name-to-hash view. A\n dedicated view or an off-chain name registry is the intended fix. Status: Specified.\n\n## Design rationale\n\nThe gateway runs on the operator's own hardware and binds to loopback by default, so the network surface is\nsomething you place deliberately behind your own TLS and rate limits rather than something exposed by\naccident. Settlement happens per request rather than against a held balance, so the gateway never escrows\nmore than the single request or batch in flight, and an operator is paid for the work actually performed.\nThe OpenAI shape is kept intact so the payment difference sits behind one middleware and the rest reads as an\nordinary inference proxy. The cost of per-request settlement is an on-chain transaction in the hot path; the\nbenefit is that no balance is held on a caller's behalf.\n\n## Failure modes\n\nThe paid surface is where the gateway is security relevant, and it fails closed.\n\n- **No payment.** A paid route without a valid `x-payment` header returns `402`, never the work.\n- **Replayed or forged payment.** The nonce is single-use and minted by this gateway, the signature is\n verified, the window is checked, and the recipient is bound to your treasury, so a replayed payment is\n rejected before it touches the chain.\n- **Settlement revert.** If the on-chain settlement reverts, the gateway returns `402` with the transaction\n hash rather than running the handler.\n- **Oversized request.** `max_tokens` is clamped to the ceiling before pricing, a batch over 1000 requests\n is rejected, and a batch spanning more than 32 distinct models (`MAX_DISTINCT_BATCH_MODELS`) is rejected,\n so a caller cannot price small and demand large.\n- **Open chat in production.** The unauthenticated chat path is gated behind two development flags and the\n gateway refuses to enable it on a non-loopback bind, so it cannot be left exposed by accident.\n- **Pool dispatch.** A request that scores to a pool returns `503` today rather than failing silently; route\n such traffic to the per-provider path until pool dispatch ships.\n\n## Access and canon\n\nCommercial tier. This is operator and deployment depth, the configuration and settlement detail an operator\nneeds to stand up a gateway. The client-facing REST surface that calls it is public and lives on the\n[client page](/sdks/inference-gateway).\n\nThe gateway runs on hardware you control. Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity. No API keys, treasury addresses, or operator account material appear here; that material is loaded\nfrom a keystore or a KMS reference and is never documented. The repository contains no hardcoded credentials\nat the audited SHA.\n\n## Source and verification\n\n- Source: `citrate-inference-gateway`. Run modes and configuration in `gateway/src/main.rs` and\n `gateway/src/config.rs`; router assembly in `gateway/src/lib.rs`; route handlers in `gateway/src/`\n (`chat.rs`, `batch.rs`, `models.rs`, `usage.rs`, `health.rs`, `metrics.rs`); chain reads in\n `gateway/src/queries.rs` (the `ChainQueries` trait, not an HTTP route); x402 middleware in\n `crates/x402-axum/src/layer.rs`; operator reference in `gateway/RUNBOOK.md`.\n- Audited against SHA: `603fe92`.\n- Status: Implemented (pre-audit) for the free routes: the run modes, on-chain reads and per-provider\n dispatch with failover exist and run. The x402 settlement path and the API-key routes are built and tested\n but not mounted by the released binary, so paid calls are not live. This slice has not had an external audit. Specified: pool\n dispatch (returns `503` today), durable usage accounting, and a name-to-hash model view.\n"},"/compute/node-agent":{"slug":"/compute/node-agent","title":"Citrate Node","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-node-agent (crates/, README.md)","syncedSha":"0e63363","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"`compute.json`, `crates/config/src/lib.rs`","anchor":"computejson-cratesconfigsrclibrs"},{"depth":3,"text":"Environment variables","anchor":"environment-variables"},{"depth":3,"text":"Bidder, `crates/bidder/src/lib.rs`","anchor":"bidder-cratesbiddersrclibrs"},{"depth":3,"text":"Heartbeat, `crates/heartbeat/src/lib.rs`","anchor":"heartbeat-cratesheartbeatsrclibrs"},{"depth":3,"text":"Supervision HTTP, `crates/supervision/src/server.rs`","anchor":"supervision-http-cratessupervisionsrcserverrs"},{"depth":3,"text":"Marketplace calls, `crates/chainio/`","anchor":"marketplace-calls-crateschainio"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Citrate Node is the daemon an operator runs to sell compute on Citrate Market from their own hardware.\nIt reads a small settings file, watches the market over JSON-RPC, decides which jobs to bid on, proves\nliveness with a heartbeat, and drives a won job to payout. It holds no keys: every write it wants made is\nhanded, unsigned, to a separate signing surface. This page is for operators on Citrate Network, chain id 40204.\n\n## What it is\n\nCitrate Node is a Rust workspace of focused crates, built into one `node-agent` binary. The binary loads\n`compute.json`, samples the clock, reads chain state, runs the bidder, and exposes a loopback supervision\nAPI the operator's tools drive. Compute is sold on Citrate Market; the machine running this daemon stays\non your premises, and the work it performs settles in SALT.\n\nOne property shapes the whole design: the daemon holds no signing keys. Every on-chain write is produced\nas an unsigned `SignatureRequest` and queued for an external signing surface, the operator's Citrate\nKeyring or a signing relay, which signs and broadcasts it. The daemon only observes the resulting\ntransaction. This is why selling involves two roles, the agent that decides and the signer that holds\nkeys, and it is enforced in code at `crates/lifecycle/src/lib.rs` and `crates/node-agent/src/main.rs`\n(ADR-agent-signing / TD-17).\n\nThe crates, each citing the path it is audited against:\n\n| Crate | Path | Role |\n|---|---|---|\n| `config` | `crates/config/src/lib.rs` | Parse `compute.json` into `ComputeSettings`; schedule-window logic. |\n| `bidder` | `crates/bidder/src/lib.rs` | Pure `evaluate()` cost-plus bid decision. |\n| `heartbeat` | `crates/heartbeat/src/lib.rs` | 30-second liveness loop, `heartbeat()` calldata. |\n| `chainio` | `crates/chainio/` | Chain-40204 address book, JSON-RPC read client, ABI codec, outbound TLS gate. |\n| `supervision` | `crates/supervision/src/server.rs` | Loopback HTTP control surface, bearer-token gated. |\n| `node-agent` | `crates/node-agent/` | The binary that ties the crates together. |\n| `lifecycle` | `crates/lifecycle/src/lib.rs` | Job state machine, plans the next unsigned write. |\n| `executor` | `crates/executor/` | Model provisioning and inference adapter. |\n| `earnings` | `crates/earnings/` | Claimable poll and `claimRewards()` calldata. |\n| `pinning` | `crates/pinning/` | Replication-slot pinning sidecar. |\n\nThe settings, bidder, heartbeat, and supervision surfaces are the implemented selling path (SELL-S1). The\nexecution, model-provisioning, and earnings surfaces (SELL-S2) compile and are wired into the daemon loop\nbut several paths still report themselves as not yet production; treat them as experimental. The agent\nhas not had an external audit.\n\n## How to use it\n\n1. Build the binary from the workspace root with `cargo build --release`. The result is\n `target/release/node-agent`.\n2. Write `compute.json`, your participation policy. Start with `enabled: false` to dry-run the wiring,\n then flip it to `true`.\n3. Self-check offline by running `node-agent path/to/compute.json`. No RPC is contacted; the agent prints\n your policy, the clock, and the heartbeat calldata it would send.\n4. Run live as a daemon with `CITRATE_RPC_URL` and `CITRATE_PROVIDER_ADDRESS` set. The daemon brings up\n the loopback supervision API, reads chain state each tick, runs the bidder, and beats every 30 seconds.\n5. Wire up your signer. The signing surface pulls unsigned requests from `/signature-requests`, signs and\n broadcasts them, then reports each back to `/signature-requests/{id}/observed`.\n\nThe runnable, end-to-end version of this is [become a compute seller](/operators/tutorials/become-a-seller),\nand the full operating procedure is [sell compute](/operators/sell-compute).\n\n## Reference\n\n### `compute.json`, `crates/config/src/lib.rs`\n\nAudited against `ComputeSettings`. Unknown fields are ignored, so newer writers stay forward-compatible; a\nmissing or empty file falls back to disabled, the fail-safe default.\n\n| Field | Type | Default | Meaning |\n|---|---|---|---|\n| `enabled` | bool | `false` | Master participation switch. |\n| `allocation_percent` | u8, 0 to 100 | `0` | Fraction of the GPU you allot. Out of range is rejected. In S1 this is read and surfaced, not yet hardware-enforced. |\n| `schedule` | enum | `always` | `always`, `nights` (22:00 to 05:59 local), or `weekends` (Saturday and Sunday). |\n\n```json\n{\n \"enabled\": true,\n \"allocation_percent\": 50,\n \"schedule\": \"always\"\n}\n```\n\n### Environment variables\n\nAudited against the crates at this SHA. No secrets belong here. Every outbound URL is validated at client\nconstruction and fails closed on plaintext HTTP to a non-loopback host (`crates/chainio/src/outbound.rs`).\n\n| Variable | Default | Required | Purpose |\n|---|---|---|---|\n| `CITRATE_RPC_URL` | unset | No, S1 runs offline | Chain-40204 JSON-RPC endpoint, https or loopback http. |\n| `CITRATE_PROVIDER_ADDRESS` | unset | No | The operator's provider account address, 20-byte hex. |\n| `CITRATE_NODE_AGENT_DAEMON` | unset | No | Enable daemon mode (`1` or `true`), or pass `--daemon`. |\n| `CITRATE_NODE_AGENT_ADDR` | `127.0.0.1:19600` | No | Supervision bind address; must stay loopback or the daemon refuses to start. |\n| `CITRATE_NODE_AGENT_TOKEN_FILE` | `$HOME/.citrate/node-agent/supervision.token` | No | Bearer-token file, minted at startup, mode 0600. |\n| `CITRATE_NODE_PFLOPS_1E18` | `6e18`, 6 pflops | No | Node throughput as fixed-point times 1e18, feeds execution-time estimates. |\n| `CITRATE_CLAIM_THRESHOLD_WEI` | `1e18`, 1 SALT | No | Auto-claim earnings once claimable reaches this threshold (S2). |\n| `CITRATE_MODEL_CACHE_DIR` | `/var/lib/citrate-node-agent/models` | No | Model-weights cache (S2). |\n| `CITRATE_MODEL_SHA256` | unset | No | Trusted weights digest, recomputed locally for integrity (S2). |\n| `CITRATE_RESIDENT_MODEL_HASH` | unset | No | 64-hex model hash that binds the resident llama-server to one declared model; a job for any other model is refused (S2). |\n| `CITRATE_IPFS_GATEWAY` | unset | No | Gateway for model-CID weight fetch (S2). |\n| `CITRATE_LLAMA_URL` | unset | No | Resident llama-server inference endpoint (S2). |\n| `CITRATE_JOB_INPUT_DIR` | unset | No | Watched directory for off-chain job input (S2). |\n| `CITRATE_HTTP_TIMEOUT_SECS` | `30` | No | Total-request timeout for the outbound RPC and inference clients. |\n| `CITRATE_HTTP_CONNECT_TIMEOUT_SECS` | `10` | No | Connect timeout for the outbound clients. |\n| `CITRATE_HTTP_READ_TIMEOUT_SECS` | `60` | No | Per-read inactivity timeout, covers the streaming weight fetch. |\n\nThe execution path turns on only when `CITRATE_IPFS_GATEWAY`, `CITRATE_LLAMA_URL`, and\n`CITRATE_JOB_INPUT_DIR` are all set; otherwise the daemon bids and claims earnings but does not execute\nwon jobs (`crates/node-agent/src/main.rs`, `build_job_executor`).\n\nThere is also a dev-only escape hatch, `CITRATE_NODE_AGENT_ALLOW_INSECURE_OUTBOUND=1`. It bypasses the TLS\nrequirement and permits plaintext HTTP to non-loopback hosts, for LAN test rigs only. A network attacker\non a plaintext RPC link can feed the agent false chain state, false oracle prices, and false job state.\nThe agent logs loudly whenever it is set. Do not set it on any production node.\n\n### Bidder, `crates/bidder/src/lib.rs`\n\nA pure function, `evaluate(job, oracle, settings, caps) -> BidDecision`. The gates run cheapest first,\nthen pricing:\n\n1. `enabled == false`, skip (Disabled).\n2. Outside the schedule window, skip (OutsideSchedule).\n3. `maxPrice >= 10 SALT`, skip (ExceedsCommitmentCap); S1 stays below the threshold above which a job\n auto-upgrades to a verification tier whose precompile is stubbed.\n4. Tier other than Commitment, skip (UnsupportedTier).\n5. Active jobs at or above 80% of capacity, skip (AtCapacity).\n6. Time to deadline below twice the estimated execution seconds, skip (DeadlineInfeasible).\n7. Schedule window closes within twice the estimated execution seconds, skip (WindowClosingSoon).\n8. Oracle price stale, skip (OracleStale).\n9. Price is `cost x 1.15`, capped at `0.9 x maxPrice`; if that would fall below cost, skip (Unprofitable);\n otherwise bid.\n\n### Heartbeat, `crates/heartbeat/src/lib.rs`\n\nThe cadence is 30 seconds (`HEARTBEAT_INTERVAL`), kept under the on-chain heartbeat window so a single\nmissed beat never trips suspension. The calldata is the `HeartbeatMonitor.heartbeat()` selector alone, no\narguments. Send errors are logged but do not stop the loop.\n\n### Supervision HTTP, `crates/supervision/src/server.rs`\n\nLoopback only; a non-loopback bind is rejected at startup. A 256-bit bearer token is minted at startup,\npersisted at mode 0600, and required on every endpoint except `/health`.\n\n| Method | Route | Auth | Response |\n|---|---|---|---|\n| GET | `/health` | none | Health snapshot for liveness probes. |\n| GET | `/status` | bearer | `idle`, `bidding`, `executing`, or `paused`. |\n| POST | `/pause` | bearer | Stop new bids; in-flight jobs finish. |\n| POST | `/resume` | bearer | Resume bidding. |\n| GET | `/signature-requests` | bearer | The unsigned `SignatureRequest`s waiting for the signer. |\n| POST | `/signature-requests/{id}/observed` | bearer | Mark a request signed and broadcast; body `{\"tx_hash\":\"0x...\"}`. |\n\n### Marketplace calls, `crates/chainio/`\n\nThe read client and ABI codec work against the chain-40204 address book, mirrored from Citrate Network and\nguarded by a divergence test. On `ComputeMarketplace` the agent reads `getProvider` and `getJob` and builds\ncalldata for `registerProvider`, `bidOnJob`, `assignBestBid`, `startExecution`, `submitCommitment`,\n`submitResult`, and `completeJob`. It reads `saltPerPflopHour` and `isPriceStale` from `ComputePricingOracle`,\nsends `heartbeat()` to `HeartbeatMonitor`, claims from `ContributionAccounting`, and reads `getModel` from\n`ModelRegistry`. The marketplace functions are documented in full in [compute contracts](/contracts/compute).\n\n## Design rationale\n\nThe two-role split, an agent that never holds keys and a signer that does, is the load-bearing decision.\nAn unattended daemon that watches the market and reacts to prices is exactly the kind of process you do not\nwant holding a signing key on a production GPU host. By emitting unsigned requests and letting the operator's\nCitrate Keyring or a relay sign them, a compromise of the daemon cannot move funds or stake on its own. The\ncost is the extra signer hop, which the supervision API and the observe callback are built to make routine.\n\nThe bidder's gates lean conservative for the same reason. New providers start with no reputation, jobs that\nmiss a deadline are slashed, and the night and weekend schedules exist so an operator can sell idle hours\nwithout supervising the machine. The 80% capacity cap and the twice-execution-time margins all reserve\nheadroom so the daemon does not take on work it cannot safely finish.\n\n## Failure modes\n\n- A non-loopback `CITRATE_NODE_AGENT_ADDR` is refused at startup; the supervision surface cannot be widened\n to a routable interface.\n- Plaintext HTTP to a non-loopback RPC, IPFS, or inference endpoint fails closed at client construction, so\n a misconfigured daemon dies at startup rather than mid-job. The dev-only insecure-outbound flag is the\n only override, and it is forbidden in production.\n- The bearer token is minted locally at mode 0600. A web page the operator visits cannot read it, which\n also closes the cross-site request hole on `/pause` and `/resume`. `/health` is intentionally open and\n exposes no secret.\n- Past the execution deadline the lifecycle planner aborts rather than submit a late, slashable result.\n- A stale oracle stops the bidder from pricing off bad data.\n\n## Access and canon\n\nTier commercial.kyc. The agent is operator-depth implementation, the bid-pricing logic, the lifecycle, and\nthe capacity gates, that any contracted operator should have but whose anonymous theft would materially help\na competitor clone the selling side of the market. Access is gated on identity verification through Citrate's in-house verification (VERI),\nnot on a seat. Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity. Compute is sold from your own hardware, and\nSALT settles the work; it is the unit you count in, not a product to hold. No secrets appear on this page:\nthe supervision token is generated locally and never transcribed, and the agent holds no keys by design.\n\n## Source and verification\n\nVerified against `citrate-node-agent` at `38bc9d1`. `compute.json` against `crates/config/src/lib.rs`; the\nbidder gates and cost-plus pricing against `crates/bidder/src/lib.rs`; the 30-second cadence against\n`crates/heartbeat/src/lib.rs`; the loopback bind, bearer token, and routes against\n`crates/supervision/src/server.rs` and `crates/supervision/src/auth.rs`; the no-keys signing seam against\n`crates/lifecycle/src/lib.rs` and `crates/node-agent/src/main.rs`; the outbound TLS gate against\n`crates/chainio/src/outbound.rs`; the marketplace calls against `crates/chainio/src/marketplace.rs` and the\nchain-40204 address book in `crates/chainio/src/generated/addresses.json`. Status: SELL-S1 (settings,\nbidder, heartbeat, supervision, live reads) Implemented, pre-audit; SELL-S2 (execution, executor, earnings)\nSpecified and experimental.\n"},"/compute/pool":{"slug":"/compute/pool","title":"Citrate Compute Pool","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-compute-pool (pool-coordinator/, training-worker/)","syncedSha":"e5b7280","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"The two crates","anchor":"the-two-crates"},{"depth":3,"text":"How the coordinator decides","anchor":"how-the-coordinator-decides"},{"depth":3,"text":"Coordinator environment variables","anchor":"coordinator-environment-variables"},{"depth":3,"text":"Worker environment variables","anchor":"worker-environment-variables"},{"depth":3,"text":"What the worker library already contains","anchor":"what-the-worker-library-already-contains"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The compute pool is how several machines act as one provider on Citrate Market. It is two\ndaemons: a coordinator that spreads single-prompt inference work across a pool, and a worker\nthat takes part in distributed training and in multi-stage inference. It is for operators who\nrun more than one machine and want them to share work and share the pay.\n\n## What it is\n\nA pool is a group of machines, each one a member, that present themselves to the market as a\nsingle provider. The work and the settlement still happen on the public ledger, the Citrate\nNetwork; the pool only decides which member does a given job and divides the payment when the\njob is done.\n\nTwo daemons live in the `citrate-compute-pool` workspace, and they play different roles.\n\n- The coordinator, `citrate-pool-coordinator`, handles single-prompt inference. Every member\n of an on-chain `ComputePool` runs a copy. The copies watch the chain for a `ComputeRequested`\n event, and the one whose account is the elected coordinator for the current epoch picks a\n member, records the dispatch on-chain, sends the prompt to that member over HTTPS, and then\n submits the completion or the failure. An epoch is a fixed window of 100 blocks; the elected\n coordinator rotates with it.\n- The worker, `citrate-training-worker`, takes part in jobs that span machines. In `training`\n mode it is one node of a data-parallel training pool, the surface we call Citrate Orchard. In\n `pipeline` mode it owns one stage of a model too large for a single machine, and activations\n flow through the stages in sequence.\n\nBoth daemons keep their keys, their data, and their model weights on the operator's own\nhardware. Nothing about a job reaches the public ledger except the dispatch record, the\ncompletion, and the payment, which is the on-premise default that holds across the network.\n\n## How to use it\n\n1. Build the workspace. From the `citrate-compute-pool` root, `cargo build --release` produces\n both binaries. Neither one depends on a chain crate at the Rust level; they speak to the\n network over JSON-RPC at runtime, so no deploy key is needed to build.\n2. Give each daemon an account. Both load a secp256k1 key, either from a Secret Storage v3\n keystore file plus its passphrase, or from a raw private-key hex string for testnet only. The\n account address you configure must match the key.\n3. Point the daemon at the network and the contract. The coordinator needs the `ComputePool`\n address and the map of member endpoints; the worker needs its mode and its job contract.\n4. Run the coordinator on every member machine. Each copy decides for itself whether it is the\n elected coordinator for the current epoch and acts only when it is.\n5. Run the worker where the training or pipeline job lives, scoped to the job id you want it to\n watch.\n\nA coordinator and a worker, started from the environment up:\n\n```bash\n# Coordinator: one copy per pool member\nexport CITRATE_POOL_KEYSTORE_PATH=/secure/pool-keystore.json\nexport CITRATE_POOL_KEYSTORE_PASSPHRASE=\"$KEYSTORE_PASS\" # from your secret store\nexport CITRATE_POOL_WALLET_ADDRESS=0x\nexport CITRATE_POOL_CONTRACT=0x\nexport CITRATE_POOL_RPC_URL=https://\nexport CITRATE_POOL_MEMBER_ENDPOINTS=\"0xm1=https://m1/pool-infer,0xm2=https://m2/pool-infer\"\ncitrate-pool-coordinator\n\n# Worker: one per training or pipeline job\nexport CITRATE_WORKER_MODE=training\nexport CITRATE_TRAINING_KEYSTORE_PATH=/secure/worker-keystore.json\nexport CITRATE_TRAINING_KEYSTORE_PASSPHRASE=\"$KEYSTORE_PASS\"\nexport CITRATE_WORKER_CONTRACT=0x\nexport CITRATE_WORKER_JOB_ID=42\ncitrate-training-worker\n```\n\n## Reference\n\n### The two crates\n\n| Path | Crate | Role |\n|---|---|---|\n| `pool-coordinator/` | `citrate-pool-coordinator` | Watches `ComputeRequested`, decides the member, records dispatch, posts the prompt, completes or fails the job. |\n| `training-worker/` | `citrate-training-worker` | `training` mode is a data-parallel training node, `pipeline` mode is one stage of a multi-stage model. |\n\nThe workspace also builds two binaries this page does not cover: `citrate-coop-worker` (a member-facing\nregister-and-poll daemon in `training-worker/src/bin/coop_worker.rs`) and `citrate-training-coordinator` (a\nseparate HTTP coordinator service in the `training-coordinator/` crate).\n\n### How the coordinator decides\n\nThe decision loop is `handle_event` in `pool-coordinator/src/lib.rs`. Before it touches the chain, it runs\nan admission gate (`pool-coordinator/src/lib.rs`, CP-B-008): it rejects the event with `RejectedEvent` if\nthe payment is below `CITRATE_POOL_MIN_PAYMENT_GRAINS`, the prompt is larger than\n`CITRATE_POOL_MAX_PROMPT_BYTES`, or `max_tokens` is above `CITRATE_POOL_MAX_TOKENS`. For an admitted\n`ComputeRequested` event it then does five things, in order, and the order matters.\n\n1. It reads `coordinatorFor(poolId, epoch)`, with the epoch derived from the event's block\n number as `block / 100` (`pool-coordinator/src/chain.rs`, `epoch_of`). If the elected\n coordinator is not this daemon's own account, it stops and does nothing on-chain.\n2. It reads the pool's members, keeps only those that are active and have a GPU, and picks one.\n Selection is deterministic, not a rotating cursor: the member is\n `active[keccak256(job_id) mod active_count]`, where `select_member` takes the last eight bytes\n of the digest as a `u64` and reduces it modulo the active-member count\n (`pool-coordinator/src/dispatcher.rs`). The same job id always picks the same member, so a\n coordinator that restarts mid-job picks the same target, and two daemons that briefly believe\n they are coordinator pick the same target rather than two different ones.\n3. It resolves the chosen member to its HTTPS endpoint from the configured map, failing with\n `UnknownMemberEndpoint` if the member is not listed.\n4. It records the dispatch on-chain before making the HTTP call. Recording first means that if\n the call hangs and the daemon crashes, the chain already knows the dispatch happened and the\n timeout clock has started, so the job cannot sit pending forever.\n5. It POSTs the prompt to `member/pool-infer` and waits. On success it submits `completeJob`,\n which distributes payment across the members. On failure it submits `failJob`, which refunds\n the buyer.\n\nThe request body is `{model, prompt, max_tokens, job_id}` and the response is\n`{output, input_tokens?, output_tokens?}` (`pool-coordinator/src/provider.rs`). A 2xx response\nwith an empty or whitespace `output` is rejected as `ProviderFailed`, which routes the job down\nthe refund path. This is the pay-for-no-work guard; it does not yet check that the output is\ncorrect, only that there is one.\n\n### Coordinator environment variables\n\nRead from `pool-coordinator/src/config.rs` (chain, endpoints, timeouts, admission caps),\n`pool-coordinator/src/wallet.rs` (the account keys), and `pool-coordinator/src/main.rs` (contract address,\npoll cadence, metrics bind).\n\n| Variable | Default | Required | Purpose |\n|---|---|---|---|\n| `CITRATE_POOL_KEYSTORE_PATH` | none | one of | SSv3 keystore file path. |\n| `CITRATE_POOL_KEYSTORE_PASSPHRASE` | none | if keystore set | Keystore passphrase. |\n| `CITRATE_POOL_PRIVATE_KEY_HEX` | none | one of (testnet) | Raw 64-hex key. |\n| `CITRATE_POOL_WALLET_ADDRESS` | none | yes | Must match the derived key. |\n| `CITRATE_POOL_CONTRACT` | none | yes | `ComputePool` address. |\n| `CITRATE_POOL_MEMBER_ENDPOINTS` | `\"\"` | yes | `addr1=url1,addr2=url2` member endpoint map. |\n| `CITRATE_POOL_CHAIN_ID` | `40204` | no | Chain id, verified against the RPC at startup. |\n| `CITRATE_POOL_RPC_URL` | `http://127.0.0.1:8545` | no | JSON-RPC endpoint. |\n| `CITRATE_POOL_PROVIDER_TIMEOUT_SECS` | `30` | no | Per-request member timeout. |\n| `CITRATE_POOL_MIN_PAYMENT_GRAINS` | `1` | no | Admission gate: reject a request paying below this. |\n| `CITRATE_POOL_MAX_PROMPT_BYTES` | `131072` | no | Admission gate: reject a prompt larger than this (128 KiB). |\n| `CITRATE_POOL_MAX_TOKENS` | `8192` | no | Admission gate: reject a request asking for more tokens than this. |\n| `CITRATE_POOL_POLL_INTERVAL_SECS` | `3` | no | Event poll cadence. |\n| `CITRATE_POOL_CONFIRMATIONS_BUFFER` | `12` | no | Re-scan depth for reorg tolerance. |\n| `CITRATE_POOL_FROM_BLOCK` | `latest` | no | Event start block. |\n| `CITRATE_POOL_WS_URL` | none | no | Opt-in websocket endpoint for event subscription. |\n| `CITRATE_POOL_METRICS_ADDR` | none | no | Prometheus `/metrics` bind, warns if not loopback. |\n\n### Worker environment variables\n\nRead from `training-worker/src/bin/main.rs`.\n\n| Variable | Default | Required | Purpose |\n|---|---|---|---|\n| `CITRATE_WORKER_MODE` | none | yes | `training` or `pipeline`. |\n| `CITRATE_TRAINING_KEYSTORE_PATH` | none | one of | SSv3 keystore path. |\n| `CITRATE_TRAINING_KEYSTORE_PASSPHRASE` | none | if keystore set | Passphrase. |\n| `CITRATE_TRAINING_PRIVATE_KEY_HEX` | none | one of (testnet) | Raw key. |\n| `CITRATE_WORKER_CONTRACT` | none | yes | Training or pipeline contract address. |\n| `CITRATE_WORKER_JOB_ID` | none | recommended | The job to watch, by id. |\n| `CITRATE_WORKER_CHAIN_ID` | `40204` | no | Chain id. |\n| `CITRATE_WORKER_RPC_URL` | `https://rpc.citrate.ai` | no | JSON-RPC endpoint. |\n| `CITRATE_WORKER_POLL_INTERVAL_SECS` | `3` | no | Event poll cadence. |\n| `CITRATE_WORKER_CONFIRMATIONS_BUFFER` | `12` | no | Re-scan depth for reorg tolerance. |\n| `CITRATE_WORKER_FROM_BLOCK` | `latest` | no | Event start block. |\n| `CITRATE_WORKER_METRICS_ADDR` | none | no | Metrics bind, not yet wired for the worker. |\n\n### What the worker library already contains\n\nThe training state machine is written and tested, even where the production binary does not yet\ndrive it. In `training-worker/src/worker.rs` a `Worker` runs the full lifecycle, joining,\nloading shared starting weights, the per-step forward and backward pass, a quantized\nall-reduce of the gradient with peers, a per-step commitment, and, when a worker is the elected\ncoordinator for an epoch, aggregating the peers' commitments into a Merkle root and posting it\nwith `commitEpoch`. The `EpochAggregator` in that file enforces that only registered members\ncount toward a root and that each `(worker, step)` pair counts once, so a single flooding peer\ncannot finalize a root over forged work. The pipeline path in `training-worker/src/pipeline.rs`\nruns the same idea for one stage of a model. Today these run against a small deterministic model,\nan in-process transport, and a mock chain client, which is enough to check the state machine end\nto end.\n\n## Design rationale\n\nThe coordinator holds no shared state, and that is deliberate. Because the member is chosen by\nhashing the job id rather than advancing a cursor, every copy of the daemon reaches the same\nanswer without talking to the others, a restart does not lose its place, and the brief window\nwhere two copies both think they are coordinator resolves on-chain: the contract's \"job not\npending\" guard rejects the second `recordDispatch`. The cost is that selection is not perfectly\neven per member; over many jobs it is close enough, and the property we wanted was agreement\nwithout coordination. Recording the dispatch before the HTTP call, rather than after, trades a\nlittle latency for the guarantee that a crashed coordinator leaves a recoverable trail instead\nof a job stuck pending forever.\n\n## Failure modes\n\nThe surface is security relevant where money moves, so it fails toward the buyer. A member that\nreturns nothing, times out, or returns empty output is treated as work not done: the coordinator\nsubmits `failJob` and the buyer is refunded rather than paying for silence. The chain id is\nchecked against the RPC at startup, so a daemon pointed at the wrong network stops instead of\nsigning transactions there. Pool membership and stake are enforced by the on-chain contract, not\nby the daemons, so a machine cannot pay itself by lying to a coordinator. Bind any metrics\nendpoint to loopback unless you put authentication in front of it; the coordinator warns when the\nbind address is not loopback. The output guard closes the pay-for-empty-work path, not the\npay-for-wrong-work path: checking that an answer is correct, not merely present, is on-chain work\nthat is specified but not yet built.\n\n## Access and canon\n\nTier commercial: this is paid-seat marketplace operation, where operators sell pooled compute\nunder a service level. Every member account on the public network is identity-checked through\nVERI; Citrate keeps the verification result, not the personal data behind it. Keys, data, and\nmodel weights stay on the operator's hardware, and the only things the pool publishes are the\ndispatch record, the completion, and the payment. No secrets appear on this page: keystore\npassphrases and private keys come from your own secret store and are never printed.\n\nThis page connects to [federated learning](/research/learning) for the training surface\n(Citrate Orchard), to the [compute contracts](/contracts/compute) the daemons call, and to the\n[node agent](/compute/node-agent) that a single machine runs when it is not part of a pool.\n\n## Source and verification\n\n- Source repo: `citrate-compute-pool`.\n- Files: `pool-coordinator/src/lib.rs`, `dispatcher.rs`, `chain.rs`, `provider.rs`, `config.rs`;\n `training-worker/src/lib.rs`, `worker.rs`, `pipeline.rs`, `bin/main.rs`, `training-worker/src/wallet.rs`.\n- Audited against SHA: `e5b7280`.\n- Status by component:\n - Coordinator decision loop (`handle_event`, `select_member`, `epoch_of`, output guard):\n Implemented, with unit and smoke tests. The live JSON-RPC adapter (`HttpChainAdapter`) is the\n seam the loop runs against; the loop and its guards are tested through a mock chain.\n Pre-audit.\n - Coordinator binary: Implemented, pre-audit.\n - Worker binary (`bin/main.rs`): Implemented for event observation. It watches a job and logs\n the events that would drive the state machine; wiring those events to spawn a `Worker` is a\n follow-up. Pre-audit.\n - Training and pipeline state machines (`worker.rs`, `pipeline.rs`): Implemented against a\n deterministic model, in-process transport, and mock chain (S0). The real GPU backend,\n cross-machine libp2p transport, and live chain are Specified, not yet built (S1 and S2).\n - On-chain output correctness checking and challenge hooks: Specified, not yet built.\n"},"/contracts/compute":{"slug":"/contracts/compute","title":"Compute contracts","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/contracts/src (ComputeMarketplace.sol, ComputePool.sol, ComputePoolTraining.sol, ComputeVerifier.sol, BulkComputeGateway.sol, ComputePricingOracle.sol, interfaces/IComputePricingOracle.sol)","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"ComputeMarketplace","anchor":"computemarketplace"},{"depth":3,"text":"ComputePool","anchor":"computepool"},{"depth":3,"text":"ComputePoolTraining","anchor":"computepooltraining"},{"depth":3,"text":"ComputeVerifier","anchor":"computeverifier"},{"depth":3,"text":"ComputePricingOracle","anchor":"computepricingoracle"},{"depth":3,"text":"BulkComputeGateway","anchor":"bulkcomputegateway"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"These are the contracts that let a machine operator sell compute on Citrate Market and let a buyer pay\nfor verified work. Six contracts form one settlement loop: a buyer posts a job, an operator runs it,\nthe result is verified by tier, and payment settles on the public ledger. This page is for operators,\nbuyers, and anyone integrating against the marketplace.\n\n## What it is\n\nCitrate Market is the part of the public ledger where compute is bought and sold. An operator runs a\nCitrate Node off-chain to do the actual work, on hardware they control; the contracts here record the\nagreement, hold the escrow, decide whether the returned work was valid, and split the payment. The\nheavy data, the model weights and the inputs, never live on-chain. What the ledger keeps is a hash, a\nproof, and a receipt.\n\nThe loop has six parts:\n\n- **ComputeMarketplace** runs the single-provider job lifecycle: post, bid, assign, execute, verify,\n settle.\n- **ComputePool** and **ComputePoolTraining** are the multi-provider variants, one for pooled inference\n with a throughput guarantee, one for distributed training across many workers.\n- **ComputeVerifier** decides whether a returned result is valid, by one of three tiers.\n- **ComputePricingOracle** maps compute cost and SALT price so a job can be quoted.\n- **BulkComputeGateway** lets an institution pre-fund credits with a stablecoin and spend them later.\n\nAll six share one governance pattern. Each inherits a `Governable` mixin with a two-step transfer\n(`transferGovernance` then `acceptGovernance`), an `onlyGovernance` modifier, and a `governance()`\nreader. Five of the six also inherit `ReentrancyGuard`; the pricing oracle does not move value, so it\ndoes not need it.\n\nOperators sell into this surface through [Operators, sell compute](/operators/sell-compute) and run the\ndaemon described in [run a node](/operators/run-a-node). The on-chain verification leans on the compute\n[precompiles](/chain/precompiles), in particular the Halo2-KZG inference verifier at `0x0108`.\n\n## How to use it\n\nThe single-provider path, from a buyer's side, runs like this.\n\n1. Quote the work. Ask **ComputePricingOracle** what one PFLOP-hour costs in SALT with\n `saltPerPflopHour()`, or estimate a specific job with `estimateJobCost(modelHash, inputTokens,\n outputTokens, verificationTier)`.\n2. Post the job. Call `postJobWithMethod(...)` on **ComputeMarketplace** with the model hash, the input\n hash, a maximum price, a verification tier, a payment method, a bid window, and an execution window.\n The escrow is taken from `msg.value` in SALT, or debited from bulk credits if you chose that method.\n3. Wait for bids and assign. Operators call `bidOnJob`. Anyone can then call `assignBestBid`, which\n scores the bids on price, reputation, current load, and verification fit, and assigns the winner.\n4. The operator executes. They call `startExecution`, optionally post a Tier-1 commitment with\n `submitCommitment`, then return the result with `submitResult`. Submitting the result triggers\n verification inline.\n5. Settle. Once the verifier returns Valid and no dispute is open, call `completeJob`. Payment splits\n into the provider's share, a burn, and a treasury fee.\n\nTo sell instead of buy, register first: `registerProvider(supportedModels)` with a stake of at least\n`MIN_PROVIDER_STAKE`, then bid on jobs that match your models. To pool with other operators, see\n**ComputePool** below; to join a training run, see **ComputePoolTraining**.\n\n## Reference\n\nThe audited public surface of each contract, with the function names as they appear in source. The\ncanonical truth is the Solidity in `contracts/src`; treat ABIs as coming from the published package, not\nhand-copied from here.\n\n### ComputeMarketplace\n\n`contracts/src/ComputeMarketplace.sol`, `contract ComputeMarketplace is ReentrancyGuard, Governable`.\nThe single-provider job lifecycle, with escrow in SALT or bulk credits, a burn and a treasury fee on\nsettlement, provider staking and slashing, timeouts, and disputes. The constructor takes a verifier and\na treasury address and deploys a fresh `Burner` at construction.\n\n| Function | Notes |\n|---|---|\n| `postJob(bytes32 modelHash, bytes inputHash, uint256 maxPrice, ComputeVerifier.VerificationTier tier, uint256 bidWindow, uint256 execWindow) payable returns (uint256)` | Legacy poster; escrow defaults to SALT. |\n| `postJobWithMethod(bytes32 modelHash, bytes inputHash, uint256 maxPrice, ComputeVerifier.VerificationTier tier, PaymentMethod paymentMethod, uint256 bidWindow, uint256 execWindow) payable returns (uint256)` | Choose `SALT` or `BulkCredits` escrow. |\n| `autoAssignJob(bytes32 modelHash, bytes inputHash, ComputeVerifier.VerificationTier tier) payable returns (uint256)` | One-shot post and assign; requires at least `MIN_AUTO_ASSIGN_PAYMENT` (0.01 SALT). |\n| `bidOnJob(uint256 jobId, uint256 price, uint256 estimatedLatency)` | Operator bids during the bid window. |\n| `assignBestBid(uint256 jobId)` | Permissionless; scores bids by price, reputation, load, and verification fit. |\n| `startExecution(uint256 jobId)` / `submitCommitment(uint256 jobId, bytes32 commitment)` | Assigned provider begins work, or posts a Tier-1 commitment. |\n| `submitResult(uint256 jobId, bytes outputHash, bytes proof)` | Returns output and proof; runs verification inline. |\n| `completeJob(uint256 jobId)` | Settles: provider payment, burn, treasury fee. |\n| `expireJob(uint256 jobId)` | Refunds escrow if no assignment by the bid deadline. |\n| `timeoutJob(uint256 jobId)` | Slashes the assigned provider `TIMEOUT_SLASH_BPS` (5%) on a missed execution deadline. |\n| `failJob(uint256 jobId)` | Provider self-reports failure. |\n| `disputeResult(uint256 jobId) payable` | Files a dispute with a `DISPUTE_BOND` of 10 SALT. |\n| `resolveDispute(uint256 jobId, bool requesterWins)` | `onlyGovernance`. |\n| `registerProvider(bytes32[] supportedModels) payable` / `addStake() payable` | Onboarding; stake at least `MIN_PROVIDER_STAKE` (1000 SALT). |\n\nViews include `getJob`, `getJobBids`, `getProvider`, `getProviderCount`, and `providerSupportsModel`.\nGovernance setters are `setTreasury`, `setSlashingContract`, `setBurner`, `setBulkGateway`, and\n`setPricingOracle`.\n\nThe settlement receipt is one event:\n\n```solidity\nevent JobCompleted(\n uint256 indexed jobId,\n address indexed provider,\n uint256 providerPayment,\n uint256 burned,\n uint256 treasuryFee\n);\n```\n\nThe split is 95% to the provider, 2.5% burned, and 2.5% to the treasury. The burn and the fee each\nequal the price divided by `BME_BURN_DIVISOR` and `TREASURY_DIVISOR`, both 40. The lifecycle is captured\nin `enum JobState { Posted, Bidding, Assigned, Executing, Verifying, Completed, Expired, Timeout, Failed,\nDisputed }`, and the payment choice in `enum PaymentMethod { SALT, BulkCredits }`.\n\n### ComputePool\n\n`contracts/src/ComputePool.sol`, `contract ComputePool is ReentrancyGuard, Governable`. Multi-provider\nGPU pools with per-GPU staking, payment shared in proportion to GPU contribution, slashing on a missed\nthroughput guarantee, and a coordinator election. The constructor takes no arguments; the deployer\nbecomes governance.\n\n| Function | Notes |\n|---|---|\n| `createPool(string name, PoolMode mode, uint256 minProviders, uint256 guaranteedThroughput, uint256 pricePerUnit) returns (uint256 poolId)` | The creator does not auto-join. |\n| `joinPool(uint256 poolId, uint256 gpuCount) payable` | Stake `MIN_STAKE_PER_GPU` (10 SALT) per GPU. |\n| `leavePool(uint256 poolId)` / `dissolvePool(uint256 poolId)` | Leaving requires no active jobs; dissolve is creator-only. |\n| `pausePool(uint256 poolId)` / `resumePool(uint256 poolId)` | Creator only. |\n| `requestPoolCompute(uint256 poolId, bytes jobSpec, uint256 maxPrice) payable returns (uint256 jobId)` | Legacy bytes spec. |\n| `requestPoolComputeStruct(uint256 poolId, PoolJobSpec spec, uint256 maxPrice) payable returns (uint256 jobId)` | Typed spec; requires `spec.version == 1`. |\n| `completeJob(uint256 jobId)` / `failJob(uint256 jobId)` | Callable by governance, the pool creator, or the dispatcher. |\n| `reclaimExpiredJob(uint256 jobId)` | Requester refund after `JOB_DEADLINE` (600 blocks). |\n| `reportSLAViolation(uint256 poolId, uint256 actualThroughput)` | `onlyGovernance`; slashes `SLA_PENALTY_BPS` (10%). |\n| `recordDispatch(uint256 jobId)` / `reassignCoordinator(uint256 jobId)` | The elected coordinator records dispatch; a member can reassign after `COORDINATION_TIMEOUT` (20 blocks). |\n\nViews include `getPool`, `getPoolMembers`, `getPoolGPUCount`, `getMember`, `getJob`, `isPoolSolvent`,\n`coordinatorFor(poolId, epoch)`, and `decodePoolJobSpec`. The pool kinds are `enum PoolMode {\nInferencePool, DataParallel, PipelineParallel }`.\n\n### ComputePoolTraining\n\n`contracts/src/ComputePoolTraining.sol`, `contract ComputePoolTraining is ReentrancyGuard, Governable`.\nA distributed-training lifecycle: recruit workers, commit one Merkle root per epoch, challenge a step\nwith a fraud proof, finalize. Only the per-epoch roots are stored on-chain; the individual step\ncommitments live off-chain in the training mesh. The constructor takes a governance address.\n\n| Function | Notes |\n|---|---|\n| `requestTrainingJob(TrainingJobSpec spec) payable returns (uint256 jobId)` | `msg.value` must equal the per-epoch budget times the epoch count. |\n| `joinTrainingJob(uint256 jobId) payable` | A worker posts the per-worker stake. |\n| `closeRecruitment(uint256 jobId, address coordinator_)` | Permissionless once the minimum workers have joined. |\n| `commitEpoch(uint256 jobId, uint32 epoch, bytes32 root)` | Coordinator commits an epoch's Merkle root; pays that epoch's budget across the workers still in good standing. |\n| `challengeStep(uint256 jobId, uint32 epoch, uint32 step, address target, bytes32 leaf, bytes32[] merkleProof) payable` | Fraud proof; `CHALLENGE_BOND` is 1 SALT. |\n| `voteChallenge(uint256 jobId, uint32 epoch, uint32 step, address target, bool uphold)` | Committee vote; `COMMITTEE_QUORUM` is 2. |\n| `reassignCoordinator(uint256 jobId, address newCoordinator)` | After `COORDINATION_TIMEOUT` (100 blocks). |\n| `finalizeTrainingJob(uint256 jobId)` / `abortRecruiting(uint256 jobId)` | Finalize after the challenge window; abort returns stakes. |\n| `claimDeferredPayout(uint256 jobId)` | Pull-payment fallback when a push payout fails. |\n| `setCommittee(address member, bool active)` | `onlyGovernance`. |\n\nViews include `getJob`, `getWorker`, `getWorkerList`, `getEpochRoot`, `getChallenge`, and `heldStake`.\nThe job states are `enum JobState { Recruiting, Training, Awaiting, Finalized, Aborted }` and a challenge\nruns through `enum ChallengeState { None, Voting, ResolvedUphold, ResolvedReject }`.\n\n### ComputeVerifier\n\n`contracts/src/ComputeVerifier.sol`, `contract ComputeVerifier is ReentrancyGuard, Governable`. The\noutput-verification dispatcher. Every entry point is `onlyMarketplace`: the marketplace configures a\njob's tier, then asks the verifier to rule. There are three tiers, plus a bisection-bounded dispute\npath. The constructor takes the marketplace address.\n\n| Function | Notes |\n|---|---|\n| `configureJob(uint256 jobId, uint256 value, VerificationTier requestedTier)` | Marketplace registers a job's tier. |\n| `overrideTierToTEE(uint256 jobId)` | Forces the TEE tier. |\n| `submitCommitment(uint256 jobId, address provider, bytes32 commitment)` | Tier-1 commit. |\n| `verify(uint256 jobId, VerificationTier tier, bytes proofData) returns (VerificationResult)` | Dispatches to the tier handler. |\n| `verifyCommitment(uint256 jobId, bytes32 commitment, bytes output, bytes32 nonce) returns (bool)` | Tier 1. |\n| `verifyZKProof(uint256 jobId, bytes proof, bytes publicInputs) returns (bool)` | Tier 2; calls the precompile at `0x0108` by `staticcall`. |\n| `verifyTEEAttestation(uint256 jobId, bytes attestation, bytes signature) returns (bool)` | Tier 3; a 65-byte signature recovered against a registered TEE oracle. |\n| `initiateDispute(uint256 jobId)` / `performBisectionStep(uint256 jobId)` | `MAX_BISECTION_ROUNDS` is 10. |\n| `resolveDispute(uint256 jobId, VerificationResult outcome)` | Governance or marketplace. |\n| `addTEEOracle(address)` / `removeTEEOracle(address)` / `setMarketplace(address)` | `onlyGovernance`. |\n\nThe tiers are `enum VerificationTier { Commitment, ZKProof, TEE }` and the verdict is\n`enum VerificationResult { Pending, Valid, Invalid }`. A job above `VALUE_THRESHOLD` (10 SALT) cannot\nsettle on a bare commitment; the verifier upgrades it to the ZK tier. The constant\n`INFERENCE_PROOF_VERIFY = address(0x0108)` points at the Halo2-KZG verifier precompile, described in\n[precompiles](/chain/precompiles). The ZK tier is a research preview (the v1 circuit is small, not a\nfull-model proof), and hardening is in progress. The TEE tier is inert on chain 40204 today because no TEE\noracle is registered with `ComputeVerifier` (`teeOracleCount()` returns 0).\n\n### ComputePricingOracle\n\n`contracts/src/ComputePricingOracle.sol`, `contract ComputePricingOracle is IComputePricingOracle,\nGovernable` (interface at `contracts/src/interfaces/IComputePricingOracle.sol`). A quorum oracle that\nmaps compute cost in USD cents per PFLOP-hour and SALT price in USD cents, by a 67% committee vote, rate\nlimited to a 10% change per update, with staleness tracking. The constructor takes the two opening\nprices, both of which must be above zero.\n\n| Function | Notes |\n|---|---|\n| `addOracleMember(address)` / `removeOracleMember(address)` | `onlyGovernance`. |\n| `proposeComputePrice(uint256 newPrice)` / `proposeSaltPrice(uint256 newPrice)` | `onlyOracle`; the price updates once the vote reaches quorum. |\n| `saltPerPflopHour() view returns (uint256)` | Current SALT cost of one PFLOP-hour. |\n| `computeToSalt(uint256 pflopHours) view returns (uint256 saltCost)` | Converts PFLOP-hours to SALT. |\n| `estimateJobCost(bytes32 modelHash, uint256 inputTokens, uint256 outputTokens, uint8 verificationTier) view returns (uint256 saltCost)` | `modelHash` is reserved for future per-model overrides and is unused today. |\n| `isPriceStale() view returns (bool)` | True after `MAX_STALENESS` (7200 blocks). |\n\nKey constants: `QUORUM = 67`, `MAX_STALENESS = 7200`, `MAX_PRICE_CHANGE_BPS = 1000`,\n`TOKENS_TO_PFLOP_FACTOR = 1e12`, and verification multipliers of 1.0x for Commitment, 1.5x for ZK, and\n2.0x for TEE.\n\n### BulkComputeGateway\n\n`contracts/src/BulkComputeGateway.sol`, `contract BulkComputeGateway is ReentrancyGuard, Governable`.\nLets an institution buy compute credits in PFLOP-hours with a stablecoin, routed to a treasury and\npriced through the oracle; an authorized spender, typically the marketplace, debits those credits. The\nconstructor takes a treasury, an oracle, and a governance address.\n\n| Function | Notes |\n|---|---|\n| `purchaseComputeCredits(address stablecoin, uint256 amount) returns (uint256 creditsReceived)` | `MIN_PURCHASE_USD` is $10.00 at 6 decimals. |\n| `spendCredits(address institution, uint256 creditAmount) returns (bool success)` | `onlyAuthorizedSpender`. |\n| `getCreditBalance(address institution) view returns (uint256 credits)` | Current credit balance. |\n| `estimateCallsRemaining(address institution, uint256 avgTokensPerCall) view returns (uint256)` | Rough call budget. |\n| `currentCreditPriceUsd() view returns (uint256 priceUsd6)` | Credit price in USD at 6 decimals. |\n| `getPurchaseHistory(address institution) view returns (Purchase[])` | Plus `purchaseCount` and `institutionPurchaseCount`. |\n| `authorizeSpender` / `revokeSpender` / `setOracle` / `setTreasury` | `onlyGovernance`. |\n\nThe receipt is `event CreditsPurchased(address indexed institution, address indexed stablecoin, uint256\nusdAmount, uint256 creditsReceived, uint256 purchaseIndex)`.\n\n## Design rationale\n\nThe split between on-chain record and off-chain work is the whole point. A model run is large and\nprivate, so it happens on the operator's own hardware; the ledger keeps only the hash, the proof, and\nthe receipt. That is why ComputeVerifier offers three tiers rather than one. A small job can settle on a\ncheap commitment, a job above the value threshold must carry a zero-knowledge proof checked by the\n`0x0108` precompile (a research preview), and a job that needs hardware attestation is designed to\nrequire a TEE oracle signature (inert on 40204 until an oracle is registered). The buyer\nchooses how much assurance to pay for, and the contract enforces a floor for high-value work.\n\nThe training pool stores only one Merkle root per epoch. Putting every step commitment on-chain would be\nruinous, so the design keeps the steps in the off-chain mesh and lets any worker challenge a step with a\nfraud proof. The chain has to store little and still adjudicate honestly. Pricing sits behind a quorum\noracle rather than a single feed so that no one member can move the price more than 10% or push a stale\nnumber through.\n\n## Failure modes\n\nThese contracts hold escrow and stake, so the failure paths matter.\n\n- **A provider misses the execution deadline.** Anyone calls `timeoutJob`, which slashes 5% of the\n provider's stake. In this build the escrow is refunded to the requester rather than held for\n reassignment; there is no reassignment path yet.\n- **A returned result is wrong.** The verifier fails closed. A ZK proof that reverts, returns the wrong\n length, or returns anything other than the success word is treated as Invalid, so a bad proof never\n settles as valid. Above the value threshold a bare commitment is rejected and upgraded to the ZK tier.\n- **A buyer disputes a result.** They post the 10 SALT dispute bond and the marketplace runs a bounded\n bisection, at most 10 rounds, with governance or the marketplace resolving the outcome.\n- **A pool misses its throughput guarantee.** Governance calls `reportSLAViolation`, which slashes 10%\n of the pool's stake.\n- **A training worker commits a bad step.** A challenger posts the 1 SALT bond and submits a Merkle\n fraud proof; a committee vote at a quorum of 2 upholds or rejects it, and an upheld challenge slashes\n the cheating worker.\n\nThe pool coordinator election depends on an off-chain coordinator binary to record seeds, because the\non-chain `coordinatorFor` derives randomness from a recent block hash and that is only reliable for the\nmost recent few hundred blocks. Off-chain proof generators for both ZK verification and training Merkle\nproofs must match the on-chain wire format exactly, or valid-looking proofs will be rejected.\n\n## Access and canon\n\nTier: commercial for the marketplace, pools, gateway, and oracle, and academic for ComputeVerifier,\nwhich is the formal-methods and proof surface. These contracts are open on the public ledger and anyone\ncan read them; the full lifecycle, scoring, and settlement design is paid-seat depth.\n\nNo secrets appear on this page. There are no private keys, mnemonics, internal hostnames, or\ncredentials. The only hardcoded address is the public protocol precompile `0x0108`. Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity.\n\n## Source and verification\n\n- Source: `citrate-chain/contracts/src/`, in `ComputeMarketplace.sol`, `ComputePool.sol`,\n `ComputePoolTraining.sol`, `ComputeVerifier.sol`, `BulkComputeGateway.sol`, `ComputePricingOracle.sol`,\n and the interface `interfaces/IComputePricingOracle.sol`.\n- Audited against `citrate-chain` SHA `9d5959e`.\n- Status: Implemented, pre-audit, on testnet 40204. The timeout-reassignment gap, the off-chain\n coordinator-seed dependency, and the assumed 6-decimal stablecoins are open items noted above and not\n yet externally audited. Re-verify deployed bytecode with `eth_getCode` if the chain has been re-rolled.\n"},"/contracts/economics":{"slug":"/contracts/economics","title":"Economics contracts","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/contracts/src/{WrappedSALT,LiquidStakingPool,IPFSIncentives,ContributionAccounting,StablecoinTreasury,MarketMakerAllocation}.sol","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"WrappedSALT","anchor":"wrappedsalt"},{"depth":3,"text":"LiquidStakingPool","anchor":"liquidstakingpool"},{"depth":3,"text":"IPFSIncentives","anchor":"ipfsincentives"},{"depth":3,"text":"ContributionAccounting","anchor":"contributionaccounting"},{"depth":3,"text":"StablecoinTreasury","anchor":"stablecointreasury"},{"depth":3,"text":"MarketMakerAllocation","anchor":"marketmakerallocation"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"These are the on-premise economic primitives that settle work on the Citrate Network: a wrapped form of\nSALT that signed payments can move, a staking pool that lets staked SALT stay liquid, storage incentives\nfor content kept on IPFS, an accounting contract that tracks the work done so rewards follow it, a treasury\nfor stablecoin revenue, and an allocation that funds the network's market maker. This page is for the\ndevelopers and integrators building against those contracts on chain id 40204.\n\n## What it is\n\nSALT is the unit the Citrate Network counts in. It settles the work the network performs; it is not a\nproduct to hold. The contracts here are the on-chain machinery that records that work and pays it out, and\nnothing on this page treats SALT as an instrument to speculate on. For the base facts about SALT, supply,\nand gas, read [network economics](/chain/economics).\n\nEach contract does one job:\n\n| Surface | Contract | What it settles |\n|---|---|---|\n| SC-econ-wrappedSALT | WrappedSALT (wSALT) | native SALT wrapped as an ERC-20 so a signed authorization can move it |\n| SC-econ-staking | LiquidStakingPool (stSALT) | SALT staked into a pool, with a liquid share token in return |\n| SC-econ-ipfs | IPFSIncentives | rewards for operators who pin model and data content on IPFS |\n| SC-econ-contrib | ContributionAccounting | the record of seven kinds of work, weighted, with rewards split by score |\n| SC-econ-stable | StablecoinTreasury | stablecoin revenue from institutional compute purchases |\n| SC-econ-mm | MarketMakerAllocation | the gas-fee share that funds the network's market maker |\n\nThe first four are public; a builder needs them and their on-chain interfaces are public anyway.\nStablecoinTreasury and MarketMakerAllocation are tier commercial: the contract bytecode is public on chain,\nbut the narrative around the network's revenue and its market-maker arrangement is written for contracted\nprincipals. No keys or private endpoints appear on this page.\n\n## How to use it\n\nYou read and call these contracts the same way you would any contract on an EVM-compatible network.\n\n1. Point a client at the Citrate Network and confirm the chain id is 40204, as shown in\n [what Citrate is](/start/what-is-citrate).\n2. Resolve the address you want from the canonical registry, `contracts/addresses/40204.json`, rather than\n copying an address from prose. The keys there are the contract names used below.\n3. For a read, call a view function over `eth_call`. For a write, send a transaction signed by an account\n that holds the right role; the access column in each table below tells you which.\n4. Confirm an address holds the code you expect with `eth_getCode` before you send value to it.\n\nFor a network-wide snapshot of supply, gas price, staked amount, and treasury in a single call, use\n`citrate_getEconomicState`, documented in [chain RPC](/chain/rpc).\n\n## Reference\n\nEvery function below is read from the cited `.sol` file at the audited SHA. If a symbol is not listed here,\nit is not in the contract at this SHA.\n\n### WrappedSALT\n\n`contracts/src/WrappedSALT.sol`, `is IERC3009, ReentrancyGuard`. An ERC-20 wrapper around native SALT,\n`name=\"Wrapped SALT\"`, `symbol=\"wSALT\"`, `decimals=18`. It adds EIP-3009 authorized transfers so a holder\ncan sign a transfer that someone else submits, which is what the x402 payment flow relies on. The EIP-712\ndomain separator is rebuilt whenever `block.chainid` changes, so an authorization signed before a re-genesis\ncannot be replayed against the new chain.\n\n| Function | Purpose |\n|---|---|\n| `deposit()` / `receive()`, payable | wrap native SALT into wSALT |\n| `withdraw(uint256 amount)` | unwrap wSALT back into native SALT |\n| `transfer`, `approve`, `transferFrom` | standard ERC-20 |\n| `transferWithAuthorization(from, to, value, validAfter, validBefore, nonce, v, r, s)` | EIP-3009 signed transfer; the holder signs, anyone submits |\n| `transferWithFeeAuthorization(from, to, treasury, value, fee, validAfter, validBefore, nonce, v, r, s)` | one signed authorization for the gross `value`, split inside the contract into `value - fee` to `to` and `fee` to `treasury` |\n| `receiveWithAuthorization(...)` | pull-style EIP-3009 transfer; the caller must be the payee |\n| `cancelAuthorization(authorizer, nonce, v, r, s)` | cancel an unused authorization |\n| `DOMAIN_SEPARATOR()` | the current-chain EIP-712 domain separator |\n| `authorizationState(authorizer, nonce)` | whether a nonce has been used or cancelled |\n\nThe fee-bearing path binds `(treasury, fee)` into the signed digest through a distinct type hash,\n`TRANSFER_WITH_FEE_AUTHORIZATION_TYPEHASH`, so a submitter cannot redirect the fee leg to another address.\nEvents: `Transfer`, `Approval`, `Deposit`, `Withdrawal`, `AuthorizationUsed`, `AuthorizationCanceled`. The\nerror `InvalidFeeAuthorization` reverts a fee transfer whose signature does not recover to `from`.\n\n### LiquidStakingPool\n\n`contracts/src/LiquidStakingPool.sol`, `is ReentrancyGuard, Governable`. A shares-based staking pool,\n`name=\"Staked SALT\"`, `symbol=\"stSALT\"`, `decimals=18`. You deposit SALT and receive stSALT shares; rewards\nreported to the pool raise the value each share is worth, so the staked SALT stays usable as a share token\nwhile it earns. Withdrawals wait out `WITHDRAWAL_DELAY` blocks before they can be claimed.\n\n| Function | Access | Purpose |\n|---|---|---|\n| `deposit()`, payable | any | stake SALT, mint stSALT shares, returns `sharesOut` |\n| `requestWithdrawal(uint256 shareAmount)` | any | burn shares and queue the SALT release, returns `requestId` |\n| `claimWithdrawal(uint256 requestId)` | the requester | claim once `WITHDRAWAL_DELAY` has passed |\n| `reportRewards(uint256 rewards, uint256 slashed)` | an oracle | report rewards or slashing; applied at quorum |\n| `depositCollateral()` / `withdrawCollateral(uint256)` | a compute provider | post or remove provider collateral |\n| `slashProvider(address, uint256)` | governance | slash a provider's collateral back into the pool |\n| `donate()`, payable | any | add SALT without changing the share price |\n| `addOracle(address)` / `removeOracle(address)` | governance | manage the oracle committee |\n| `getSharePrice()`, `balanceOf(address)`, `previewDeposit(uint256)`, `previewWithdraw(uint256)` | view | share and value math |\n| `transferGovernance` / `acceptGovernance` | inherited | two-step governance handover |\n\nConstants: `WITHDRAWAL_DELAY = 50400` blocks, about seven days; `ORACLE_QUORUM = 67`, the percentage of the\ncommittee that must agree before a report applies; `MIN_COLLATERAL_BPS = 1000`; `MAX_REWARD_RATE_BPS =\n20000`; `MAX_SLASH_RATE_BPS = 1000`. Oracle reports carry a per-nonce replay guard, the committee must agree\non the same `(rewards, slashed)` values, and the caps bound how much any single report can move the pool.\nEvents: `Deposited`, `WithdrawalRequested`, `WithdrawalClaimed`, `RewardsReported`, `ProviderSlashed`,\n`OracleAdded`, `OracleRemoved`, `CollateralDeposited`, `CollateralWithdrawn`, `Donated`.\n\n### IPFSIncentives\n\n`contracts/src/IPFSIncentives.sol`, `is AccessControl, ReentrancyGuard`. This is the deployed storage\nincentive, a report-and-claim model. An authorized reporter attests that an operator pinned a content id of\na given size and model type, the contract accrues a reward for that pin, and the operator later withdraws\nthe accrued SALT.\n\n| Function | Access | Purpose |\n|---|---|---|\n| `reportPinning(string cid, uint256 sizePinned, ModelType modelType)` | `REPORTER_ROLE` | attest a pin and accrue its reward |\n| `claimRewards()` | any | withdraw accrued SALT |\n| `depositRewards()`, payable | `DEFAULT_ADMIN_ROLE` | fund the reward pool |\n| `updateBaseReward(uint256 newRate)` | `DEFAULT_ADMIN_ROLE` | set the base reward rate |\n| `calculateReward(uint256 sizePinned, ModelType)` | view | preview a reward |\n| `getModelPinners(string cid)` | view | the operators pinning a content id |\n\nEvents: `PinReported`, `RewardClaimed`, `BaseRewardUpdated`, `RewardsDeposited`. Two later designs exist in\nthe tree, a sealed proof-of-replication mechanism (`IPFSIncentivesV2.sol`) and a commit-reveal extension of\nit (`IPFSIncentivesV3.sol`) with a grief-slashable wrong-CommD challenge. Both are now listed in the\ncanonical registry (`contracts/addresses/40204.json`); this page documents version one's surface, and the\nsource and verification note points to the later versions.\n\n### ContributionAccounting\n\n`contracts/src/ContributionAccounting.sol`, `is Governable`. This is the record that lets rewards follow\nwork. It tracks contributions across seven types, weights each type, keeps a cached score per contributor,\nand splits a funded pool in proportion to those scores. The seven types are `Validation`, `ModelHosting`,\n`AdapterCreation`, `DataProvision`, `AppDevelopment`, `BridgeInfra`, and `Governance`. Weights are basis\npoints, where 10000 is a one-times multiplier.\n\n| Function | Access | Purpose |\n|---|---|---|\n| `recordContribution(address contributor, ContributionType ctype, uint256 amount)` | a recorder or governance | record work and refresh the contributor's score |\n| `recordDimensionContribution(address contributor, bytes32 dimension, uint256 amount)` | a recorder or governance | record a per-dimension running total |\n| `fundRewards()`, payable | any | add SALT to the reward pool |\n| `distributeRewards()` | governance | freeze each contributor's share of the pool by score |\n| `claimRewards()` | any | withdraw a claimable balance |\n| `updateWeight(ContributionType ctype, uint256 newWeight)` | governance | change a type's weight and rescore every contributor |\n| `addRecorder(address)` / `removeRecorder(address)` | governance | manage the recorder allowlist |\n| `getScore(address)`, `pendingReward(address)`, `contributorCount()`, `getContributorListPage(offset, limit)`, `getDimensionScore(address, bytes32)` | view | read scores, pending shares, and the contributor list |\n\nThe active contributor list is capped at `MAX_CONTRIBUTORS = 1024` so distribution and rescoring stay within\na bounded gas budget. `distributeRewards` allocates shares first and lets each contributor claim later, so\nclaim order does not change anyone's amount. Events: `ContributionRecorded`,\n`DimensionContributionRecorded`, `RewardsDistributed`, `RewardClaimed`, `WeightUpdated`, `RecorderAdded`,\n`RecorderRemoved`.\n\n### StablecoinTreasury\n\n`contracts/src/StablecoinTreasury.sol`, `is ReentrancyGuard, Governable`. Tier commercial. It accumulates\nstablecoins from institutional compute purchases across an allowlist of accepted tokens, tracks revenue and\nactivity per epoch, and distributes to recipients under governance control. No SALT is involved; it moves\nERC-20 stablecoins only, and assumes each accepted token is pegged one-to-one to the US dollar.\n\n| Function | Access | Purpose |\n|---|---|---|\n| `deposit(address stablecoin, uint256 amount)` | any | deposit an accepted stablecoin |\n| `distribute(address stablecoin, address[] recipients, uint256[] amounts)` | governance | distribute to a length-matched list of recipients |\n| `addStablecoin(address)` / `removeStablecoin(address)` | governance | manage the accepted-token allowlist |\n| `recordActivity(uint256 jobCount, uint256 inferenceCount)` | an authorized activity recorder | record per-epoch compute and inference counts |\n| `emergencyWithdraw(address stablecoin, address to)` | governance | move a stablecoin's full balance to a governance address |\n| `totalValueLocked()`, `stablecoinCount()`, `getAcceptedStablecoins()`, `getEpochRevenue(uint256)`, `getCurrentEpoch()` | view | read balances, the allowlist, and epoch data |\n\nAn epoch is `EPOCH_LENGTH = 1000` blocks; the allowlist is capped at `MAX_STABLECOINS = 20`. Events:\n`StablecoinAdded`, `StablecoinRemoved`, `Deposited`, `Distributed`, `EmergencyWithdrawal`, `EpochAdvanced`,\n`ActivityRecorderSet`, `ActivityRecorded`.\n\n### MarketMakerAllocation\n\n`contracts/src/MarketMakerAllocation.sol`, `is Governable`. Tier commercial. It receives a share of gas-pool\nfees, skimmed before the network's wider revenue split, to fund the market maker who provides liquidity and\nhandles listings. The market maker withdraws the accumulated SALT; governance can change the rate and the\nmarket-maker address.\n\n| Function | Access | Purpose |\n|---|---|---|\n| `receive()`, payable | the block producer or revenue distributor | take the gas-fee allocation |\n| `withdraw(uint256 amount)` | the market maker | withdraw part of the accrued SALT |\n| `withdrawAll()` | the market maker | withdraw the full balance |\n| `changeMarketMaker(address newMaker, string reason)` | governance | replace the market-maker address with a recorded reason |\n| `changeAllocationRate(uint256 newBps)` | governance | change the skim rate within its bounds |\n| `calculateAllocation(uint256 gasFees)` | view | preview the skim and the remainder |\n| `availableBalance()`, `changeHistoryCount()` | view | read the balance and the change history |\n| `transferGovernance` / `acceptGovernance` | inherited | two-step governance handover |\n\nThe default rate is `allocationBps = 1000`, ten percent. It is bounded between `MIN_ALLOCATION_BPS = 100` and\n`MAX_ALLOCATION_BPS = 1500`, and a rate change is held off until `RATE_CHANGE_COOLDOWN = 302400` blocks,\nabout seven days, have passed since the last change. Events: `AllocationReceived`, `Withdrawn`,\n`MarketMakerChanged`, `AllocationRateChanged`.\n\n## Design rationale\n\nThe shape of these contracts follows from one rule: pay for work that happened, and keep the accounting\nhonest while you do it. The staking pool uses a shares model so a staker's claim grows with reported rewards\nwithout a separate bookkeeping pass, and it holds the share price steady against a first-depositor\nmanipulation by computing shares against a virtual offset rather than the raw balance. The storage incentive\nkeeps the deployed version a simple report-and-claim, because the sealed-proof and commit-reveal designs add\nreal complexity and are not yet the live mechanism. ContributionAccounting freezes each round's shares at\ndistribution time and bounds its contributor set, so a reward split cannot be reordered for advantage or\npriced out by gas. The two commercial contracts are kept narrow and governance-gated because they touch the\nnetwork's revenue and a single strategic relationship, and the cooldown and rate bounds on the market-maker\nskim keep that relationship from quietly drifting.\n\n## Failure modes\n\nThese contracts move value, so the relevant question is how each one fails closed.\n\n- WrappedSALT will not unwrap more than an account holds, and the fee path reverts with\n `InvalidFeeAuthorization` rather than settling if the signed digest does not recover to `from`. The\n rebuilt domain separator means an authorization signed before a chain-id change cannot be replayed after\n one.\n- LiquidStakingPool applies a reward report only when a 67 percent quorum agrees on the same values, rejects\n a report that exceeds its caps, and rejects unsolicited SALT through `receive()` so that the only ways in\n are `deposit()`, which mints shares, and `donate()`, which does not move the share price.\n- ContributionAccounting refuses a new contributor once the cap is reached rather than running an unbounded\n loop, and recomputes scores on a weight change so the split stays consistent.\n- StablecoinTreasury distributes only up to the recorded balance and requires recipient and amount arrays of\n equal length; its emergency path moves funds to a governance address, not an arbitrary one.\n- MarketMakerAllocation caps the skim rate and enforces a cooldown between changes, so a single change cannot\n push the allocation past its bounds or be repeated rapidly.\n\n## Access and canon\n\nWrappedSALT, LiquidStakingPool, IPFSIncentives, and ContributionAccounting are public, the open primitives a\nbuilder needs, with on-chain interfaces that are public by nature. StablecoinTreasury and\nMarketMakerAllocation are tier commercial: the deployed bytecode and the interface are public on chain, but\nthe narrative around the network's revenue and its market-maker arrangement is written for contracted\nprincipals. No keys, mnemonics, private endpoints, or credentials appear on this page or are needed to read\nthese contracts. The deployed addresses are public testnet values.\n\n## Source and verification\n\n- Source repo: `citrate-chain` at SHA `9d5959e`.\n- Files: `contracts/src/WrappedSALT.sol`, `contracts/src/LiquidStakingPool.sol`,\n `contracts/src/IPFSIncentives.sol`, `contracts/src/ContributionAccounting.sol`,\n `contracts/src/StablecoinTreasury.sol`, `contracts/src/MarketMakerAllocation.sol`.\n- Addresses: the canonical registry is `contracts/addresses/40204.json` for chain 40204. At this SHA it\n lists all three, `IPFSIncentives` (version one), `IPFSIncentivesV2`, and `IPFSIncentivesV3`; the deploy\n script `contracts/script/DeployAll.s.sol` deploys version one, and V2 and V3 are deployed and registered\n separately. The superseded `contracts/DEPLOYED_ADDRESSES.md` is no longer the source of truth, so resolve\n from `addresses/40204.json` and confirm with `eth_getCode` before sending value.\n- Status: Implemented, pre-audit. The contracts run on testnet 40204 and carry remediations from the\n SECREM-01 and re-audit sprints in their source comments, with Foundry invariant suites in places, but no\n external third-party audit has been completed. Treat as experimental.\n"},"/contracts/governance":{"slug":"/contracts/governance","title":"Governance Contracts","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/contracts/src/{TreasuryGovernor,DisputeResolution,AgentDecisionRegistry,SpecRegistry}.sol","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"TreasuryGovernor","anchor":"treasurygovernor"},{"depth":3,"text":"DisputeResolution","anchor":"disputeresolution"},{"depth":3,"text":"AgentDecisionRegistry","anchor":"agentdecisionregistry"},{"depth":3,"text":"SpecRegistry","anchor":"specregistry"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The governance surface is four contracts that decide how the network spends its treasury, how it settles a\ndisputed compute result, and how it keeps an accountable record of what its agents do. They sit on the\npublic ledger, Citrate Network, and any contracted integrator should expect to read and call them.\n\n## What it is\n\nGovernance on Citrate is not one monolith. It is four small contracts, each with one job, so that a\ntreasury vote, a compute dispute, an agent audit record, and a formal-specification pin never share a\nfailure surface. Three of the four inherit a common ownership mixin, `Governable`, which hands authority\nover in two steps: the sitting governance account proposes a successor with `transferGovernance`, and the\nsuccessor must call `acceptGovernance` before it takes effect, with `cancelGovernanceTransfer` available\nbefore acceptance. A mistyped or unreachable successor therefore cannot lock governance, because the\nhandover does not complete until the new account acts.\n\n| Contract | Role | Status |\n|---|---|---|\n| `TreasuryGovernor` | On-chain governor for SALT and stSALT treasury spending | Implemented, pre-audit |\n| `DisputeResolution` | Bisection game that settles a challenged compute result | Implemented, TLA+ specified |\n| `AgentDecisionRegistry` | Audit trail of high-risk agent tool calls, with trust tiers | Implemented, pre-audit |\n| `SpecRegistry` | Domain to IPFS map of the behavioral specs agents check | Implemented, pre-audit |\n\nThe four are pre-audit. They carry inline remediation notes from internal review, for example the SOL-21\ngovernance follow-on, but they have not completed a final third-party audit. Treat every address and\nparameter as subject to change before mainnet.\n\n## How to use it\n\nMost readers will interact with one contract at a time. The two paths worth walking end to end are a\ntreasury spend and a compute dispute.\n\n1. **Move treasury funds.** Acquire the voting power, `PROPOSAL_THRESHOLD` is 10,000 SALT, then call\n `proposeTreasurySpend`. Voters call `castVote` over the voting window. After the window, call `queue`,\n wait out `EXECUTION_DELAY`, then call `execute`. Two proposal types execute on-chain: `TreasurySpend`\n calls `treasury.distribute`, and `Call` performs a generic `(target, value, calldata)` call so the\n governor can drive any governance function on a target it controls. `ParameterChange`, `OracleUpdate`,\n and `Emergency` emit an event for an off-chain multisig to act on.\n2. **Dispute a compute result.** As the challenger, call `initiateDispute` with the job and the step range,\n posting the bond. The defender calls `acknowledgeDispute` with a matching bond. The challenger narrows\n the range with `bisect` each round, the defender commits a step with `respond`, and the governance\n referee ends it with `resolve`. If either side stalls past the deadline, anyone calls `timeoutDispute`\n and the challenger wins.\n3. **Record an agent decision.** An authorized recorder calls `registerDecision` (or\n `registerDecisionWithTierCheck`) with the agent id, the tool name, and a hash of the parameters. An\n authorized disputer can file against it; governance resolves with `resolveDispute`.\n4. **Pin a behavioral spec.** Governance calls `registerSpec` with the operation domain and the IPFS CID of\n the Gherkin feature file, and `updateSpec` to bump the version. Agents read `getSpec` before a critical\n operation.\n\n## Reference\n\nThe audited surface, each item citing its source file under `contracts/src/`.\n\n### TreasuryGovernor\n\nSource: `contracts/src/TreasuryGovernor.sol`. A full on-chain governor for treasury operations. Voting\npower is the voter's native SALT balance plus their stSALT shares valued at the share price read from\n`LiquidStakingPool`. The lifecycle is propose, vote, queue behind a timelock, then execute. Parameters are\nfixed as constants and match `core/economics/src/governance.rs`.\n\n| Constant | Value | Meaning |\n|---|---|---|\n| `PROPOSAL_THRESHOLD` | 10,000 SALT | Minimum voting power to open a proposal |\n| `VOTING_PERIOD` | 50,400 blocks | Voting window, measured in blocks (wall-clock depends on block time) |\n| `EXECUTION_DELAY` | 7,200 blocks | Timelock before a queued proposal can execute |\n| `QUORUM_BPS` | 1,000 (10%) | Quorum as basis points of total supply |\n| `APPROVAL_BPS` | 6,000 (60%) | Approval threshold as basis points of votes cast |\n| `GRACE_PERIOD` | 50,400 blocks | Window to execute before a queued proposal expires |\n| `EMERGENCY_THRESHOLD_MULTIPLIER` | 3 | Emergency proposals need three times the threshold |\n\nProposal creation, each `payable`, each returning a `proposalId`:\n\n- `proposeTreasurySpend(title, description, stablecoin, recipients[], amounts[])`\n- `proposeParameterChange(title, description, parameterKey, parameterValue)`\n- `proposeOracleUpdate(title, description, target, newOracle)`\n- `proposeCall(title, description, target, value, data)`, generic on-chain execution against a governed target\n- `proposeEmergency(title, description)`, which requires three times the threshold\n\nVoting and lifecycle:\n\n- `castVote(proposalId, support)`, where `VoteType` is `For` (0), `Against` (1), `Abstain` (2)\n- `queue(proposalId)`, only from the `Succeeded` state\n- `execute(proposalId)`, guarded by `nonReentrant`, re-checks quorum and approval, runs the treasury call\n- `cancel(proposalId)`, by the proposer or the guardian\n- `acceptGovernanceOf(target)`, a permissionless completion of a `Governable` handover where this governor\n is the pending successor\n- `transferGuardian(newGuardian)`, `onlyGuardian`\n\nViews: `state`, `getVotingPower`, `getProposal`, `quorumThreshold`, `getSpendDetails`. Events:\n`ProposalCreated`, `VoteCast`, `ProposalQueued`, `ProposalExecuted`, `ProposalCanceled`,\n`GuardianTransferred`.\n\n### DisputeResolution\n\nSource: `contracts/src/DisputeResolution.sol`. A bisection game for challenged compute jobs, inheriting\n`ReentrancyGuard` and `Governable`. The constructor takes a dispute bond and a maximum bisection-round\ncount. The design is written down and checked: the contract header cites `DisputeResolution.tla` with nine\ninvariants and the `AdversarialCompute.tla` properties, including that griefing is never profitable.\n\nFlow:\n\n- `initiateDispute(jobId, defender, rangeStart, rangeEnd)`, `payable`, `nonReentrant`; the challenger posts\n the bond\n- `acknowledgeDispute(disputeId)`, `payable`; the defender posts a matching bond\n- `bisect(disputeId, claimFaulty)`, the challenger halves the range each round\n- `respond(disputeId, stepResultHash)`, the defender commits a step result\n- `resolve(disputeId, challengerWins)`, `onlyGovernance`; pays the winner and, if the challenger wins,\n slashes the defender through the `INematocystSlashing` interface at the `Inconsistency` tier\n- `timeoutDispute(disputeId)`, callable by anyone after the deadline; the challenger wins\n\nAdmin, all `onlyGovernance`: `setDisputeBond`, `setMaxBisectionRounds`, `setSlashingContract`,\n`setRoundDeadline`. The constant `DEFAULT_ROUND_DEADLINE` is 150 blocks. Views: `getDispute`,\n`isDisputeActive`, `getRangeSize`.\n\nThe slash call is wrapped in `try / catch`, so a missing or reverting slashing contract does not block the\ndispute from resolving and paying the winner. The slashing target itself is documented under\n[security contracts](/contracts/security).\n\n### AgentDecisionRegistry\n\nSource: `contracts/src/AgentDecisionRegistry.sol`. An on-chain audit trail of high-risk agent tool calls,\ninheriting `Governable`. Each record holds an agent id, the tool name, a parameters hash, the block and\ntimestamp, the executing address, a status, and any dispute evidence. From the record count and the dispute\ncount it derives a trust score, decisions minus twice disputes, and a tier: `Untrusted` below 100,\n`Standard` from 100 to under 500, `Trusted` at 500 or above.\n\nFunctions:\n\n- `registerDecision(agentId, toolName, paramsHash)`, `onlyAuthorizedRecorder`\n- `disputeDecision(decisionId, evidence)`, `onlyAuthorizedDisputer`\n- `resolveDispute(decisionId, upheld)`, `onlyGovernance`\n- `registerDecisionWithTierCheck` and `disputeDecisionWithTierCheck`, which do the same work and also emit\n `TrustTierChanged` when a tier boundary is crossed\n- `setAuthorizedRecorder(recorder, allowed)` and `setAuthorizedDisputer(disputer, allowed)`, `onlyGovernance`\n\nViews: `getDecisionHistory`, `getDecisionCount`, `getDisputeStatus`, `getTrustScore`, `getTrustTier`.\nEvents include `DecisionRecorded`, `DecisionDisputed`, `DisputeResolved`, and `TrustTierChanged`. The\ncontract reverts with `NotAuthorizedRecorder`, `NotAuthorizedDisputer`, or `ZeroAddress`.\n\nA second contract, `AgentDecisionRegistryV2`, lives at `contracts/src/rbac/AgentDecisionRegistryV2.sol`. It\nis a separate, newer design, not a drop-in replacement: it logs every signed action with a fuller shape\n(user, tenant, correlation id, event class, artifact root) and is append-only against the\n`AgentDecisionLog.tla` spec. It ships as a new contract with its own migration path; the V1 contract above\nis the one this page documents as the governance audit trail, and it remains current.\n\n### SpecRegistry\n\nSource: `contracts/src/SpecRegistry.sol`. A map from an operation domain, for example `\"contract_deploy\"`,\nto the IPFS CID of a Gherkin `.feature` file that agents must check before a critical operation. It inherits\n`Governable`; an earlier version had its own single-step `transferGovernor`, which was removed in the\nRM-L/WP-L1.1 migration in favor of the two-step mixin.\n\nMutators, all `onlyGovernance`: `registerSpec(domain, cid)`, `updateSpec(domain, newCid)` which bumps the\nversion, `deactivateSpec(domain)`, `reactivateSpec(domain)`. Views: `getSpec` returning cid, active flag,\nand version; `hasActiveSpec`; `getAllDomains`. Events: `SpecRegistered`, `SpecUpdated`, `SpecDeactivated`,\n`SpecReactivated`.\n\nA treasury spend, start to finish:\n\n```solidity\nuint256 id = governor.proposeTreasurySpend(\n \"Grant: Q3 dev fund\", \"...\", usdc, recipients, amounts\n);\ngovernor.castVote(id, TreasuryGovernor.VoteType.For);\n// voting period elapses\ngovernor.queue(id);\n// EXECUTION_DELAY blocks elapse\ngovernor.execute(id);\n```\n\n## Design rationale\n\nWe split governance into four contracts rather than one because the failure modes are different in kind. A\ntreasury vote is slow and deliberate and gated on capital. A compute dispute is fast and adversarial and\ngated on a bond. An agent audit record is high-volume and append-only. A spec pin is rare and governed.\nFolding them together would mean one upgrade, one bug, and one blast radius covering all four. Keeping them\napart costs a little duplication and buys independent review and independent upgrade.\n\nThe dispute game is bisection rather than full re-execution because re-running a long computation on chain\nis not affordable. Bisection narrows the disagreement to a single step in a logarithmic number of rounds,\nand only that step needs adjudication. The design is written in TLA+ first so the property that matters,\nthat an attacker who griefs always loses net SALT, is checked before the code is trusted. That formal work\nis described under [research](/research/learning).\n\n## Failure modes\n\n- **Governance handover.** A handover that names the wrong successor does not complete, because the\n successor must call `acceptGovernance`. The system stays under the current governance until a real\n successor accepts. This is the point of the two-step mixin.\n- **Stalled dispute.** If a party goes silent, the dispute does not hang. After the round deadline anyone\n calls `timeoutDispute` and the challenger wins, so a defender cannot escape a losing position by waiting.\n- **Slashing dependency.** `DisputeResolution` calls the slashing contract inside `try / catch`. If the\n slashing target is unset or reverts, the dispute still resolves and the winner is still paid; only the\n stake penalty is skipped, and it can be applied once the dependency is fixed.\n- **Treasury execution.** `execute` re-checks quorum and approval at execution time, not only at proposal\n time, and is `nonReentrant`. A proposal that lost or that has expired past the grace period cannot be\n forced through.\n\n## Access and canon\n\nTier: commercial. These are deep governance contracts. An anonymous copy would materially help a competitor\nclone the network's economic and dispute machinery, so the full detail is served to contracted builders,\nnot published openly. There are no secrets here, no keys and no private endpoints; addresses are not yet\nlisted because the contracts are pre-audit and pre-deployment.\n\nThe agent-safety and formal-methods pieces, `AgentDecisionRegistry` and `SpecRegistry`, are the on-chain\nedge of the research surface. The behavioral specs they pin and the TLA+ work behind `DisputeResolution`\nlive under [research](/research/learning). Slashing and finality are covered under\n[Citrate Network consensus](/chain/consensus).\n\n## Source and verification\n\n- Source repo: `citrate-chain`, files under `contracts/src/`: `TreasuryGovernor.sol`,\n `DisputeResolution.sol`, `AgentDecisionRegistry.sol`, `SpecRegistry.sol`, plus\n `contracts/src/rbac/AgentDecisionRegistryV2.sol` for the V2 note and `contracts/src/lib/Governable.sol`\n for the ownership mixin.\n- Audited against `citrate-chain` SHA `9d5959e`.\n- Status by contract: `TreasuryGovernor` Implemented, pre-audit; `DisputeResolution` Implemented and TLA+\n specified (`DisputeResolution.tla`, `AdversarialCompute.tla`), pre-audit; `AgentDecisionRegistry`\n Implemented, pre-audit, with `AgentDecisionRegistryV2` Implemented and TLA+ specified\n (`AgentDecisionLog.tla`); `SpecRegistry` Implemented, pre-audit. None has completed a final third-party\n audit. Verify deployed bytecode yourself with `eth_getCode` once addresses are published.\n"},"/contracts/models":{"slug":"/contracts/models","title":"Model contracts","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/contracts/src (ModelRegistry.sol, ModelMarketplace.sol, LoRAFactory.sol, ModelAccessControl.sol, InferenceRouter.sol, interfaces/IModelRegistry.sol, interfaces/IModelMarketplace.sol)","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"ModelRegistry","anchor":"modelregistry"},{"depth":3,"text":"InferenceRouter","anchor":"inferencerouter"},{"depth":3,"text":"ModelMarketplace","anchor":"modelmarketplace"},{"depth":3,"text":"LoRAFactory","anchor":"lorafactory"},{"depth":3,"text":"ModelAccessControl","anchor":"modelaccesscontrol"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"These are the contracts that register a model on the public ledger, sell access to it, route inference\nto the operators that serve it, and build adapters on top of it. A model is registered once and from\nthere it can be listed, gated, adapted, and called. This page is for model owners, inference consumers,\nand integrators.\n\n## What it is\n\nA model on Citrate is a record, not a file. The weights stay where the owner put them, usually behind an\nIPFS CID; the public ledger keeps the model's identity, its owner, its price, and a count of the work it\nhas done. Inference itself runs through the runtime [precompiles](/chain/precompiles); the contracts\nhere charge for it, route it, and gate it.\n\nFive contracts cover the model economy:\n\n- **ModelRegistry** is the root record. Registering a model yields a `modelHash` and proxies inference\n through the model precompile.\n- **InferenceRouter** load-balances inference across staked operators, with a cache and a refund path.\n- **ModelMarketplace** lets owners list a model and sell access, with bulk discounts and a treasury fee.\n- **LoRAFactory** builds, trains, merges, and cryptographically verifies low-rank adapters on top of a\n base model.\n- **ModelAccessControl** is a separate, tiered access registry with paid or approval-based grants,\n staking, revenue sharing, and an encrypted-inference path.\n\nA point worth holding onto: there are two access systems here, and they are not wired together.\nModelRegistry, the marketplace, the router, and the factory share one role-based registry. ModelAccessControl\nis a standalone contract with its own level-based grants. Pick the one your integration targets and stay\nin it.\n\nYou can register and call a model from the RPC surface with `citrate_deployModel` and read the catalog\nwith `citrate_getModels`; see [chain RPC](/chain/rpc). Federated training that produces models lives on\n[Citrate Orchard](/research/learning), which is a separate surface from the adapter factory described\nbelow.\n\n## How to use it\n\nThe shortest path from nothing to a paid inference call.\n\n1. Register the model. Call `registerModel(...)` on **ModelRegistry** with the name, framework, version,\n IPFS CID, size, an inference price, and the metadata struct. Send at least `REGISTRATION_FEE`\n (0.1 SALT). You get back the `modelHash`.\n2. Decide who can call it. For a simple owner-priced model, `requestInference` on the registry already\n charges and forwards payment to you. To sell in a catalog with reviews and discounts, list it on\n **ModelMarketplace**. For tiered or approval-gated access with staking, register it instead on\n **ModelAccessControl**.\n3. Serve it. Operators register on **InferenceRouter** with `registerProvider(endpoint, minPrice,\n supportedModels)`, staking at least the minimum, and the router scores and assigns requests across\n them.\n4. Call it. A consumer calls `requestInference(modelHash, inputData, maxPrice)` on the router. A cache\n hit returns the stored output with a partial refund; otherwise the assigned operator returns the\n result with `completeInference` and is paid, with any excess refunded.\n5. Adapt it, optionally. Build a LoRA adapter on the base model with `createLoRA(...)` on\n **LoRAFactory**, have an operator finish training, and verify the adapter against the inference proof\n precompile before anyone relies on it.\n\n## Reference\n\nThe audited public surface of each contract, with function names as they appear in source. The\ncanonical truth is the Solidity in `contracts/src`; treat ABIs as coming from the published package, not\nhand-copied from here.\n\n### ModelRegistry\n\n`contracts/src/ModelRegistry.sol`, `contract ModelRegistry is IModelRegistry, AccessControl,\nReentrancyGuard` (the project-local `AccessControl` and `ReentrancyGuard`, not OpenZeppelin; interface at\n`contracts/src/interfaces/IModelRegistry.sol`). Stores model metadata, manages owner permissions,\ncharges a fixed registration fee, and proxies registration and inference to the model precompile at\n`0x1000`. The constructor grants the deployer the admin and operator roles.\n\n| Function | Notes |\n|---|---|\n| `registerModel(string name, string framework, string version, string ipfsCID, uint256 sizeBytes, uint256 inferencePrice, IModelRegistry.ModelMetadata metadata) payable returns (bytes32)` | Requires `REGISTRATION_FEE` (0.1 SALT); returns the `modelHash`. |\n| `updateModel(bytes32 modelHash, string newVersion, string newIpfsCID)` | Owner only. |\n| `setInferencePrice(bytes32 modelHash, uint256 newPrice)` | Owner only. |\n| `deactivateModel(bytes32 modelHash)` / `activateModel(bytes32 modelHash)` | Owner or operator role. |\n| `grantPermission(bytes32 modelHash, address user)` / `revokePermission(bytes32 modelHash, address user)` | Owner only. |\n| `requestInference(bytes32 modelHash, bytes inputData) payable returns (bytes)` | Forwards the full `msg.value` to the model owner; no marketplace fee is retained. |\n| `withdrawFees()` | `onlyRole(DEFAULT_ADMIN_ROLE)`. |\n\nViews include `getModel` (an 8-tuple of owner, name, framework, version, IPFS CID, inference price, total\ninferences, and active flag), `getModelsByOwner`, `getModelRevenue`, `hasPermission`,\n`getAllModelHashes`, and `getModelsInfo`. Constants: `REGISTRATION_FEE = 0.1 ether` and\n`MODEL_PRECOMPILE = 0x1000`. Note that `setRegistrationFee` is intentionally inert: it is a `view` that\nalways reverts with \"Registration fee is immutable\", so the fee cannot be changed.\n\n### InferenceRouter\n\n`contracts/src/InferenceRouter.sol`, `contract InferenceRouter is AccessControl, ReentrancyGuard`\n(project-local). Routes inference to registered operators by a load-balancing score, with response\ncaching, operator staking, and payment or refund distribution. The constructor takes the registry\naddress and grants the deployer admin and operator roles.\n\n| Function | Notes |\n|---|---|\n| `registerProvider(string endpoint, uint256 minPrice, bytes32[] supportedModels) payable` | Stake at least `minProviderStake` (100 SALT default). |\n| `requestInference(bytes32 modelHash, bytes inputData, uint256 maxPrice) payable returns (uint256)` | A cache hit returns the cached output with a partial refund. |\n| `completeInference(uint256 requestId, bytes outputData)` | Assigned operator only; pays the operator, refunds the excess, caches the output. |\n| `cancelRequest(uint256 requestId)` | Requester only, while pending; full refund. |\n| `updateProviderStatus(bool isActive)` / `addStake() payable` / `withdrawStake(uint256 amount)` | Operator stake management. |\n| `withdrawEarnings()` | Operator pulls accrued earnings. |\n| `setCaching(bytes32 modelHash, bool enabled)` | `onlyRole(OPERATOR_ROLE)`. |\n| `setPlatformFee(uint256 newFee)` / `setMinProviderStake(uint256 newStake)` / `withdrawPlatformFees()` | `onlyRole(DEFAULT_ADMIN_ROLE)`; the fee is capped at 1000 bps. |\n\nViews include `getRequest`, `getUserRequests`, `getProviders`, and `getProviderInfo`. Config:\n`minProviderStake = 100 ether`, `platformFee = 250` (2.5%), and `cacheReward = 100` (1%). Requests move\nthrough `enum RequestStatus { Pending, Processing, Completed, Failed, Cancelled }`.\n\n### ModelMarketplace\n\n`contracts/src/ModelMarketplace.sol`, `contract ModelMarketplace is IModelMarketplace, AccessControl,\nReentrancyGuard` (project-local; interface at `contracts/src/interfaces/IModelMarketplace.sol`). A\nmarketplace layered on the registry: owners list models, buyers purchase inference access with bulk\ndiscounts, and a fee splits to a treasury. Adds reviews, categories, and featuring. The constructor takes\nthe registry and treasury addresses, both non-zero; the registry is immutable.\n\n| Function | Notes |\n|---|---|\n| `listModel(bytes32 modelId, uint256 basePrice, uint256 discountPrice, uint256 minimumBulkSize, uint8 category, string metadataURI)` | Price in `[MIN_PRICE, MAX_PRICE]`; category at most 10. |\n| `purchaseAccess(bytes32 modelId, uint256 quantity) payable` | Bulk discount above `minimumBulkSize`; fee `MARKETPLACE_FEE_BASIS_POINTS` (2.5%); refunds the excess. |\n| `updatePricing(...)` / `updateCategory(...)` | Owner only. |\n| `deactivateListing(bytes32 modelId)` / `activateListing(bytes32 modelId)` | Owner only. |\n| `featureModel(bytes32 modelId) payable` | Admin free; owner pays `FEATURED_FEE` (1 SALT). |\n| `unfeatureModel(bytes32 modelId)` | `onlyRole(DEFAULT_ADMIN_ROLE)`. |\n| `addReview(bytes32 modelId, uint8 rating, string comment)` | Rating 1 to 5; comment at most 500 bytes. |\n| `updateTreasuryAddress(address newTreasury)` | `onlyRole(DEFAULT_ADMIN_ROLE)`. |\n\nViews include `getListing`, `getModelsByCategory`, `getFeaturedModels`, `getTopRatedModels`,\n`getModelsByOwner`, `getPurchaseHistory`, `getModelReviews`, and `getMarketplaceStats`. Constants:\n`MARKETPLACE_FEE_BASIS_POINTS = 250`, `MIN_PRICE = 0.001 ether`, `MAX_PRICE = 1000 ether`, and\n`FEATURED_FEE = 1 ether`. A review does not require a verified purchase; the review carries a `verified`\nflag set from the reviewer's purchase history, but an unverified review still counts toward the average\nrating.\n\n### LoRAFactory\n\n`contracts/src/LoRAFactory.sol`, `contract LoRAFactory is AccessControl` (project-local; it does not\ninherit `ReentrancyGuard`). A factory for creating, training, merging, and cryptographically verifying\nlow-rank adapters against base models in the registry, through the LoRA precompile at `0x1001` and the\nHalo2-KZG inference-proof verifier at `0x0108`. The constructor takes the registry address and grants the\ndeployer admin and operator roles.\n\n| Function | Notes |\n|---|---|\n| `createLoRA(bytes32 baseModelHash, string name, string description, uint256 rank, uint256 alpha, uint256 dropout, TrainingConfig config) payable returns (bytes32)` | Requires permission on the base model; fee is `trainingFeePerEpoch` times the epoch count. |\n| `completeTraining(bytes32 loraHash, string ipfsCID)` | `onlyRole(OPERATOR_ROLE)`; records the trained-weights CID. |\n| `setAdapterModelCommitment(bytes32 loraHash, bytes32 commitment)` | `onlyRole(OPERATOR_ROLE)`; one-shot. |\n| `verifyAdapterAt(bytes32 loraHash, bytes32 inputCommitment, bytes32 outputCommitment, bytes proofBytes)` | Proof-backed verification through `0x0108`. |\n| `isAdapterVerified(bytes32 loraHash) view returns (bool)` | Whether the adapter has a verified proof. |\n| `mergeLoRAs(bytes32[] loraHashes, uint256[] weights, uint256 mergeType) payable returns (bytes32)` | Weights must sum to `1e18`. |\n| `completeMerge(bytes32 requestHash, string resultCID)` | `onlyRole(OPERATOR_ROLE)`. |\n| `inferWithLoRA(bytes32 baseModelHash, bytes32 loraHash, bytes inputData) payable returns (bytes)` | Splits 20% to the adapter creator, 80% through `modelRegistry.requestInference`. |\n| `setPublicStatus` / `grantPermission` / `revokePermission` | Creator only. |\n| `setTrainingFee` / `setMergeFee` / `withdrawFees` | `onlyRole(DEFAULT_ADMIN_ROLE)`. |\n\nViews include `getLoRA`, `getUserLoRAs`, `getModelLoRAs`, and `getMergeRequest`. Constants:\n`trainingFeePerEpoch = 0.01 ether`, `mergeFee = 0.05 ether`, `LORA_PRECOMPILE = 0x1001`,\n`INFERENCE_PROOF_VERIFY = 0x0108`, and `INFERENCE_CIRCUIT_V1 = 1`.\n\nAdapter provenance, as this contract implements it, ties to three things and no more: the base model\nhash, which must exist in the registry; the IPFS CIDs for the trained weights and for the dataset, the\nlatter carried as `datasetCID` inside the `TrainingConfig` struct; and a cryptographic commitment plus a\nproof. An operator sets a one-shot `adapterModelCommitment`, then `verifyAdapterAt` submits a Halo2-KZG\nproof to the `0x0108` precompile over the tuple of input commitment, model commitment, output commitment,\ncircuit version, and chain id. A passing proof flips `isAdapterVerified` to true. That proof shows an\nadapter produces a committed output for a committed input against the committed model. It does not, in\nthis build, link the adapter to a learning round, a contributor, or a contribution-accounting record;\nthere is no reference to those systems in the contract. Federated learning and contribution live on the\nseparate [Citrate Orchard](/research/learning) surface.\n\n### ModelAccessControl\n\n`contracts/src/ModelAccessControl.sol`, `contract ModelAccessControl is Ownable, ReentrancyGuard` (the\nOpenZeppelin versions, the one contract on this page that does). A standalone tiered access registry,\ndistinct from ModelRegistry: paid or approval-based grants, per-model staking, revenue sharing, and an\nencrypted-inference path through the runtime precompiles for model inference at `0x0101` and model\nencryption at `0x0106`. The constructor makes the deployer the owner.\n\n| Function | Notes |\n|---|---|\n| `registerModel(bytes32 modelId, string ipfsCid, bool isEncrypted, uint256 accessPrice)` | Registrant gets `ACCESS_ADMIN` on the model. |\n| `updateModel(...)` / `setModelMetadata(...)` | Owner only. |\n| `grantAccess(bytes32 modelId, address user, uint8 level, uint256 expiresAt, uint256 usageLimit)` | Owner only. |\n| `revokeAccess(bytes32 modelId, address user)` | Owner only. |\n| `requestAccess(bytes32 modelId, uint8 level, string reason) payable returns (uint256 requestId)` | Requires at least the access price. |\n| `approveAccessRequest(uint256 requestId, uint256 expiresAt, uint256 usageLimit)` | Model owner. |\n| `executeInference(bytes32 modelId, bytes inputData) payable returns (bytes)` | Requires `ACCESS_INFERENCE`. |\n| `executeEncryptedInference(bytes32 modelId, bytes encryptedInput, bytes32 proofCommitment) payable returns (bytes)` | Through the encryption precompile. |\n| `setStakingRequirement` / `stakeForAccess` / `unstake` | Per-model staking. |\n| `withdrawRevenue()` / `emergencyWithdraw()` | `emergencyWithdraw` is `onlyOwner`. |\n\nViews include `getModelStats`, `hasAccessToModel`, `getUserAccessLevel`, and `getModel`. Access levels\nare `ACCESS_NONE = 0`, `ACCESS_INFERENCE = 1`, `ACCESS_FULL = 2`, and `ACCESS_ADMIN = 3`. Note that\n`getModelStats` returns `uniqueUsers` as a placeholder zero, and `updatePrecompileAddress` is a\nnon-functional placeholder.\n\n## Design rationale\n\nThe registry keeps a model as a small record and pushes the work to the precompiles because the weights\nare large and often private; the ledger needs only the identity, the price, and the receipt. Charging\nthrough `requestInference` and forwarding the whole payment to the owner keeps the base case simple, and\nthe marketplace and access-control contracts layer richer policy on top when an owner wants it.\n\nThe two access systems exist for two audiences. A developer who wants a model in a public catalog with\nreviews and discounts uses the registry and the marketplace. An institution that wants approval-gated,\nstaked, or encrypted access uses ModelAccessControl. Keeping them separate avoids forcing one set of\nassumptions on the other, at the cost of an integrator having to choose.\n\nThe adapter factory proves what it can prove on-chain, which is a proof that a committed adapter produces\na committed output, and no more. Tying an adapter back to who contributed which gradient is a learning-\nround concern, and that belongs to Citrate Orchard, not to this factory. The honest framing is that the\nfactory verifies adapter behavior, not adapter origin.\n\n## Failure modes\n\nThese contracts move value and gate access, so the sharp edges are worth naming.\n\n- **A bad adapter is trusted.** Until `isAdapterVerified` returns true, an adapter has no on-chain proof\n behind it. Check it before relying on an adapter, because creation and training alone do not verify\n anything. The proof, when present, attests behavior, not origin.\n- **Reentrancy surface.** LoRAFactory does not inherit `ReentrancyGuard` even though `inferWithLoRA`,\n `mergeLoRAs`, and `withdrawFees` move value. On InferenceRouter only `requestInference` carries the\n guard; `completeInference`, `cancelRequest`, `withdrawEarnings`, `withdrawStake`, and\n `withdrawPlatformFees` rely on careful ordering rather than a guard. Both are flagged for external\n audit and should not be treated as verified.\n- **Unverified reviews.** A marketplace review does not require a verified purchase, so the average\n rating can be moved by addresses that never bought the model; the `verified` flag distinguishes them\n but does not exclude them from the average.\n- **Inert and placeholder surfaces.** `setRegistrationFee` on the registry always reverts by design.\n On ModelAccessControl, `getModelStats` reports a placeholder zero for unique users and\n `updatePrecompileAddress` does nothing.\n\n## Access and canon\n\nTier: ModelRegistry and InferenceRouter are public developer reference, since a builder needs them to\nregister a model and consume inference. ModelMarketplace, LoRAFactory, and ModelAccessControl are\ncommercial, the paid-seat depth covering marketplace economics, the adapter pipeline, and the access and\nstaking design.\n\nNo secrets appear on this page. There are no private keys, mnemonics, internal hostnames, or\ncredentials. The only hardcoded addresses are public protocol precompiles: `0x1000`, `0x1001`, `0x0101`,\n`0x0106`, and `0x0108`. Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity.\n\n## Source and verification\n\n- Source: `citrate-chain/contracts/src/`, in `ModelRegistry.sol`, `InferenceRouter.sol`,\n `ModelMarketplace.sol`, `LoRAFactory.sol`, and `ModelAccessControl.sol`, with interfaces\n `interfaces/IModelRegistry.sol` and `interfaces/IModelMarketplace.sol`.\n- Audited against `citrate-chain` SHA `9d5959e`.\n- Status: Implemented, pre-audit, on testnet 40204. The LoRAFactory reentrancy gap, the InferenceRouter\n guard mismatch, the unverified-review weighting, and the ModelAccessControl placeholders are open items\n noted above and not yet externally audited. Re-verify deployed bytecode with `eth_getCode` if the chain\n has been re-rolled.\n"},"/contracts/reference":{"slug":"/contracts/reference","title":"Contracts reference","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/contracts (addresses/40204.json, src/)","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The contracts that run on the Citrate Network, where to find their addresses, where to get their ABIs, and how to confirm an address is real before you call it. This page is for anyone integrating against the on-chain surface.\n\n## What it is\n\nThe Citrate Network is EVM-compatible, so the on-chain surface is a set of Solidity contracts you call with ordinary tooling. The address book lists 76 application and account-abstraction contracts for testnet beta, chain id 40204. At the last check 57 of them have code on chain; the other 19, the governance and cooperative set, are not deployed. The [chain addresses page](/chain/addresses) marks each one. They group into families: education, compute, models, network economics, governance, security, x402 payments, and account abstraction. The families are described on their own pages, linked below.\n\nTwo facts shape how you should treat this page. First, addresses drift: the chain can be re-rolled, and a contract you read about yesterday may sit at a new address today. The canonical record of what is deployed lives in the source repo, not here. Second, the ABIs are published as a package, so you never have to hand-copy them. This page tells you where both live and how to verify an address yourself.\n\n## How to use it\n\nYou need three things to call a Citrate contract: its address, its ABI, and the confidence that the address is what you think it is.\n\n1. Get the addresses. The canonical record of what is deployed is `contracts/addresses/40204.json` in the chain repo, the single table every consumer reads. The same set is published on the [chain addresses page](/chain/addresses) with a deployed or not-deployed status on each row. A `@citratelabs/chain-config` package is built in the chain repo (`packages/chain-config`) but is not published to npm yet, so read the JSON directly. Read from one of these rather than copying addresses into your code by hand; they update when the chain is re-rolled, and prose documentation may lag. (`contracts/DEPLOYED_ADDRESSES.md` is superseded and is no longer the source of truth.)\n\n```bash\n# The book is plain JSON; fetch it from the chain repo and read what you need.\ncurl -fsSL https://raw.githubusercontent.com/CitrateNetwork/citrate-chain/main/contracts/addresses/40204.json -o 40204.json\njq -r '.contracts.WrappedSALT' 40204.json\ncast code \"$(jq -r '.contracts.WrappedSALT' 40204.json)\" --rpc-url https://rpc.citrate.ai # \"0x\" means not deployed\n```\n\n2. Get the ABIs. Regenerate them from source with Foundry. After `forge build`, each contract's ABI is the `.abi` key of `out/.sol/.json`.\n\n```bash\ncd contracts\nforge build\njq '.abi' out/NematocystSlashing.sol/NematocystSlashing.json\n```\n\n3. Verify the address before you trust it. Ask the network for the code at the address with `eth_getCode`. A non-empty result means a contract is deployed there; `0x` means the address is empty or an account, and you should stop.\n\n```bash\ncurl -s https://rpc.citrate.ai \\\n -H 'content-type: application/json' \\\n -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_getCode\",\"params\":[\"
\",\"latest\"]}'\n```\n\nTo go further, compare the runtime bytecode the network returns against your local `forge build` output, the `.deployedBytecode` key of `out/.sol/.json`, to confirm the deployed contract matches the source at this SHA. The [read a contract](/contracts/tutorials/read-a-contract) tutorial walks through this step by step.\n\n## Reference\n\nThe contract families and where each is documented. Addresses for every contract are in `contracts/addresses/40204.json` and on the [chain addresses page](/chain/addresses), not transcribed here.\n\n| Family | What it covers | Page |\n|---|---|---|\n| Education | classrooms, mentorship, learning pools | [/contracts/edu](/contracts/edu) |\n| Compute | the compute marketplace, pools, pricing, verification | [/contracts/compute](/contracts/compute) |\n| Models | model registry, marketplace, access control, LoRA | [/contracts/models](/contracts/models) |\n| Network economics | wrapped SALT, liquid staking, treasury, cashout | [/contracts/economics](/contracts/economics) |\n| Governance | treasury governor, budget allocation, dispute resolution | [/contracts/governance](/contracts/governance) |\n| Security | slashing, heartbeat monitoring, TEE attestation, KYC | [/contracts/security](/contracts/security) |\n| x402 payments | the facilitator and paywall for HTTP 402 settlement | [/contracts/x402](/contracts/x402) |\n| Account abstraction | the ERC-4337 stack, validators, paymaster, factory | [/contracts/aa](/aa/contracts) |\n\nThe address source of truth and the ABI package, named once:\n\n| Resource | Where it lives |\n|---|---|\n| Canonical address table | `contracts/addresses/40204.json` (also on [/chain/addresses](/chain/addresses)) |\n| Chain config package | `@citratelabs/chain-config`, source in `citrate-chain/packages/chain-config`, not yet published to npm |\n| ABIs | regenerate with `forge build`, read the `.abi` key of `out/.sol/.json` |\n\nChain facts you will need when configuring a client:\n\n| Field | Value |\n|---|---|\n| Chain id | `40204` (`eth_chainId` returns `0x9d0c`) |\n| Block time | about 2 s measured on the testnet (the configured target is 1 s) |\n| Consensus | GhostDAG, k = 18 |\n| Public RPC (HTTP) | `https://rpc.citrate.ai` |\n| Public RPC (WebSocket) | `wss://rpc.citrate.ai` |\n\n## Access and canon\n\nPublic. Contract addresses are public on-chain data, and the ABIs and build commands are open developer reference. No private keys, no credentials, and no operational endpoints appear here. The public RPC hostname is the only network address you need; raw node addresses are not published, and you do not need them.\n\nOne pilot caveat carries from the source repo. The deploys are unsigned at this stage, so verification rests on `eth_getCode` cross-checks rather than cosign certificates. The account-abstraction stack (EntryPoint and the rest) is included in the canonical `contracts/addresses/40204.json` under `aaStack`.\n\n## Source and verification\n\nSource: `contracts/addresses/40204.json` (regenerated by `scripts/ops/emit-address-table.sh`), `contracts/README.md`, and the `packages/chain-config` source. Contracts compile under `pragma solidity ^0.8.26` (the 2026-09-07 re-roll built with solc 0.8.36). Chain id 40204 is confirmed in `node/config/testnet.toml` and `contracts/addresses/40204.json`; the testnet config sets a 1 s block target, and the measured interval is about 2 s. Audited against `citrate-chain` SHA `9d5959e`. Status: Implemented, testnet beta, pre-audit. Re-verify any address with `eth_getCode` if the chain has been re-rolled.\n\nSee also [chain RPC](/chain/rpc) and the [chain CLI](/chain/cli).\n"},"/contracts/security":{"slug":"/contracts/security","title":"Security & Slashing Contracts","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/contracts/src/{KYCRegistry,TEEAttestationRegistry,NematocystSlashing}.sol, contracts/src/interfaces/INematocystSlashing.sol","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"KYCRegistry","anchor":"kycregistry"},{"depth":3,"text":"TEEAttestationRegistry","anchor":"teeattestationregistry"},{"depth":3,"text":"NematocystSlashing","anchor":"nematocystslashing"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The security surface is where the network enforces who may participate and what happens when a participant\nmisbehaves. Three contracts carry it: a verification registry that records whether an account passed\nidentity review, an attestation registry that records whether a worker is running in a trusted execution\nenvironment, and a graduated slashing model that penalizes staked providers. This page is for protocol\nresearchers, node operators, and compute providers.\n\n## What it is\n\nTwo of these contracts gate participation, and one punishes it. `KYCRegistry` holds the result of an\nidentity check, a single boolean per account, mirrored from the off-chain identity authority. The personal\ndata behind the check never reaches the ledger; only the verification outcome does. `TEEAttestationRegistry`\nholds, per worker, whether that worker has a fresh attestation that its hardware is running in a trusted\nexecution environment. `NematocystSlashing` takes the staked SALT of a provider and reduces it when the\nprovider is shown to have misbehaved, with the penalty scaled by how severe the fault is and by how many\nproviders failed at once.\n\n| Contract | Role | Status |\n|---|---|---|\n| `KYCRegistry` | Mirror of a revocable, data-free identity claim, an anti-Sybil gate | Implemented, pre-audit |\n| `TEEAttestationRegistry` | Per-worker trusted-execution attestation state | Implemented, pre-audit |\n| `NematocystSlashing` | Graduated three-tier provider slashing with a correlation multiplier | Implemented, pre-audit |\n| `INematocystSlashing` | Minimal calling interface for the slashing contract | Implemented, interface only |\n\nThe slashing model is named for the nematocyst, the stinging cell of a cnidarian. The name is academic in\norigin, but the contract is real and runs; only the minimal `INematocystSlashing` interface is interface\nonly, and it exists so other contracts can call `slash` without importing the full implementation.\n\n## How to use it\n\n1. **Read an identity result.** Call `KYCRegistry.isVerified(account)`. A gating contract, for example the\n pinning incentive contract, calls this before it lets an account take a rewarded slot. To learn the\n stable identity an account resolves to, call `identityOf(account)`; two accounts that return the same\n non-zero value belong to the same person.\n2. **Check a worker's attestation.** Call `TEEAttestationRegistry.isAttested(worker, currentBlock)`. It\n returns true only when the worker has a fresh, non-slashed attestation that has not expired at that\n block. A pipeline contract calls this before it routes confidential inference to the worker.\n3. **Stake as a provider.** Call `NematocystSlashing.stake()` with at least `MIN_STAKE`, which is 100 SALT,\n to become a slashable provider. Call `unstake()` to withdraw the full stake and deregister, which is\n refused if the account has been banned.\n4. **Apply a slash.** Only governance calls `slash(provider, tier, evidence)`. The penalty is a fixed\n fraction of the stake for the tier, scaled up by the correlation multiplier when many providers are\n slashed in the same window.\n\n## Reference\n\nThe audited surface, each item citing its source file under `contracts/src/`.\n\n### KYCRegistry\n\nSource: `contracts/src/KYCRegistry.sol`. A deliberately small contract built on `AccessControl`. It mirrors\nthe off-chain identity authority's `kyc` claim by holding one boolean per account and, separately, the\nstable identity hash that account resolves to. It holds no value and no personal data; the off-chain claim\nis issued over the identity provider's userinfo endpoint, and only the outcome is mirrored here.\n\nTwo roles govern it: `DEFAULT_ADMIN_ROLE`, the deployer, manages updaters, and `KYC_UPDATER_ROLE`, held by\nthe identity authority key or a KYC oracle relay, sets and revokes verification.\n\n| Function | Access | What it does |\n|---|---|---|\n| `setVerified(account)` | `KYC_UPDATER_ROLE` | Marks an account verified; binds a self identity if none is set yet |\n| `setVerifiedWithIdentity(account, subHash)` | `KYC_UPDATER_ROLE` | Verifies and binds the account to a real identity hash |\n| `revoke(account)` | `KYC_UPDATER_ROLE` | Clears verification; leaves the identity binding intact |\n| `isVerified(account)` | view | Returns the current verification boolean |\n| `identityOf(account)` | view | Returns the bound identity hash, or zero if unbound |\n\nUntil the identity provider issues real subject and account claims, `setVerified` binds each account to its\nown identity, a hash of `\"PIN-self\"` and the address, so per-account verification is unchanged and the\nSybil binding is a safe no-op. When real claims arrive, the authority calls `setVerifiedWithIdentity` and\nthe binding becomes meaningful: two accounts under the same identity hash are the same person. Events:\n`KYCVerified`, `KYCRevoked`, `IdentityBound`.\n\n### TEEAttestationRegistry\n\nSource: `contracts/src/TEEAttestationRegistry.sol`. Stores, per worker, an attestation record proving the\nworker runs inside a trusted execution environment, for confidential pipeline-parallel inference. A complete\nattestation is an Azure MAA token at the VM level plus an NVIDIA NRAS claim at the GPU level. It inherits\n`ReentrancyGuard` and `Governable`, and its state machine mirrors the `PipelineParallelTEE.tla` spec:\nnot attested, attested, expired, then slashed as an absorbing terminal state.\n\nThere are two submission paths. The older governance-trusted path, `submitAttestation`, takes\nmeasurement hashes and trusts that they came from pre-approved signers. The cryptographic path,\n`submitAttestationStrictBound`, takes the raw MAA token, its signature, and the literal claim bytes; it\nverifies the RS256 signature on chain against a governance-published RSA key and verifies that the claim\nbytes appear literally inside the token payload, so the on-chain measurement is bound to real token content.\nThe NRAS side remains governance-trusted, pending a P-384 verification primitive.\n\n| Function | Access | What it does |\n|---|---|---|\n| `submitAttestation(...)` | open, gated by `strictCryptographicMode` | V1 path; reverts when strict mode is on |\n| `submitAttestationStrictBound(...)` | open | V2 path; on-chain RS256 verify plus literal claim binding |\n| `isAttested(worker, currentBlock)` | view | True only for a fresh, non-slashed, unexpired record |\n| `getAttestation(worker)` | view | Returns the full attestation record |\n| `reportExpiredServe(...)` | open, posts bond | Files a claim that a worker served past expiry |\n| `finalizeReport(reportId, uphold, stake)` | `onlyGovernance` | Adjudicates a report; slashes on uphold |\n| `proposeMaaRsaKey / finalizeMaaRsaKey` | `onlyGovernance`, then open | Timelocked install of an RSA key |\n| `setMaaRsaKeyActive(kidHash, active)` | `onlyGovernance` | Single-step activate or deactivate |\n| `setStrictCryptographicMode(enabled)` | `onlyGovernance` | Switches the V1 path on or off |\n\nKey constants: `ATTESTATION_LIFETIME_BLOCKS` is 28,800, roughly four hours; `SLASH_BPS` is 1,000, ten\npercent; `REPORT_BOND` is 1 SALT; `RSA_KEY_TIMELOCK_BLOCKS` is 3,600. `strictCryptographicMode` defaults to\ntrue, so a fresh deployment is in the cryptographic path unless governance explicitly opts out.\n\n### NematocystSlashing\n\nSource: `contracts/src/NematocystSlashing.sol`. The graduated slashing model, inheriting `ReentrancyGuard`\nand `Governable`. It maps three severities of provider misbehavior onto three tiers, each with a fixed\npenalty in basis points of the provider's stake.\n\n| Tier | `SlashTier` | Fault | Base penalty |\n|---|---|---|---|\n| 1 | `Latency` | Missed checkpoints or latency faults | `LATENCY_PENALTY_BPS` = 500 (5%) |\n| 2 | `Inconsistency` | Inconsistent results | `INCONSISTENCY_PENALTY_BPS` = 2000 (20%) |\n| 3 | `Byzantine` | Equivocation or double-signing | `BYZANTINE_PENALTY_BPS` = 10000 (100%) plus a permanent ban |\n\nA `Byzantine` slash sets `banned[provider]` to true, forfeits any remaining stake, and emits `Banned`. A\nbanned provider can never re-stake. The novel part of the design is the correlation multiplier, taken from\nEthereum slashing research: penalties scale up when many providers are slashed in the same window, so\ncoordinated failures cost far more than isolated ones.\n\n```text\nmultiplier = clamp( slashedInWindow * 30 / totalProviders, 1x, 3x )\n```\n\nIt is computed in `getCorrelationMultiplier`, with all arithmetic scaled by `1e18`. `CORRELATION_WINDOW`\nis 50 blocks; the per-block slash counter is summed across the window. The multiplier floors at 1x, so a\npenalty is never reduced below its base, and caps at 3x. When there are no providers it is 1x.\n\nA slash, step by step, in `slash(provider, tier, evidence)`, which is `onlyGovernance`:\n\n1. Require non-empty evidence and an un-banned, currently staked provider.\n2. Record the slash for correlation tracking.\n3. Compute the raw penalty as stake times the tier basis points, then multiply by the correlation\n multiplier, capped at the full stake.\n4. Deduct the penalty; for `Byzantine`, also forfeit the remainder and ban.\n5. Emit `Slashed` with the provider, tier, amount, and multiplier.\n\nStaking surface: `stake()`, `payable` and `nonReentrant`; `unstake()`, `nonReentrant`; and the views\n`isSlashable`, `slashesInWindow`, `getCorrelationMultiplier`, plus the public mappings `stakes` and\n`banned` and the counter `totalProviders`. Events: `Staked`, `Unstaked`, `Slashed`, `Banned`.\n\n`INematocystSlashing`, at `contracts/src/interfaces/INematocystSlashing.sol`, is the minimal interface that\n`DisputeResolution` and other callers use. It declares one method, `slash(address, uint8, bytes)`, so a\ncaller does not need the full implementation to trigger a penalty.\n\n## Design rationale\n\nSlashing is graduated rather than flat because the faults are not equal. A slow provider is an annoyance; a\nprovider serving inconsistent results is a correctness problem; a provider double-signing is an attack. A\nsingle penalty for all three would either be too soft for the attack or too harsh for the slowness, so the\npenalty tracks the severity. The correlation multiplier exists because the dangerous failure is the\ncorrelated one: a single bad node is noise, but many nodes failing in one window suggests a coordinated\nproblem, and the cost should rise to match. The floor at 1x means an isolated fault is never discounted.\n\nAttestation defaults to the cryptographic path because the safe default is the one that does not trust the\ncaller's word. The earlier governance-trusted path is kept only as an explicit, reversible opt-out for a\nstaged cutover. The RSA key install is timelocked so that even a one-block compromise of governance cannot\ninstall a malicious signing key, while deactivating a key stays single-step so a compromised key can be\nkilled at once.\n\n## Failure modes\n\n- **Expired attestation.** A worker that serves past its attestation expiry can be reported with\n `reportExpiredServe`, and governance slashes it on uphold. The reporter's bond is returned whether the\n report is upheld or rejected, because under-reporting is the greater risk here.\n- **Token replay.** Each MAA token signature is consumed once, tracked by its hash. Re-submitting the same\n token reverts, so a valid token cannot be replayed by the same or another worker.\n- **Governance compromise on keys.** Installing an RSA signing key is timelocked, giving off-chain monitors\n a window to observe and abort, so a brief governance hijack cannot immediately accept forged attestations.\n- **Banned provider re-entry.** A `Byzantine` slash bans the provider permanently; `stake` refuses a banned\n sender, so a fully slashed attacker cannot quietly rejoin.\n- **Identity revocation.** A revoked KYC claim flips `isVerified` to false at once while leaving the\n identity binding intact, so a gating contract sees the revocation immediately and re-verification keeps\n the same person identity.\n\n## Access and canon\n\nTier: commercial. These contracts carry the network's identity and attestation machinery, whose internals\nwould help a competitor, so the full detail is served to contracted and verified principals rather than\npublished openly. There are no secrets here, no keys, no credentials, and no private endpoints. Any deployed\naddresses are public on-chain data and are verifiable with `eth_getCode`.\n\nThis surface ties to the rest of Almanac at three points. The identity result `KYCRegistry` holds comes from\nthe VERI identity check that gates every account on the public network, covered under [accounts and\nidentity](/aa/identity); Citrate keeps the verification result, not the personal data. The compliance posture these\ngates serve, FERPA, HIPAA, SOC 2, and the rest by deployment context, is covered under\n[enterprise](/enterprise/compliance). Slashing and finality as consensus concerns are covered under [Citrate Network\nconsensus](/chain/consensus).\n\n## Source and verification\n\n- Source repo: `citrate-chain`, files under `contracts/src/`: `KYCRegistry.sol`,\n `TEEAttestationRegistry.sol`, `NematocystSlashing.sol`, and `interfaces/INematocystSlashing.sol`, plus\n `contracts/src/lib/Governable.sol` for the ownership mixin.\n- Audited against `citrate-chain` SHA `9d5959e`.\n- Status by contract: `KYCRegistry` Implemented, pre-audit; `TEEAttestationRegistry` Implemented, pre-audit,\n state machine specified against `PipelineParallelTEE.tla`; `NematocystSlashing` Implemented, pre-audit;\n `INematocystSlashing` Implemented as an interface only. None has completed a final third-party audit.\n Verify deployed bytecode yourself with `eth_getCode` once addresses are published.\n"},"/contracts/tutorials/interact-read-only":{"slug":"/contracts/tutorials/interact-read-only","title":"Interact read-only","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/contracts/src/NematocystSlashing.sol","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Step 1, confirm the chain and the contract","anchor":"step-1-confirm-the-chain-and-the-contract"},{"depth":3,"text":"Step 2, read view functions","anchor":"step-2-read-view-functions"},{"depth":3,"text":"Step 3, decode return data by hand","anchor":"step-3-decode-return-data-by-hand"},{"depth":3,"text":"Step 4, read events with eth_getLogs","anchor":"step-4-read-events-with-eth_getlogs"},{"depth":3,"text":"Step 5, the same from JavaScript","anchor":"step-5-the-same-from-javascript"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Everything you can learn from a Citrate contract without sending a transaction: calling view functions, decoding what they return, and reading the events a contract has emitted. No account, no gas, no signature. About ten minutes.\n\n## What it is\n\nTwo read-only patterns cover most of what you need from a deployed contract. The first is `eth_call`, which runs a view or pure function against current state and returns its value; the [read a contract](/contracts/tutorials/read-a-contract) tutorial covers it in detail. The second is `eth_getLogs`, which returns the events a contract has emitted over a range of blocks, so you can read its history rather than just its present state. Neither writes anything, so neither needs a key.\n\nThe examples read the slashing contract in the security family, which records how the network penalizes node operators that misbehave. Its parameters and live state are fully public; what they mean is described under [security contracts](/contracts/security).\n\n## How to use it\n\nYou need a reachable Citrate JSON-RPC endpoint. The public one is `https://rpc.citrate.ai`. You will want `curl`, and the `cast` command from [Foundry](https://book.getfoundry.sh/) for the encode-free path. Read the slashing contract's address from `contracts/addresses/40204.json`, the canonical record described on the [contracts reference](/contracts/reference), and confirm it with step 1 before trusting it.\n\nSet up the helper from [your first 10 minutes](/start/tutorials/your-first-10-minutes):\n\n```bash\nexport RPC=https://rpc.citrate.ai\nexport ADDR=\n\nrpc () {\n curl -s \"$RPC\" -H 'content-type: application/json' \\\n -d \"{\\\"jsonrpc\\\":\\\"2.0\\\",\\\"id\\\":1,\\\"method\\\":\\\"$1\\\",\\\"params\\\":${2:-[]}}\"\n}\n```\n\n### Step 1, confirm the chain and the contract\n\nCheck you are on chain 40204, then confirm code lives at the address.\n\n```bash\nrpc eth_chainId\n# {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x9d0c\"} # 0x9d0c is 40204\nrpc eth_getCode \"[\\\"$ADDR\\\", \\\"latest\\\"]\"\n# a long hex string means a contract is deployed there\n```\n\n### Step 2, read view functions\n\nThese are all `public` or `view` on the slashing contract. The fixed penalties are constants, expressed in basis points; the rest are read from live state.\n\n```bash\n# Number of registered providers (a public state variable)\ncast call $ADDR \"totalProviders()(uint256)\" --rpc-url $RPC\n\n# Correlation multiplier, scaled by 1e18; 1e18 is 1x\ncast call $ADDR \"getCorrelationMultiplier()(uint256)\" --rpc-url $RPC\n\n# Slash events recorded in the current correlation window\ncast call $ADDR \"slashesInWindow()(uint256)\" --rpc-url $RPC\n\n# Per-address views; substitute any address you want to inspect\ncast call $ADDR \"stakes(address)(uint256)\" 0x0000000000000000000000000000000000000000 --rpc-url $RPC\ncast call $ADDR \"banned(address)(bool)\" 0x0000000000000000000000000000000000000000 --rpc-url $RPC\ncast call $ADDR \"isSlashable(address)(bool)\" 0x0000000000000000000000000000000000000000 --rpc-url $RPC\n\n# Fixed tier penalties, in basis points\ncast call $ADDR \"LATENCY_PENALTY_BPS()(uint256)\" --rpc-url $RPC # 500 (5%)\ncast call $ADDR \"INCONSISTENCY_PENALTY_BPS()(uint256)\" --rpc-url $RPC # 2000 (20%)\ncast call $ADDR \"BYZANTINE_PENALTY_BPS()(uint256)\" --rpc-url $RPC # 10000 (100%)\n```\n\n### Step 3, decode return data by hand\n\nWhen you call without `cast`, the result is raw ABI-encoded hex and you decode it yourself. A `uint256` comes back as one 32-byte word.\n\n```bash\nSEL=$(cast sig \"getCorrelationMultiplier()\") # 0x...\nrpc eth_call \"[{\\\"to\\\":\\\"$ADDR\\\",\\\"data\\\":\\\"$SEL\\\"},\\\"latest\\\"]\"\n# {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x0000...0de0b6b3a7640000\"}\n# decode the 32-byte word to a number:\ncast --to-dec 0x0000000000000000000000000000000000000000000000000de0b6b3a7640000\n# 1000000000000000000 (1e18, a 1x multiplier)\n```\n\n### Step 4, read events with eth_getLogs\n\nA contract's history is in its events. `eth_getLogs` returns the logs matching a filter: the contract address, a block range, and optional topics. The first topic is the keccak256 hash of the event signature, so you can ask for one kind of event. The slashing contract emits `Staked(address,uint256)`, `Unstaked(address,uint256)`, `Slashed(...)`, and `Banned(address)`.\n\n```bash\n# Topic for the Staked event\ncast keccak \"Staked(address,uint256)\"\n# 0x... (use this as topics[0])\n\n# All Staked events from this contract, over a block range\nrpc eth_getLogs \"[{\\\"address\\\":\\\"$ADDR\\\",\\\"fromBlock\\\":\\\"0x0\\\",\\\"toBlock\\\":\\\"latest\\\",\\\"topics\\\":[\\\"\\\"]}]\" \n```\n\nEach log carries `topics` and `data`. Indexed event parameters land in `topics` after the signature hash; non-indexed parameters are ABI-encoded in `data`. For `Staked(address indexed provider, uint256 amount)`, the provider address is `topics[1]` and the amount is in `data`. Keep block ranges narrow on a busy contract, since a wide range returns a large response.\n\n### Step 5, the same from JavaScript\n\nThe same reads in viem, using the published ABI so you work in function names rather than selectors. This is read-only: a public client only, no key and no signer.\n\n```ts\nimport { createPublicClient, http, defineChain } from \"viem\";\nimport NematocystSlashing from \"./abi/NematocystSlashing.json\"; // regenerated via `forge build`\n\nconst citrate = defineChain({\n id: 40204,\n name: \"Citrate Network\",\n nativeCurrency: { name: \"SALT\", symbol: \"SALT\", decimals: 18 },\n rpcUrls: { default: { http: [\"https://rpc.citrate.ai\"] } },\n});\n\nconst client = createPublicClient({ chain: citrate, transport: http() });\nconst address = \"\" as const;\n\nconst providers = await client.readContract({\n address, abi: NematocystSlashing.abi, functionName: \"totalProviders\",\n});\nconst multiplier = await client.readContract({\n address, abi: NematocystSlashing.abi, functionName: \"getCorrelationMultiplier\",\n});\n\nconsole.log({ providers, multiplier }); // multiplier is 1e18-scaled\n```\n\n`readContract` issues an `eth_call` under the hood, exactly what you did by hand in step 3, but decoded for you.\n\n## Reference\n\nThe read surface used above, with sources.\n\n| Kind | Item | Source |\n|---|---|---|\n| View | `totalProviders()`, `slashesInWindow()`, `getCorrelationMultiplier()` | `contracts/src/NematocystSlashing.sol` |\n| View | `stakes(address)`, `banned(address)`, `isSlashable(address)` | `contracts/src/NematocystSlashing.sol` |\n| Constant | `LATENCY_PENALTY_BPS`, `INCONSISTENCY_PENALTY_BPS`, `BYZANTINE_PENALTY_BPS` | `contracts/src/NematocystSlashing.sol` |\n| Event | `Staked`, `Unstaked`, `Slashed`, `Banned` | `contracts/src/NematocystSlashing.sol` |\n\nFor encoding a single call step by step, see [read a contract](/contracts/tutorials/read-a-contract). For addresses and the ABI package, see the [contracts reference](/contracts/reference).\n\n## Failure modes\n\n- A `0x` result from `eth_getCode` means no contract is at the address. Re-read it from `contracts/addresses/40204.json`; the chain may have been re-rolled.\n- `-32601 Method not found` means the RPC method is misspelled or not served by the node.\n- An empty `eth_getLogs` response can mean the block range is wrong, the topic hash is wrong, or no such event has been emitted. Widen the range to test, then narrow it back.\n- A very wide `eth_getLogs` range can be refused or return a large payload. Page through narrow ranges instead.\n- A decoded value that looks wrong is usually a return-type mismatch. Confirm the type against the source.\n\n## Access and canon\n\nPublic and read-only. Every step here is a read against public on-chain data; no keys, no credentials, no gas. The public RPC hostname and the contract addresses are public values. Re-verify any address with step 1 before trusting it.\n\n## Source and verification\n\nFunctions and events verified against `citrate-chain` at SHA `9d5959e`: `contracts/src/NematocystSlashing.sol`. Regenerate the ABI with `forge build` (the `.abi` key of `out/NematocystSlashing.sol/NematocystSlashing.json`). Addresses live in `contracts/addresses/40204.json`, chain 40204, testnet beta. Status: Implemented, testnet beta, pre-audit.\n\nSee also [chain RPC](/chain/rpc) and the [chain CLI](/chain/cli).\n"},"/contracts/tutorials/read-a-contract":{"slug":"/contracts/tutorials/read-a-contract","title":"Read a contract","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/contracts/src (WrappedSALT.sol, LiquidStakingPool.sol)","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Step 1, confirm the contract exists","anchor":"step-1-confirm-the-contract-exists"},{"depth":3,"text":"Step 2, call a view function with raw eth_call","anchor":"step-2-call-a-view-function-with-raw-eth_call"},{"depth":3,"text":"Step 3, let cast encode and decode for you","anchor":"step-3-let-cast-encode-and-decode-for-you"},{"depth":3,"text":"Step 4, read a value that is computed live","anchor":"step-4-read-a-value-that-is-computed-live"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Read a deployed contract's state on the Citrate Network without sending a transaction. You will confirm the contract exists with `eth_getCode`, then call a view function with `eth_call`, first by encoding the selector yourself and then with tooling that does it for you. Read-only, no account, no gas, about ten minutes.\n\n## What it is\n\nReading a contract is a request, not a transaction. `eth_call` runs a function against the network's current state and hands back the return value; nothing is written, no fee is charged, and no signature is needed. The only inputs are the contract address and the function call data: a four-byte selector, the first four bytes of the keccak256 hash of the function signature, followed by any ABI-encoded arguments.\n\nThe examples below read two contracts in the network economics family: Wrapped SALT, an ERC-20 wrapper whose `symbol` and `decimals` are constants, and the liquid staking pool, whose share price is computed live. Both are described under [economics contracts](/contracts/economics).\n\n## How to use it\n\nYou need a reachable Citrate JSON-RPC endpoint. The public one is `https://rpc.citrate.ai`. A local node serves `http://127.0.0.1:8545`. You will also want `curl`, and the `cast` command from [Foundry](https://book.getfoundry.sh/cast/) for the encode-free path.\n\nSet up the same small helper used in [your first 10 minutes](/start/tutorials/your-first-10-minutes), so the steps stay short:\n\n```bash\nexport RPC=https://rpc.citrate.ai\n\nrpc () {\n curl -s \"$RPC\" -H 'content-type: application/json' \\\n -d \"{\\\"jsonrpc\\\":\\\"2.0\\\",\\\"id\\\":1,\\\"method\\\":\\\"$1\\\",\\\"params\\\":${2:-[]}}\"\n}\n```\n\nRead the address you want from `contracts/addresses/40204.json`, the canonical record described on the [contracts reference](/contracts/reference). The examples here use the Wrapped SALT and liquid staking pool addresses from that file; always confirm an address with step 1 before trusting it, because the chain can be re-rolled.\n\n### Step 1, confirm the contract exists\n\nAsk the network for the code at the address. A non-empty result means a contract is deployed there. A bare `0x` means the address is empty or an account, in which case stop, you have the wrong address.\n\n```bash\nrpc eth_getCode '[\"\", \"latest\"]'\n# {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x6080604052...\"} # long hex, a contract is here\n```\n\n### Step 2, call a view function with raw eth_call\n\n`WrappedSALT.symbol()` takes no arguments, so the call data is just its selector. The selector for `symbol()` is `0x95d89b41`. Put the address and the selector in an `eth_call`.\n\n```bash\nrpc eth_call '[{\"to\":\"\",\"data\":\"0x95d89b41\"},\"latest\"]'\n```\n\nThe `result` is an ABI-encoded string. Decoding it gives `wSALT`, the symbol declared as a constant in the source. You can compute any selector yourself with `cast sig \"symbol()\"`, which returns `0x95d89b41`.\n\n### Step 3, let cast encode and decode for you\n\nHand-encoding selectors is error-prone, so for everyday work let `cast` do it. You give it the human-readable signature and it handles both the encoding and the decoding.\n\n```bash\ncast call \"symbol()(string)\" --rpc-url $RPC # wSALT\ncast call \"decimals()(uint8)\" --rpc-url $RPC # 18\n```\n\n### Step 4, read a value that is computed live\n\n`LiquidStakingPool.getSharePrice()` returns SALT per staked-SALT share, scaled by 1e18, and returns exactly 1e18 when the pool is empty. It takes no arguments and reads live state.\n\n```bash\ncast call \"getSharePrice()(uint256)\" --rpc-url $RPC\n```\n\nA function that takes an argument encodes that argument into the call data after the selector. `cast` does the encoding from the signature.\n\n```bash\ncast call \\\n \"balanceOf(address)(uint256)\" \\\n 0x0000000000000000000000000000000000000000 \\\n --rpc-url $RPC\n# 0 (the zero address holds no shares)\n```\n\nOne caution. `LiquidStakingPool.balanceOf(address)` returns the SALT value of a staker's shares, not a raw token count, so the signature alone does not tell you the meaning. Read the contract's NatSpec before assuming what a function returns.\n\n## Reference\n\nThe functions read above, with their source files.\n\n| Contract | Function | Returns | Source |\n|---|---|---|---|\n| WrappedSALT | `symbol()` | `wSALT`, a constant | `contracts/src/WrappedSALT.sol` |\n| WrappedSALT | `decimals()` | `18`, a constant | `contracts/src/WrappedSALT.sol` |\n| LiquidStakingPool | `getSharePrice()` | SALT per share, 1e18-scaled | `contracts/src/LiquidStakingPool.sol` |\n| LiquidStakingPool | `balanceOf(address)` | SALT value of a staker's shares | `contracts/src/LiquidStakingPool.sol` |\n\nFor the full read surface and patterns like reading events, see [interact read-only](/contracts/tutorials/interact-read-only). For the address and ABI sources, see the [contracts reference](/contracts/reference).\n\n## Failure modes\n\n- A `0x` result from `eth_getCode` means no contract is at the address. Re-read it from `contracts/addresses/40204.json`; the chain may have been re-rolled since you copied it.\n- `-32601 Method not found` means the RPC method is misspelled or not served by the node.\n- A call that reverts comes back as an error, not a value. Check that the function exists in the ABI and that any arguments are well-formed.\n- A decoded value that looks wrong is often a signature mismatch. Confirm the return type against the source before reading meaning into it, as with `balanceOf` above.\n\n## Access and canon\n\nPublic and read-only. Nothing here writes state, so you can run it against any Citrate endpoint you can reach without risk. No keys, no credentials, no gas. The public RPC hostname and the contract addresses are public values; re-verify any address with step 1 before trusting it.\n\n## Source and verification\n\nFunctions verified against `citrate-chain` at SHA `9d5959e`: `contracts/src/WrappedSALT.sol` (`symbol`, `decimals`), `contracts/src/LiquidStakingPool.sol` (`getSharePrice`, `balanceOf`). Addresses live in `contracts/addresses/40204.json`, chain 40204, testnet beta. Status: Implemented, testnet beta, pre-audit.\n\nSee also [chain RPC](/chain/rpc) and the [chain CLI](/chain/cli).\n"},"/contracts/x402":{"slug":"/contracts/x402","title":"x402 payment contracts","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/contracts/src/{X402Facilitator,X402Paywall}.sol","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"X402Facilitator","anchor":"x402facilitator"},{"depth":3,"text":"X402Paywall","anchor":"x402paywall"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"These two contracts let a client pay for a single HTTP request rather than holding an account or pre-funding\nan allowance, following the x402 pattern that revives the HTTP 402 Payment Required response. A paywall\nverifies a signed payment and grants time-bound access; a facilitator settles such payments for a provider\nand takes a fee. This page is for contracted integrators building pay-per-request services on the Citrate\nNetwork.\n\n## What it is\n\nIn the x402 pattern a server answers a request with 402 Payment Required, the client signs an authorization\nto move payment, and the server grants access once that authorization settles. On the Citrate Network the\npayment is carried by Wrapped SALT, the EIP-3009 wrapper documented in\n[economics contracts](/contracts/economics): the client signs a `transferWithAuthorization` over wSALT, and\nthe server submits it. The client never needs to pre-approve an allowance, so the request can be paid for\nwith a single signature.\n\n| Surface | Contract | What it does |\n|---|---|---|\n| SC-x402-paywall | X402Paywall | verify a signed payment for a resource and grant time-bound access |\n| SC-x402-facilitator | X402Facilitator | settle signed payments on a provider's behalf, single or batched, taking a fee |\n\nBoth contracts are tier commercial. Their bytecode and interfaces are public on chain, but the settlement\nand fee logic is written for contracted builders. The paywall is explicitly a reference: a production\nprovider should review the access and replay properties against its own needs before relying on it.\n\nThis is the contract layer. The Citrate Network also accelerates x402 signature checks below the contract\nlayer, in the node's x402 payment precompiles at addresses `0x0200` to `0x0209`, which verify EIP-712 and\nEIP-3009 payloads about nine times more cheaply than the equivalent Solidity and fail closed by returning\nthe zero address on a bad signature. The contracts here do not call those precompiles directly; they settle\nthrough wSALT's EIP-3009 functions, and the precompiles are the faster path for a provider that verifies a\npayload off the contract path. For the precompile surface see [chain precompiles](/chain/precompiles), and\nfor how pay-per-request fits the compute marketplace see [Citrate Market](/compute/pool).\n\n## How to use it\n\nThe direct paywall path needs no facilitator. A provider runs it like this.\n\n1. Answer the client's request with 402 Payment Required, naming the resource and its price.\n2. The client signs a wSALT `transferWithAuthorization` to the provider's address for at least the price,\n with a fresh nonce and a validity window.\n3. The provider calls `X402Paywall.verifyAndGrant(resourceId, from, value, validAfter, validBefore, nonce,\n salt, v, r, s)`, where the `nonce` must equal `keccak256(abi.encode(resourceId, salt))` (the C047\n nonce-binding check). On success the payment settles to the provider and access is granted until `now + accessTTL`.\n4. The provider serves the resource. For later requests it checks `hasAccess(payer, resourceId)` and serves\n without a new payment until the grant expires.\n\nTo collect through a facilitator that takes a fee instead, the facilitator holds `FACILITATOR_ROLE` and\ncalls `X402Facilitator.settlePayment(...)` with the client's signed authorization; the gross value is split\ninto the net amount to the provider and the fee to the treasury inside wSALT, in one authorization.\n\n## Reference\n\nEvery function below is read from the cited `.sol` file at the audited SHA.\n\n### X402Facilitator\n\n`contracts/src/X402Facilitator.sol`, `is AccessControl, ReentrancyGuard`. Constructor\n`(wSALT, treasury, feeBps)`, with `feeBps` capped at 1000, ten percent. It settles signed payments and\nroutes a fee to the treasury. The role `FACILITATOR_ROLE` may settle; `DEFAULT_ADMIN_ROLE` may change the\nfee and treasury.\n\n| Function | Access | Purpose |\n|---|---|---|\n| `settlePayment(from, to, value, validAfter, validBefore, nonce, v, r, s)` | `FACILITATOR_ROLE` | settle one payment; consumes a single signed authorization for the gross `value` and splits net plus fee inside wSALT |\n| `batchSettle(PaymentAuthorization[] payments)` | `FACILITATOR_ROLE` | settle a batch, one authorization each, emitting a `BatchSettled` summary |\n| `setFacilitatorFee(uint256 newFeeBps)` | `DEFAULT_ADMIN_ROLE` | change the fee, capped at 1000 bps |\n| `setTreasury(address newTreasury)` | `DEFAULT_ADMIN_ROLE` | change the fee recipient |\n\nSettlement calls `wSALT.transferWithFeeAuthorization`, which consumes one EIP-3009 authorization for the\ngross `value` and splits it internally, so no separate ERC-20 approval is needed and the single-signature\nproperty holds. Public state: `wSALT`, immutable, `treasury`, `feeBps`, and the `FACILITATOR_ROLE`\nidentifier. The `PaymentAuthorization` struct is `(from, to, value, validAfter, validBefore, nonce, v, r,\ns)`. Events: `PaymentSettled(from, to, value, fee, nonce)`, `BatchSettled(count, totalValue, totalFees)`,\n`FeeUpdated`, `TreasuryUpdated`.\n\n### X402Paywall\n\n`contracts/src/X402Paywall.sol`. A reference resource gate. Constructor `(wSALT, resourcePrice)`; the\ndeployer becomes the `provider`. Access is time-bound: `accessGrantedUntil[key]` holds the expiry timestamp\nand `accessTTL` is applied at grant time, defaulting to `DEFAULT_ACCESS_TTL = 1 days`.\n\n| Function | Access | Purpose |\n|---|---|---|\n| `verifyAndGrant(resourceId, from, value, validAfter, validBefore, nonce, salt, v, r, s)` | any caller | require `value >= resourcePrice`, `nonce == keccak256(abi.encode(resourceId, salt))`, and that current access has expired, run the wSALT transfer to the provider, and grant access for `now + accessTTL` |\n| `hasAccess(address payer, bytes32 resourceId)` | view | true while the grant's expiry is in the future |\n| `setPrice(uint256 newPrice)` | the provider | change the resource price |\n| `setAccessTTL(uint256 newTTL)` | the provider | change the access window; must be greater than zero |\n\nPublic state: `wSALT` and `provider`, both immutable, `resourcePrice`, `accessGrantedUntil`, `accessTTL`,\nand `DEFAULT_ACCESS_TTL`. The access key is `keccak256(abi.encodePacked(from, resourceId))`. Events:\n`AccessGranted(payer, resourceId, amount, expiresAt)`, `PriceUpdated`, `AccessTtlUpdated`.\n\nA direct paywall purchase reads as follows.\n\n```text\n1. Client GET /resource -> 402 Payment Required (price, resourceId)\n2. Client signs wSALT transferWithAuthorization to the provider\n3. Provider calls X402Paywall.verifyAndGrant(resourceId, from, value, ...)\n4. On success the payment settles; access is valid for accessTTL\n```\n\n## Design rationale\n\nThe split between a paywall and a facilitator follows the two ways a provider actually collects. A small\nprovider gating its own endpoint wants no intermediary, so the paywall settles straight to the provider and\nrecords nothing but an expiry. A provider that wants someone else to handle settlement, or that batches many\npayments, uses the facilitator, which is why the fee leg lives there and not in the paywall. Both lean on\nwSALT's fee-bearing authorization so the client signs once for the gross amount and the contract does the\nrouting; the alternative, a separate allowance and a second transfer, would have meant either an unbounded\napproval or a settlement that could half-complete. Access is time-bound rather than permanent so a paid\ngrant expires and the same resource can be sold again, which is the normal shape of a metered service.\n\n## Failure modes\n\nThese contracts collect payment, so they are written to fail closed.\n\n- `verifyAndGrant` reverts if the payment is below the price, and refuses to grant while an existing grant is\n still active, so a single signed authorization cannot be replayed into a second window. The wSALT transfer\n itself rejects a reused nonce or an expired validity window.\n- The facilitator's fee is capped at ten percent in both the constructor and `setFacilitatorFee`, and only\n `FACILITATOR_ROLE` may settle, so an arbitrary caller cannot push payments through it.\n- The fee split happens inside wSALT against a digest that binds `(treasury, fee)`, so a submitter cannot\n redirect the fee, and the authorization is marked used once, so a settlement cannot be repeated.\n- As a reference, the paywall leaves one judgment to the integrator: its access window is a single\n per-`(payer, resourceId)` expiry, which is correct for a metered resource but should be reviewed for a\n service that needs per-request rather than per-window gating.\n\n## Access and canon\n\nTier commercial. These contracts are the network's pay-per-request settlement path; the bytecode and\ninterfaces are public on chain, but the settlement and fee detail is written for contracted builders. The\npaywall is a reference implementation for documentation and testing, not a turnkey production gate. No keys,\nmnemonics, or private endpoints appear on this page or are needed to read these contracts.\n\n## Source and verification\n\n- Source repo: `citrate-chain` at SHA `9d5959e`.\n- Files: `contracts/src/X402Facilitator.sol` and `contracts/src/X402Paywall.sol`.\n- Related: the wSALT wrapper these settle through is in `contracts/src/WrappedSALT.sol`, documented in\n [economics contracts](/contracts/economics); the node-level x402 precompiles are at `0x0200` to `0x0209`,\n documented in [chain precompiles](/chain/precompiles).\n- Addresses: both contracts are in the canonical registry `contracts/addresses/40204.json` for chain 40204,\n as `X402Facilitator` and `X402Paywall`. Resolve from there and confirm with `eth_getCode` before sending\n value.\n- Status: Implemented, pre-audit. Both contracts run on testnet 40204 and carry SOL-01 and SOL-12\n remediations in their source comments, but no external third-party audit has been completed. Treat as\n experimental.\n"},"/core/for-agents":{"slug":"/core/for-agents","title":"For agents and headless operators","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-core/src/surfaces/Agent.tsx, src-tauri/src/ceremony.rs, src-tauri/src/lib.rs","syncedSha":"2d88191","toc":[{"depth":2,"text":"The one rule that makes this safe","anchor":"the-one-rule-that-makes-this-safe"},{"depth":2,"text":"What an agent can drive","anchor":"what-an-agent-can-drive"},{"depth":2,"text":"Connecting an external agent","anchor":"connecting-an-external-agent"},{"depth":2,"text":"A note on running unattended","anchor":"a-note-on-running-unattended"}],"body":"Citrate Core is designed to be operated by an agent as well as a person. This page is for anyone pointing an\nagent at their node and account, whether that is the on-device agent in the `Agent` surface or an external\none you connect.\n\n![The Agent surface: a keyless workbench whose actions route to you for approval.](/core/app-agent.png)\n\n## The one rule that makes this safe\n\nAn agent can do a great deal in Citrate Core, but it cannot sign. Every action that would produce a\nsignature, sending value, staking, registering the node, any on-chain write, is routed to the\nSignatureCeremony and waits for a human approval. The agent holds no key and cannot approve on your behalf.\nThis is what lets you give an agent broad reach without handing it your account.\n\nBecause of this, the useful division of labor is: let the agent read, plan, monitor, and prepare; keep the\napproval with a person. An agent can watch the node, summarize activity, draft an action, and queue it for\nyou; you approve the moment that matters.\n\n## What an agent can drive\n\nThrough the app's command surface, an agent can:\n\n- Start, stop, and monitor the node, and read its status, peers, and logs.\n- Read the account: balances, staking position, and activity.\n- Read and write the memory graph and files.\n- Prepare an on-chain action and submit it to the ceremony for approval.\n- Use on-device inference and the model surface.\n\nAnything that signs stops at the approval step. A locked account fails those requests closed rather than\ndegrading to something less safe.\n\n## Connecting an external agent\n\nThe app exposes its capabilities to agents over a local command surface rather than by handing out keys.\nThe same ceremony gate applies to an external agent as to the built-in one: it can request an action, and a\nperson approves it. Keep the node's ports on loopback while doing this; the agent talks to the app, not to\nan exposed node.\n\n## A note on running unattended\n\nIf you want the node to run while you are away, that is fine: the supervisor keeps the process healthy on\nits own and an agent can monitor it. What you should not do is arrange for signatures to happen without a\nperson. The design deliberately has no unattended-signing path, and working around it would defeat the\nprotection the ceremony exists to give you. Queue actions for approval instead, and approve them when you\nare back.\n\nFor the signing model in full, see [keys and safety](/core/safety). For the node lifecycle, see\n[run a node](/core/run-a-node).\n"},"/core/getting-started":{"slug":"/core/getting-started","title":"Getting started with Citrate Core","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-core/README.md, src/onboarding/Onboarding.tsx, src/App.tsx, src-tauri/src/node.rs","syncedSha":"2d88191","toc":[{"depth":2,"text":"Install and open","anchor":"install-and-open"},{"depth":2,"text":"Step 0: Welcome","anchor":"step-0-welcome"},{"depth":2,"text":"Step 1: Sign in","anchor":"step-1-sign-in"},{"depth":2,"text":"Step 2: Verify identity","anchor":"step-2-verify-identity"},{"depth":2,"text":"Step 3: Membership","anchor":"step-3-membership"},{"depth":2,"text":"Step 4: Account ready","anchor":"step-4-account-ready"},{"depth":2,"text":"Step 5: Grant and stake","anchor":"step-5-grant-and-stake"},{"depth":2,"text":"Step 6: Node ignition","anchor":"step-6-node-ignition"},{"depth":2,"text":"After onboarding","anchor":"after-onboarding"}],"body":"This page walks the first run of Citrate Core from the welcome screen to a live node, one step at a time.\nThe whole flow is a guided sequence: you sign in, verify who you are, take a membership, receive your\naccount and your grant, stake, and start the node. Every signature along the way goes through the on-screen\napproval described in [keys and safety](/core/safety).\n\n## Install and open\n\nDownload Citrate Core for your platform and open it. On first launch the app provisions a fresh local\nidentity and the onboarding sequence begins. If you are building from source instead, see the repository\nREADME; the packaged download is the supported path for most people.\n\n## Step 0: Welcome\n\nThe welcome screen states what the app is and offers two doors. **Join the network** starts the guided\nsetup. **Explore free** opens the app in a read-only preview so you can look around before you commit to a\nmembership.\n\n![Step 0, the welcome screen: the app introduces itself and offers Join the network or Explore free.](/core/onboarding-s0-welcome.png)\n\n## Step 1: Sign in\n\nYou sign in to a Citrate identity. The app opens a sign-in against the Citrate authority (`auth.citrate.ai`)\nover a local loopback so the exchange stays on your machine. A first-time visitor creates an identity here;\na returning one signs back in.\n\n![Step 1, sign in: authenticate to your Citrate identity to continue past the welcome screen.](/core/onboarding-s1-sign-in.png)\n\n## Step 2: Verify identity\n\nThe app asks you to verify your identity through VERI, Citrate's in-house verification, as part of membership. You\ncomplete a short check here. The result gates the steps that follow; the verification itself is handled by\nthe identity service, and Citrate Core only carries the outcome.\n\n![Step 2, verify identity: complete the VERI check that gates membership.](/core/onboarding-s2-verify-identity.png)\n\n## Step 3: Membership\n\nMembership is the single thing that funds everything else. Taking it here is what later covers your account\nand your validator stake and opens every surface. The step shows what the membership includes and confirms\nthe amount before anything is charged.\n\n![Step 3, membership: review and take the membership that funds the account and the stake.](/core/onboarding-s3-membership.png)\n\n## Step 4: Account ready\n\nYour Citrate Keyring account is provisioned. This is a smart account on chain 40204: it deploys lazily on\nits first outgoing action, and deposits to its address are safe immediately. The step confirms the account\nis ready and shows its address so you can receive to it.\n\n![Step 4, the account is ready: your Citrate Keyring smart account is provisioned and can receive.](/core/onboarding-s4-wallet-ready.png)\n\n## Step 5: Grant and stake\n\nMembership releases a grant into your account, and the grant is placed into a validator stake so your node\ncan take part in producing blocks. This is a signed sequence: the app walks you through it and asks you to\napprove each on-chain action. When it finishes, your stake is bonded and the membership token is minted to\nyour account.\n\n![Step 5, grant and stake: the grant lands in your account and bonds into a validator stake, step by step.](/core/onboarding-s5-grant-and-stake.png)\n\n## Step 6: Node ignition\n\nThe last step starts your node. The app spawns the node process, joins it to chain 40204, and shows it\ncoming online. It also offers to download the local inference model so on-device AI works without a network\nround trip. From here the app shell opens onto the full sidebar.\n\n![Step 6, node ignition: the node process starts, joins chain 40204, and comes online.](/core/onboarding-s6-node-ignition.png)\n\n## After onboarding\n\nYou land on the [dashboard](/core/tour). Two good next moves:\n\n- Read [run a node](/core/run-a-node) to understand what your node is doing, what it needs, and how to keep\n it healthy at home or in a business.\n- Read [keys and safety](/core/safety) so you understand where your keys live, how signing works, and how\n to back up your recovery phrase.\n\nIf you chose **Explore free** at the welcome screen, you can return to this sequence at any time from the\nmembership prompt; nothing on the network is charged until you take the membership at step 3.\n"},"/core/overview":{"slug":"/core/overview","title":"Citrate Core","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-core/README.md, src/App.tsx, src/shell/Sidebar.tsx, docs/CITRATE_CORE_FEATURE_MAP_AND_SITEMAP.md","syncedSha":"2d88191","toc":[{"depth":2,"text":"What is inside the window","anchor":"what-is-inside-the-window"},{"depth":2,"text":"How it is built","anchor":"how-it-is-built"},{"depth":2,"text":"What it connects to","anchor":"what-it-connects-to"},{"depth":2,"text":"Where to go next","anchor":"where-to-go-next"}],"body":"Citrate Core is the desktop home for the whole federation. It is one window that holds a Citrate Keyring\naccount, runs a full Citrate Network node, and opens onto every service in the network: the account and\nits staking position, a local reader for the BlockDAG, on-device AI inference, file storage, a memory\ngraph, groups and messaging, a compute cluster, model training, and an agent workbench. One membership\nfunds the account, covers the validator stake, and unlocks every surface. Keys are sealed in the operating\nsystem keyring and never leave the machine.\n\nIt is for people who want to hold their account and run a node on their own hardware, at home or in a\nbusiness, without stitching a browser, a key-holding extension, and a server together by hand. It runs on macOS,\nLinux, and Windows. Chain id is 40204 (testnet beta).\n\n![The Citrate Core dashboard: the node vitals strip, the on-device agent, recent activity, and tutorials, inside the grouped sidebar.](/core/app-dashboard.png)\n\n## What is inside the window\n\nThe window is a left sidebar and a main surface. The sidebar groups the surfaces the way you use them\n(`src/shell/Sidebar.tsx`):\n\n- **You** holds your own things: `Dashboard`, the `Wallet` account view, `Storage` (your memory graph),\n `Files` (your storage on the network), `Models`, `Agent`, `Connections`, and `Journal`.\n- **Your Groups** holds shared things: `People`, `Groups`, `Comms`, `Cluster`, `Train`, and `Community`.\n- **Your Node** holds `Node`, the operator surface for the node this app runs.\n- **More** holds `Commissary`, `Settings`, and the `ALF` learning surface.\n\nThe [tour](/core/tour) walks every surface with a screenshot. If you are installing for the first time,\nstart with [getting started](/core/getting-started); if you mainly came to run a node, jump to\n[run a node](/core/run-a-node).\n\n## How it is built\n\nCitrate Core is a Tauri application: a Rust backend (`src-tauri/`) and a React interface (`src/`), packaged\nas one signed desktop app. The backend supervises the node process and other local services (the inference\nruntime, an IPFS node, the memory service), talks to chain id 40204, and holds the account. Every signature,\nwhether it comes from you, from the on-device agent, or from a background service, goes through one\nHIC (Human In Control) approval path called the SignatureCeremony (`src-tauri/src/ceremony.rs`). Nothing else can\nsign, and no key or recovery phrase ever crosses the interface boundary. Key handling and safe operation are\ncovered in [keys and safety](/core/safety).\n\n## What it connects to\n\nCitrate Core is a client for the live federation, not a private copy of it. It reads and writes against the\nsame services documented across this handbook:\n\n| Surface in Core | What it talks to |\n|---|---|\n| `Node` | the Citrate Network node it runs locally, joined to chain 40204 |\n| `Wallet` | your account and its staking position, through the [Citrate Keyring](/aa/identity) |\n| `Models`, `Agent` | on-device inference and the [inference gateway](/sdks/inference-gateway) |\n| `Files`, `Storage` | network file storage and the [memory graph](/apps/memories) |\n| `Comms`, `Groups` | the server-blind [messaging relay](/apps/comms) |\n| `Train` | federated [model training](/research/learning) rounds |\n\n## Where to go next\n\n1. [Getting started](/core/getting-started) installs the app and walks the first-run flow end to end.\n2. [A tour of Citrate Core](/core/tour) is the screen-by-screen reference.\n3. [Run a node](/core/run-a-node) covers the `Node` surface and safe operation at home or in a business.\n4. [Keys and safety](/core/safety) covers the keyring, signing, backups, and updates.\n5. [For agents](/core/for-agents) covers operating the node and account from an agent.\n"},"/core/run-a-node":{"slug":"/core/run-a-node","title":"Run a node from Citrate Core","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-core/src/surfaces/Node.tsx, src-tauri/src/node.rs, src-tauri/config/member-node.toml","syncedSha":"2d88191","toc":[{"depth":2,"text":"The Node surface","anchor":"the-node-surface"},{"depth":2,"text":"What the node needs","anchor":"what-the-node-needs"},{"depth":2,"text":"How it stays correct","anchor":"how-it-stays-correct"},{"depth":2,"text":"Keeping it healthy","anchor":"keeping-it-healthy"},{"depth":2,"text":"Running it headless","anchor":"running-it-headless"}],"body":"Citrate Core runs a full Citrate Network node for you, supervised, inside the app. The `Node` surface is\nthe console for it. This page is for anyone keeping a node healthy at home or in a business. For the\ndaemon-level runbook (the same node, run by hand outside the app), see\n[run a Citrate Node](/operators/run-a-node).\n\n![The Node surface: start and stop, the live log tail, peers, validator status, resources, and crash records.](/core/app-node.png)\n\n## The Node surface\n\nThree tabs: **Operations**, **Earning**, and **Pinning**.\n\nOperations is the console. **Start** and **Stop** control the process. Below them:\n\n- **Log tail** streams the node log (`~/.citrate/core/node.log`) while it runs.\n- **Peers** lists who the node is connected to.\n- **Validator** shows whether your stake is registered, how many blocks you have proposed, your election\n odds, and how many times the supervisor has restarted the process.\n- **Resources and sidecars** shows CPU, memory, the data directory, and whether encryption at rest is on.\n Encryption at rest reads `ON, keyring`: the node's on-disk state is sealed with a key held in the\n operating system keyring.\n- **Crash records** lists any supervised restarts. The supervisor restarts the process with backoff, so a\n single sidecar failure does not take the app down.\n\n## What the node needs\n\nA node is a long-running process that keeps a copy of the ledger live and, once your stake is registered,\ntakes part in producing blocks. Practical requirements:\n\n| Need | Detail |\n|---|---|\n| Network | outbound peer connections on port `30303`; the app dials the network for you |\n| Local ports | the node binds its RPC to `127.0.0.1:8545` and WebSocket to `127.0.0.1:8546`, loopback only |\n| Disk | a data directory under the app's data folder; plan for the ledger to grow over time |\n| Memory | syncing is memory-heavy; budget several gigabytes of headroom while the node catches up |\n| Uptime | the more your node is online, the more consistently it validates and earns |\n\nThe RPC surface is bound to loopback on purpose. It is a local interface for the app, not something exposed\nto the internet. If you ever need to reach it from another machine, put it behind your own reverse proxy\ndeliberately; do not open the port directly.\n\n## How it stays correct\n\nCitrate Core injects the network's consensus settings when it starts the node, and it ships a node\nconfiguration whose peer list includes the sequencer, so a fresh install finds the network on the first\nrun. Two things are true by design and worth knowing:\n\n- Block production is a network role tied to your stake, not a local switch. The shipped configuration\n keeps the local `[mining]` flag off; your node validates through its registered stake, not by racing to\n produce blocks on its own.\n- The node binary is matched to the app. Do not replace it with a binary copied from elsewhere. A mismatched\n node can appear to sync cleanly while computing a different view of the ledger, which quietly forks you off\n the network. Always take the binary that ships with the app, and let the app update it.\n\n## Keeping it healthy\n\n- Leave the app running to keep the node online. The supervisor handles ordinary restarts on its own.\n- Watch the `Node` surface after an update or a restart: the log tail should advance and the peer count\n should be non-zero within a minute or two.\n- Back up your recovery phrase before you rely on the node for a stake. See [keys and safety](/core/safety).\n- The earning side, how a node is paid in SALT for the work it does, is covered under\n [rewards](/operators/rewards).\n\n## Running it headless\n\nAgents and headless operators can drive the same node lifecycle. The signing model stays the same: an agent\ncan start, stop, and monitor the node, but any on-chain action it wants to take is routed to a human\napproval. See [for agents](/core/for-agents).\n"},"/core/safety":{"slug":"/core/safety","title":"Keys, safety, and safe operation","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-core/src-tauri/src/ceremony.rs, src-tauri/src/wallet.rs, CLAUDE.md, src-tauri/src/node.rs","syncedSha":"2d88191","toc":[{"depth":2,"text":"Where your keys live","anchor":"where-your-keys-live"},{"depth":2,"text":"How signing works","anchor":"how-signing-works"},{"depth":2,"text":"Back up your recovery phrase","anchor":"back-up-your-recovery-phrase"},{"depth":2,"text":"Updates","anchor":"updates"},{"depth":2,"text":"What to expose","anchor":"what-to-expose"},{"depth":2,"text":"A safe-operation checklist","anchor":"a-safe-operation-checklist"}],"body":"Citrate Core is built so that the sensitive parts, your keys and your signatures, have exactly one path\nthrough the app and never leave it. This page explains where your keys live, how signing works, and how to\nkeep an install safe at home or in a business.\n\n## Where your keys live\n\nYour account keys are sealed in the operating system keyring and the node's on-disk state is encrypted at\nrest with a key held there too. The `Node` surface shows this as `encryption at rest: ON, keyring`. Keys\nare never written to the interface, never handed to a background service, and never sent over the network.\nNo part of the app can read a key or a recovery phrase across the boundary between the Rust backend and the\ninterface; the code is structured so that is not possible.\n\n## How signing works\n\nEvery signature in Citrate Core goes through one approval path, the SignatureCeremony. This is true whether\nthe request comes from you, from the on-device agent, or from a background service. The rules are strict on\npurpose:\n\n- **One human approval per signature.** There is no auto-approve and no approve-the-last-one. Each approval\n is bound to a specific request and yields exactly one signature.\n- **You see what you are signing.** The approval shows the decoded intent of the action. If the app cannot\n decode the request, it blocks it until you explicitly acknowledge that you are approving raw data.\n- **A locked account fails closed.** If the account is locked, a signing request fails rather than falling\n back to anything less safe.\n- **No sidecar ever signs.** No background service, daemon, or remote holds a key or signs on its own.\n\nThis is why the on-device agent is described as keyless: it can prepare an action, but it cannot complete\none without your approval.\n\n## Back up your recovery phrase\n\nDuring setup the app provisions your account and you take responsibility for its recovery phrase. Back it up\nbefore you rely on the account for anything that matters, especially before you place a stake. The phrase is\nthe only way to restore the account on another machine; Citrate cannot recover it for you. Keep it offline.\n\n## Updates\n\nCitrate Core updates itself through a signed updater. Take updates when the app offers them. The node binary\nis delivered with the app and matched to it: do not replace the node binary by hand, because a mismatched\none can sync cleanly while computing a different view of the ledger and quietly fork you off the network.\nLetting the app manage the binary is what keeps your node on the canonical chain.\n\n## What to expose\n\nThe node's read and write interface is bound to loopback (`127.0.0.1:8545` and `:8546`) so it is reachable\nonly from your own machine. Treat that as the default and keep it. If you have a genuine reason to reach the\nnode from another host, put it behind a reverse proxy you control, with its own authentication; do not open\nthe port to the network directly.\n\n## A safe-operation checklist\n\n- Membership and identity are set up, and your recovery phrase is backed up offline.\n- The app is the only thing that manages the node binary and the node configuration.\n- The node's ports stay on loopback unless you have deliberately fronted them.\n- You approve each signing request after reading its decoded intent, and you never blind-approve raw data\n you do not understand.\n- You take app updates promptly.\n\nFor running the node itself, see [run a node](/core/run-a-node). For driving the app from an agent under the\nsame signing rules, see [for agents](/core/for-agents).\n"},"/core/tour":{"slug":"/core/tour","title":"A tour of Citrate Core","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-core/src/surfaces/, src/shell/Sidebar.tsx, docs/CITRATE_CORE_FEATURE_MAP_AND_SITEMAP.md","syncedSha":"2d88191","toc":[{"depth":2,"text":"You","anchor":"you"},{"depth":3,"text":"Dashboard","anchor":"dashboard"},{"depth":3,"text":"Account","anchor":"account"},{"depth":3,"text":"Storage","anchor":"storage"},{"depth":3,"text":"Files","anchor":"files"},{"depth":3,"text":"Models","anchor":"models"},{"depth":3,"text":"Agent","anchor":"agent"},{"depth":3,"text":"Connections","anchor":"connections"},{"depth":3,"text":"Journal","anchor":"journal"},{"depth":2,"text":"Your Groups","anchor":"your-groups"},{"depth":3,"text":"Groups","anchor":"groups"},{"depth":3,"text":"Comms","anchor":"comms"},{"depth":3,"text":"Cluster","anchor":"cluster"},{"depth":3,"text":"Train","anchor":"train"},{"depth":3,"text":"Community","anchor":"community"},{"depth":2,"text":"More","anchor":"more"},{"depth":3,"text":"Commissary","anchor":"commissary"},{"depth":3,"text":"Settings","anchor":"settings"},{"depth":3,"text":"ALF","anchor":"alf"},{"depth":2,"text":"Next","anchor":"next"}],"body":"This is the screen-by-screen tour of Citrate Core. Each surface is a page in the sidebar; the sidebar groups\nthem as **You**, **Your Groups**, **Your Node**, and **More**. The `Node` surface has its own page,\n[run a node](/core/run-a-node).\n\n## You\n\n### Dashboard\n\nThe home surface. A strip of node vitals across the top (height, peers, finality, node state, staked\namount, and today's validating status), the on-device agent in the center, recent account activity on the\nright, and a set of short tutorials.\n\n![The dashboard: node vitals, the on-device agent, recent activity, and tutorials.](/core/app-dashboard.png)\n\n### Account\n\nYour Citrate Keyring account, under the `Wallet` label. Four tabs: **Overview**, **Staking**, **Activity**,\nand **Identity**. Overview shows liquid SALT, staked SALT, and wrapped SALT, a paymaster meter for\nsponsored actions, and the send and receive panels. The receive panel shows your smart account address,\nwhich deploys lazily on its first outgoing action, so deposits are safe before the account has ever sent\nanything.\n\n![The account surface, Overview tab: balances, the paymaster meter, and send and receive.](/core/app-wallet.png)\n\n### Storage\n\nYour memory graph: a local, searchable knowledge store that agents and the app read from and write to.\nEntries are yours, held on device, and can be anchored to the network when you choose.\n\n![Storage: the local memory graph you and your agents read and write.](/core/app-storage.png)\n\n### Files\n\nYour files on the network storage layer. Pin, browse, and manage content addressed by hash.\n\n![Files: content-addressed storage on the network.](/core/app-files.png)\n\n### Models\n\nBrowse and manage AI models available to the app, including the local inference model the node can serve.\n\n![Models: browse and manage the models available on device and on the network.](/core/app-models.png)\n\n### Agent\n\nA keyless agent workbench. The agent can act on your behalf, and every action it wants to sign is routed to\nyou for approval through the same ceremony the rest of the app uses. It never holds a key.\n\n![Agent: a keyless workbench whose signatures route to you for approval.](/core/app-agent.png)\n\n### Connections\n\nThe people and services your account is connected to across the federation.\n\n![Connections: your links to people and services across the federation.](/core/app-connections.png)\n\n### Journal\n\nA running record of what your node and account have done, in plain language, useful for keeping a personal\nlog or handing context to an agent.\n\n![Journal: a plain-language record of what your node and account have done.](/core/app-journal.png)\n\n## Your Groups\n\n### Groups\n\nCreate and join groups, assign roles, manage a roster, and send messages. Groups are the unit of shared\nmembership and access.\n\n![Groups: create and join groups, assign roles, and manage a roster.](/core/app-groups.png)\n\n### Comms\n\nMessaging over the server-blind Citrate relay. The relay carries sealed messages without being able to read\nthem; see [Citrate Comms](/apps/comms) for the protocol.\n\n![Comms: sealed messaging over the server-blind relay.](/core/app-comms.png)\n\n### Cluster\n\nJoin a compute cluster and share files and capacity with its members.\n\n![Cluster: join a compute cluster and share capacity with its members.](/core/app-cluster.png)\n\n### Train\n\nTake part in federated model training rounds from your own machine. Your data stays local; only the agreed\nupdates leave the device.\n\n![Train: take part in federated training rounds with your data staying local.](/core/app-train.png)\n\n### Community\n\nThe wider community surface: shared spaces and activity across the network.\n\n![Community: shared spaces and activity across the network.](/core/app-community.png)\n\n## More\n\n### Commissary\n\nWhere memberships and entitlements are taken and managed. Checkout opens in your browser; the outcome\nreturns to the app.\n\n![Commissary: take and manage memberships and entitlements.](/core/app-commissary.png)\n\n### Settings\n\nApplication settings, grouped into sections for the account, the node, identity, models, storage, and the\napp itself. This is where you manage the local configuration described in [keys and safety](/core/safety).\n\n![Settings: application, account, node, and identity configuration.](/core/app-settings.png)\n\n### ALF\n\nThe ALF learning surface: the education and mentorship programs on the network, reachable from inside the\napp.\n\n![ALF: the learning and mentorship surface inside the app.](/core/app-alf.png)\n\n## Next\n\nThe one surface not shown above is `Node`, the operator console for the node this app runs. It has its own\npage: [run a node](/core/run-a-node).\n"},"/core/troubleshooting":{"slug":"/core/troubleshooting","title":"Troubleshooting Citrate Core","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-core/src-tauri/src/node.rs, README.md, docs/RELEASE_LINUX.md","syncedSha":"2d88191","toc":[{"depth":2,"text":"The node will not come online, or shows zero peers","anchor":"the-node-will-not-come-online-or-shows-zero-peers"},{"depth":2,"text":"The node syncs but the height looks wrong, or you suspect a fork","anchor":"the-node-syncs-but-the-height-looks-wrong-or-you-suspect-a-fork"},{"depth":2,"text":"The node uses a lot of memory while catching up","anchor":"the-node-uses-a-lot-of-memory-while-catching-up"},{"depth":2,"text":"The window opens blank on Linux","anchor":"the-window-opens-blank-on-linux"},{"depth":2,"text":"An update prompt fails or an update banner appears repeatedly","anchor":"an-update-prompt-fails-or-an-update-banner-appears-repeatedly"},{"depth":2,"text":"A signing prompt will not let me approve","anchor":"a-signing-prompt-will-not-let-me-approve"},{"depth":2,"text":"On-device AI does not respond","anchor":"on-device-ai-does-not-respond"},{"depth":2,"text":"Where to get more detail","anchor":"where-to-get-more-detail"}],"body":"Common issues and what to do about them. If something here does not cover your case, the `Node` surface log\ntail and the app's settings are the first places to look.\n\n## The node will not come online, or shows zero peers\n\nGive it a minute or two after a start or an update: the log tail should begin to advance and the peer count\nshould rise above zero. If it stays at zero peers, the node is not reaching the network. Check that your\nmachine has outbound network access on port `30303` and that any firewall is not blocking it. Citrate Core\nships a node configuration whose peer list already includes the sequencer, so a fresh install should find\nthe network on its own; if you have edited the node configuration by hand, restore the shipped one.\n\n## The node syncs but the height looks wrong, or you suspect a fork\n\nDo not run a node binary you copied from somewhere else. A mismatched binary can sync cleanly while\ncomputing a different view of the ledger, which forks you off the network without any obvious error. Take\nthe binary that ships with the app and let the app update it. Reinstalling the current release restores the\nmatched binary.\n\n## The node uses a lot of memory while catching up\n\nSyncing is memory-heavy while the node is catching up to the current height. Budget several gigabytes of\nheadroom during the initial sync; usage settles once the node is caught up. If your machine is tight on\nmemory, keep other heavy applications closed during the first sync.\n\n## The window opens blank on Linux\n\nOn some Linux graphics stacks the window renders blank because the interface cannot get a GPU surface. Start\nthe app with `WEBKIT_DISABLE_DMABUF_RENDERER=1` set in the environment. This is a known interaction with\ncertain drivers and does not indicate a problem with your account or the node.\n\n## An update prompt fails or an update banner appears repeatedly\n\nIf an update check cannot reach a published release it may surface a one-off message. It is harmless: your\naccount and node are unaffected. Take the update when the app offers a working one; if a prompt is stuck,\nrestart the app.\n\n## A signing prompt will not let me approve\n\nIf the account is locked, signing requests fail closed by design. Unlock the account and try the action\nagain. If the prompt shows raw, undecoded data, the app is telling you it could not decode the action; only\nacknowledge raw mode if you are certain of what you are approving. See [keys and safety](/core/safety).\n\n## On-device AI does not respond\n\nThe local inference model is offered as a download during setup. If you skipped it, the on-device agent and\nsome model features will not have a model to run. Download the model from the `Models` surface, then retry.\n\n## Where to get more detail\n\n- The `Node` surface log tail and crash records for anything node-related.\n- [Run a node](/core/run-a-node) for what a healthy node looks like.\n- [Keys and safety](/core/safety) for anything about signing, the account, or updates.\n"},"/enterprise/compliance-full":{"slug":"/enterprise/compliance-full","title":"Compliance Posture, Full Package (Gated)","tier":"public","orgId":null,"sourceKind":"gated","source":"citrate-compliance/","syncedSha":"8757357","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to request access","anchor":"how-to-request-access"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This page confirms that a full compliance package exists and explains how to request it. It is a pointer, not the package. The audit-grade material itself is confidential and is never built into these public docs.\n\n## What it is\n\nBehind the public summary at [Compliance posture](/enterprise/compliance) sits a complete compliance package: System Security Plans, control crosswalks against the relevant NIST families, control narratives, plans of action with milestones, and the evidence that backs each one. We keep that package in a private home and treat it as the source of record for assessors and auditors.\n\nThe public summary tells you where we stand, in plain language. This package is what an assessor reads when they need the detail behind that summary. We do not reproduce any of that detail here: no control scores, no plan-of-action items, no evidence, and no named third parties appear on this page or anywhere in the public docs.\n\n## How to request access\n\nAccess is for people with a contractual reason to read the package: contracted assessors, issued auditors, and named principals running due diligence, each under a non-disclosure agreement.\n\n1. Ask through your commercial or compliance contact at Citrate, or through your account channel.\n2. We confirm your role and put the non-disclosure agreement in place.\n3. We grant time-bound access to the package in its private home.\n\n## Access and canon\n\nThe full package is confidential. It is served at request time from its private home, under a non-disclosure agreement, to named recipients only. It is never copied into this documentation tree, and the public build never includes it. Every access is logged. The sanitized public summary, which anyone may read, is at [Compliance posture](/enterprise/compliance).\n\nSome of the frameworks the package covers are certified and some are in progress. We label each one honestly in the package and in the public summary, and we show no scores on this page.\n\n## Source and verification\n\nPrivate source: the `citrate-compliance` corpus and its audit archive. Audited against `citrate-compliance` SHA `8757357`. Status: Implemented (the package exists and is maintained); certifications in progress are labeled as in progress, with no scores shown here.\n"},"/enterprise/compliance":{"slug":"/enterprise/compliance","title":"Compliance posture, public and sanitized","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-compliance/frameworks/README.md","syncedSha":"8757357","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes and honest gaps","anchor":"failure-modes-and-honest-gaps"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Where Citrate stands on the major compliance frameworks, stated honestly and by deployment context. This is\nthe sanitized public summary. It is a point-in-time status, not a claim of certification or attestation, and\nnot legal advice. Where a framework is in progress, it is in progress, and we say so plainly.\n\n## What it is\n\nTwo facts shape the whole posture, and the rest follows from them.\n\nFirst, Citrate ships on-premise. The customer operates the software on hardware it controls; there is no\nvendor-operated hosted environment. Citrate Ground is the private, on-premise half of the network, and it is\nthe default. The public Citrate Network only ever sees what an operator chooses to publish.\n\nSecond, the operator of the software performs no data services. It does not host, store, transmit, process,\nor access customer data, and it is not a data processor, controller, business associate, or sub-processor\nunder any regime. That keeps the software operator outside the customer's authorization boundary: the\ncustomer's own controls govern the regulated workload.\n\nBecause of those two facts, most frameworks apply to the customer's deployment rather than to a hosted\nservice. The compliance floor therefore depends on where Citrate runs:\n\n- Public Citrate Network. Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity. SOC 2 is the general floor\n for the surrounding services.\n- Citrate Ground, on-premise. The customer's deployment carries the floor for its own context: HIPAA for\n health, NIST 800-171 and 800-53 with ITAR considerations for federal and regulated work, SOC 2 generally.\n- Citrate Schools. FERPA, COPPA, and CIPA, covered separately under [K-12 and education](/enterprise/k12).\n\nThe table below describes Citrate's own readiness work to support customers who must meet these frameworks.\nIt does not assert that any certification has been earned where it has not.\n\n## How to use it\n\n1. Find your deployment context above, then read the matching rows in the table.\n2. Read the honest caveat in each row before you plan around the status. A framework \"in progress\" is not a\n framework held.\n3. For an authoritative, current, per-framework status with evidence, request the gated package. It is\n available to contracted and identity-verified principals and assessors under agreement. See\n [compliance posture, full](/enterprise/compliance-full).\n4. For the K-12 floor specifically, read [K-12 and education](/enterprise/k12). For the federal context,\n read [federal](/enterprise/federal).\n\n## Reference\n\nFramework status, sanitized and point-in-time. Source: `citrate-compliance/frameworks/README.md` and the\nper-framework digests in `citrate-compliance/frameworks/`.\n\n| Framework | Status | Honest caveat |\n|---|---|---|\n| SOC 2 (Type I and II) | In progress. Control narratives drafted across the Trust Service Criteria; a CPA engagement is underway. | Not yet attested. No SOC 2 report has been issued. Type II additionally requires an operating-effectiveness observation window. |\n| CMMC 2.0 Level 1 | Self-attestation model; the Level 1 practices are filled. | Self-attested, not third-party assessed. |\n| CMMC 2.0 Level 2 (NIST 800-171 r2) | In progress. A self-assessment draft is authored; remediation of submission blockers is underway. | Not yet submitted and not certified. A third-party assessment would be a later, separate step for contracts that require it. |\n| FedRAMP (Low and Moderate) | Not started as an authorization; sponsor-gated. Outline material is authored. | FedRAMP is the cloud pathway. Because the first deployments are on-premise, it is not required for them. An authorization requires an agency sponsor, a cloud provider, and a third-party assessor. |\n| FIPS 140-3 | In progress, and out of our hands. The underlying cryptographic module is in the validation queue. | Validation timing is controlled by the validation program, not by Citrate. Tracked as a known residual. |\n| ITAR and EAR | An export-control overlay; public-surface leakage scanning and a disclaimer-check gate are in place. | Responsibility for any controlled technical data deployed in the software remains with the customer. An outside-counsel opinion is still pending. |\n| FERPA, COPPA, CIPA | The education-privacy floor for the school product track. | See [K-12 and education](/enterprise/k12). |\n\n## Design rationale\n\nWe deliberately avoid certification language we have not earned. We describe the posture as high-assurance,\non-prem capable, air-gap friendly, role-gated, auditable, and private-network deployable, with encryption in\ntransit and at rest. We do not write \"military grade,\" \"fully compliant everywhere,\" \"impossible to hack,\"\nor \"zero risk.\" The reason is practical as much as honest: procurement teams want evidence, baselines, and\ncontract language, and a slogan fails every one of those tests.\n\nThe on-premise default is what makes the rest coherent. If the software operator never holds customer data,\nthen the customer's own assessment governs the regulated workload, and the questions a contracting officer\nasks have clean answers rather than negotiated ones.\n\n## Failure modes and honest gaps\n\n- SOC 2 is in progress, not attested. Treat any reliance on a SOC 2 report as premature until one is issued.\n- CMMC Level 2 is drafted, not submitted or certified. A third-party assessment is a separate future step.\n- FedRAMP is not pursued as an authorization for on-premise deployments and requires a sponsor if it ever is.\n- FIPS 140-3 validation timing depends on the external validation program, not on us.\n- The ITAR and EAR outside-counsel opinion is pending; export responsibility for controlled data stays with\n the customer.\n\n## Access and canon\n\nThis sanitized summary is intentionally public so a prospective customer or partner can understand where we\nstand without an agreement in place. What is gated is the full framework packages: the system security\nplans, control crosswalks, plan-of-action items, self-assessment detail, and audit evidence. Those live in\nthe private `citrate-compliance` corpus and the audit archive, behind contract and identity verification.\nSee [compliance posture, full](/enterprise/compliance-full) and [security questionnaires](/enterprise/questionnaires).\n\nNo control scores, plan-of-action item detail, operator personal data, named CPA, sponsor, or counsel, or\nremediation timelines appear on this page. Every node operator on the public network is identity-checked\nthrough VERI; Citrate keeps the verification result, not the personal data behind it.\n\n## Source and verification\n\nSource: `citrate-compliance/frameworks/README.md` and the per-framework digests under\n`citrate-compliance/frameworks/` (private repo), which point in turn to the executive posture briefing and\nthe audit archive. Audited against `citrate-compliance` SHA `8757357`. Status: Specified. The posture and the\nframework mappings are written down and current as of that SHA; the certifications described as \"in progress\"\nare not yet held, and none of the underlying scores or evidence are reproduced here.\n"},"/enterprise/dpa":{"slug":"/enterprise/dpa","title":"Data Processing Agreement (Gated)","tier":"public","orgId":null,"sourceKind":"gated","source":"citrate-compliance/","syncedSha":"8757357","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to request access","anchor":"how-to-request-access"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This page confirms that a Data Processing Agreement exists and explains how to request it. The agreement itself is contractual and gated; it is not authored in these public docs.\n\n## What it is\n\nCitrate maintains a Data Processing Agreement, with the service-level and subprocessor terms that go alongside it, as part of its contracting package. Because Citrate runs on-premise on Citrate Ground, a customer's data and models stay on the customer's own hardware, and the data-handling boundary sits with the customer. The agreement is what states that boundary in writing, so each side knows what it is responsible for.\n\nWe do not reproduce any of the terms here. No service levels, no subprocessor identities, and no customer specifics appear on this page or anywhere in the public docs.\n\n## How to request access\n\nAccess is for named principals with a contractual reason to read the agreement, each under a non-disclosure agreement.\n\n1. Ask through your account channel or your commercial contact at Citrate.\n2. We confirm your role and put the non-disclosure agreement in place.\n3. We share the current document of record from its private home.\n\n## Access and canon\n\nThe agreement is confidential. It is served at request time from its private home, under a non-disclosure agreement, to named recipients only. It is never copied into this documentation tree, and the public build never includes it. Every access is logged. The sanitized public summary of our compliance posture, which anyone may read, is at [Compliance posture](/enterprise/compliance).\n\n## Source and verification\n\nPrivate source: the `citrate-compliance` corpus. Audited against `citrate-compliance` SHA `8757357`. Status: Implemented (the agreement exists and is maintained as the document of record); no terms are shown here.\n"},"/enterprise/federal":{"slug":"/enterprise/federal","title":"Federal and On-Prem Isolation (Gated)","tier":"public","orgId":null,"sourceKind":"gated","source":"citrate-compliance + nist-agent","syncedSha":"8757357","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to request access","anchor":"how-to-request-access"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This page confirms that a federal and on-prem isolation package exists and explains how to request it. The package is confidential; it is never built into these public docs, and nothing here is a claim of certification or authorization.\n\n## What it is\n\nFor federal and defense engagements, Citrate maintains a confidential package covering the federal control families, the authorization pathway, and the export-control overlay that those engagements require. Citrate runs on-premise on Citrate Ground and can run air-gapped, so a customer deploys inside their own authorization boundary, on their own hardware. The vendor performs no data services and is not itself inside that boundary.\n\nThe package ties to the air-gapped deployment described for [Citrate NIST Agent](/apps/nist-agent), which is the surface that runs against the federal control corpus on Citrate Ground. The detail behind all of this, the control implementations, the system security plan bodies, and any sponsor or assessor particulars, stays in the private home. No scores, control detail, sponsor names, or customer specifics appear on this page or anywhere in the public docs.\n\nThese frameworks are in progress, not certified. Nothing on this page is a claim of authorization to operate, of certification, or of attestation. The honest public summary is at [Compliance posture](/enterprise/compliance).\n\n## How to request access\n\nAccess is for people with a contractual reason to read the package: federal contracting officers, sponsors, contracted assessors, and issued auditors, each under a non-disclosure agreement.\n\n1. Ask through your commercial or compliance contact at Citrate.\n2. We confirm your role and put the non-disclosure agreement in place.\n3. We grant time-bound access to the package in its private home.\n\n## Access and canon\n\nThe package is confidential. It is served at request time from its private home, under a non-disclosure agreement, to named recipients only. It is never copied into this documentation tree, and the public build never includes it. Every access is logged. The sanitized public summary, which anyone may read, is at [Compliance posture](/enterprise/compliance); for the schools deployment context see [Citrate Schools](/enterprise/k12).\n\n## Source and verification\n\nPrivate source: the `citrate-compliance` corpus and the `nist-agent` control corpus. Audited against `citrate-compliance` SHA `8757357`. Status: Specified (the package and the air-gapped deployment are designed and documented; the frameworks it targets are in progress, not certified, with no scores shown here).\n"},"/enterprise/procurement":{"slug":"/enterprise/procurement","title":"Procurement, how to buy Citrate","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-commercial/CITRATE_PROCUREMENT_ORDER_FORM.md","syncedSha":"fea06db","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes and honest gaps","anchor":"failure-modes-and-honest-gaps"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The non-sensitive shape of a Citrate procurement: a master agreement, order forms, and statements of work.\nThis page is for contracted and identity-verified principals planning a purchase. Deal-specific economics,\nnamed parties, and rate cards are negotiated privately and are not published here. Nothing on this page is\na binding offer.\n\n## What it is\n\nCitrate is procured as software you run, not a hosted service. The software is delivered to infrastructure\nyou control, and you operate it; there is no vendor-operated hosting environment. The standard commercial\nstructure has three layers:\n\n1. A master license and implementation services agreement, the umbrella contract that governs the\n relationship: definitions, IP ownership, confidentiality, warranties, indemnification, limitation of\n liability, term and termination, and dispute resolution. Signed once.\n2. Order forms. Each transaction, a license procurement, an implementation engagement, or ongoing support,\n is issued as an order form under the master agreement. An order form carries the commercial specifics for\n that transaction and prevails over the master agreement for the deal it governs.\n3. Statements of work. Implementation is scoped phase by phase as statements of work, signed by a joint\n steering committee, and billed on a time-and-materials basis against a rate card.\n\nThree commercial properties are fixed, not negotiable per deal:\n\n- On-premise, customer-controlled delivery. The software is delivered; you operate it. There is no\n vendor-operated hosting environment.\n- No data services. The software operator does not host, store, transmit, process, or access customer data,\n and is not a data processor, controller, business associate, or sub-processor under any regime. This keeps\n the operator outside your authorization boundary. See [compliance posture](/enterprise/compliance).\n- A one-time perpetual license for the software, plus separately billed time-and-materials implementation.\n There is no recurring subscription or usage fee for the license itself.\n\n## How to use it\n\nA typical deal proceeds in this order.\n\n1. Introduction and mutual non-disclosure, before any deal-specific material is shared.\n2. Discovery, the first implementation phase: an assessment of your environment, identity provider,\n monitoring stack, and compliance posture.\n3. The master agreement and the first order form for the license, executed with the deal-specific economics\n filled in by the commercial team and outside counsel.\n4. Statements of work per phase: discovery, then installation and configuration, then integration and\n validation, then knowledge transfer and operator training, then acceptance testing.\n5. Acceptance and escrow. The acceptance certificate triggers the final license milestone, and the source\n escrow is deposited.\n6. Optional ongoing support or a resale track, each via a separate order form.\n\nTo receive the procurement template, the master agreement, and the rate card under agreement, contact the\ncommercial team through your account channel. Per-deal materials are released after non-disclosure and\nidentity verification. See [district registration](/apps/district-registration) for the K-12 onboarding\npath specifically.\n\n## Reference\n\nThe procurement template, `citrate-commercial/CITRATE_PROCUREMENT_ORDER_FORM.md`, defines the following\ncomponents at a structural level.\n\n| Component | What it covers, non-sensitive |\n|---|---|\n| Order form, license | Effective date, parties, a fee schedule keyed to milestones (execution, delivery, acceptance), location of use, and hosting marked not applicable because delivery is on-premise. |\n| Order form, implementation | A phased engagement scoped against a rate card, invoiced monthly in arrears, governed by a steering committee. |\n| License grant | Perpetual, non-exclusive, worldwide; rights to use, copy, modify, deploy, and white-label on customer-controlled infrastructure; restrictions on standalone resale and on open-sourcing. |\n| Deliverables at delivery | Source, reproducible-build binaries, smart-contract source and tests, a documentation set, TLA+ specifications, build and test scripts, and detached cryptographic signatures over every artifact. |\n| Acceptance testing | A defined window, 90 days by default, with an acceptance-certificate or rejection-notice path, and deemed acceptance if neither issues. |\n| No-data-services carve-out | The explicit, absolute statement that the operator never holds customer data, central to your compliance boundary. |\n| Master agreement terms | Confidentiality, IP ownership, fees and payment, warranties, indemnification, limitation of liability, term and termination, governing law, and arbitration. |\n| Source-code escrow | A deposit with a mutually agreed escrow agent, with defined release conditions: insolvency, cessation of operations, or an uncured warranty breach. |\n| Resale addendum | An optional, separately executed track if you later wish to resell the software. |\n\nThe supporting templates in `citrate-commercial/commercial/` cover the federal procurement path: a\ncommercial-item determination, representations and certifications, a statement-of-work template, a mutual\nnon-disclosure template, a source-code escrow agreement, and an acceptance test plan, among others. They are\nreleased to contracted principals, not authored in this docs tree.\n\n## Design rationale\n\nThe structure exists to remove negotiation friction rather than create it. A perpetual license plus\ntime-and-materials implementation separates what you own from what you pay people to do, so the two are\npriced and accepted independently. The no-data-services carve-out is the load-bearing term: because the\noperator never holds your data, your own assessment governs the regulated workload, and a contracting\nofficer's questions have clean answers. The mandatory knowledge-transfer phase exists so your own operations\nteam can run the deployment without us, which is the point of buying software you run.\n\n## Failure modes and honest gaps\n\n- The procurement template is a draft for legal review. Outside counsel reviews every clause against your\n deal context, jurisdiction, and regulatory profile before execution.\n- The structure is final; the economics are not. Dollar values, dates, the customer entity, hosting\n locations, and warranty scope are negotiated per deal and are not published here.\n- Implementation estimates are good-faith estimates only. Actual hours are invoiced against the rate card.\n- Acceptance has a deadline. If you issue neither an acceptance certificate nor a rejection notice within the\n acceptance window, acceptance is deemed automatic.\n\n## Access and canon\n\nThis page is the commercial-tier structural summary, shared with contracted and identity-verified principals\nso a buyer's procurement team can plan. The terms and economics are gated to the private\n`citrate-commercial` repo. No dollar values, named customer or vendor entities, rate-card figures, patent\nschedule, signatory personal data, or hosting addresses are reproduced here; those are deal-specific and\nconfidential. Per-company spaces are provisioned at runtime per contract and are not authored in this docs\ntree. For where the operator sits in your authorization boundary, see [compliance posture](/enterprise/compliance).\n\n## Source and verification\n\nSource: `citrate-commercial/CITRATE_PROCUREMENT_ORDER_FORM.md` and the templates under\n`citrate-commercial/commercial/` (private repo). Audited against `citrate-commercial` SHA `fea06db`. Status:\nSpecified. The procurement structure is written down and current as of that SHA; it is a template for legal\nreview, not an executed agreement, and the deal-specific terms it brackets are negotiated privately.\n"},"/enterprise/questionnaires":{"slug":"/enterprise/questionnaires","title":"Security Questionnaires, SIG and CAIQ (Gated)","tier":"public","orgId":null,"sourceKind":"gated","source":"citrate-compliance/","syncedSha":"8757357","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to request access","anchor":"how-to-request-access"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This page confirms that prepared security-questionnaire responses exist and explains how to request them. The completed responses are gated; they are not authored in these public docs.\n\n## What it is\n\nCitrate keeps prepared responses to the standard third-party security questionnaires, the Shared Assessments SIG and the Cloud Security Alliance CAIQ, along with customer-specific variants. Each response is backed by the evidence in the private compliance corpus, so a reviewer reads answers that match the package, not answers written for the occasion.\n\nWe treat completed responses as confidential. They carry the same security-sensitive detail as the full compliance package, so we do not reproduce any answers, control mappings, or evidence on this page or anywhere in the public docs.\n\n## How to request access\n\nAccess is for procurement and security-review teams running vendor due diligence, as named principals under a non-disclosure agreement.\n\n1. Ask through your commercial contact at Citrate, naming the questionnaire you need.\n2. We confirm your role and put the non-disclosure agreement in place.\n3. We share the completed SIG or CAIQ response from its private home.\n\n## Access and canon\n\nThe completed responses are confidential. They draw on the gated [full compliance package](/enterprise/compliance-full) and are served at request time from their private home, under a non-disclosure agreement, to named recipients only. They are never copied into this documentation tree, and the public build never includes them. Every access is logged. The sanitized public summary, which anyone may read, is at [Compliance posture](/enterprise/compliance).\n\n## Source and verification\n\nPrivate source: the `citrate-compliance` corpus. Audited against `citrate-compliance` SHA `8757357`. Status: Implemented (prepared responses exist and are maintained); no answers or mappings are shown here.\n"},"/methodology/rules":{"slug":"/methodology/rules","title":"The 13 Agentile rules","tier":"public","orgId":null,"sourceKind":"linked","source":"docs/AGENTILE_RULES.md","syncedSha":"cd729ed","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The federation-wide rules that govern every repository under CitrateNetwork. People and agents follow them\nequally. This page is a one-line-each index; the full statement with rationale lives in\n`docs/AGENTILE_RULES.md`, with the federation-active copy in\n`citrate-federation/agentile/rules/CORE_RULES.md`. Per Rule 9, where this index and those files differ, the\nfiles win.\n\n## What it is\n\nThe rules constrain what can ship; the [workflow](/methodology/workflow) constrains when and how. They\ncarry over from the pre-split monorepo and are enforced by CI and review together. If the methodology is\nnew to you, start with the [Agentile primer](/start/agentile).\n\n## Reference\n\n| # | Rule | In one line |\n|---|---|---|\n| 0 | Read before writing code | Read `AGENT_ENTRY.md` and the repo's `owners.md` before starting. |\n| 1 | No mocks, no stubs, no TODOs | Production paths contain real code; mocks live behind `#[cfg(test)]` or a dev flag. |\n| 2 | Test count only goes up | The `cargo test --workspace` count is monotone within a sprint; removing a test needs an ADR. |\n| 3 | Audits are immutable | Reports in `audits/` are dated and never edited; corrections go in a dated follow-up. |\n| 4 | Sprint file is authoritative | Status lives in `sprints/active/`, not in chat, memory, or a PR description. |\n| 5 | Rule-12 frontmatter | Every document carries `created`, `branch`, `author`, and `status`. |\n| 6 | Daily benchmark on chain crates | Sessions touching core `citrate-chain` crates end with a benchmark run. |\n| 7 | Data-source tracing | Every endpoint declares its data source before it is implemented. |\n| 8 | Zero `.unwrap()` in production | `grep .unwrap() src/` returns zero in production crates; CI enforces it. Use `?` and typed errors. |\n| 9 | One source of truth per topic | Do not duplicate docs. Link, do not copy. |\n| 10 | Authorization before destruction | Force-push, delete, or rotation needs explicit human sign-off, not just green CI. |\n| 11 | Federation manifest is canonical | `citrate-federation/manifest.toml` wins over any per-repo divergence. |\n| 12 | Cross-repo deps follow the drift map | Add a `[[drift]]` manifest entry first, then the `Cargo.toml` or `package.json` dependency. |\n| 13 | Visibility flips need sign-off | Private to public on a Tier-1 repo needs federation-lead sign-off (and customer sign-off where the work is customer-specific). |\n\nA note on numbering: the frontmatter constraint is called Rule 5 in the federation renumbering and Rule 12\nin the archive numbering. It is the same rule.\n\n## Access and canon\n\nPublic. The rules are public-good methodology and contain no secrets. The internal procedures that apply\nthese rules to sensitive operations are gated; see [SOPs](/methodology/sops).\n\n## Source and verification\n\nThe canonical sources are `docs/AGENTILE_RULES.md` (with rationale) and\n`citrate-federation/agentile/rules/CORE_RULES.md` (federation-active), at SHA `cd729ed`. Status:\nImplemented.\n"},"/methodology/sops":{"slug":"/methodology/sops","title":"Standard operating procedures","tier":"public","orgId":null,"sourceKind":"authored","source":"per-surface tutorials + repo READMEs","syncedSha":"cd729ed","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"An index of the standard operating procedures Citrate documents for the people who use the network:\ncustomers, developers, and node operators. Internal procedures (incident response, access review, hardware\ndisposal) are gated and not listed here.\n\n## What it is\n\nA standard operating procedure is a repeatable, audited procedure. Citrate tiers them by audience:\n\n- **Public and developer procedures** that any builder can follow, written as runnable tutorials on the\n surface they belong to.\n- **Commercial and operator procedures** for paid seats and node operators, some identity-gated, written on\n the relevant Citrate Market, Citrate Node, and enterprise pages.\n- **Internal procedures** the team runs on the network itself (incident, access review, key rotation).\n These are confidential: served only through the gated `/internal/sops` route, sourced from a private\n repository, and never built into the public docs.\n\nThis page indexes the first two. Per Rule 9, each entry links to the page where the procedure actually\nlives rather than restating it.\n\n## Reference\n\nPublic and developer procedures:\n\n| Procedure | Audience | Where it lives |\n|---|---|---|\n| Your first 10 minutes on Citrate | developer | [tutorial](/start/tutorials/your-first-10-minutes) |\n| Call the Citrate RPC | developer | [tutorial](/chain/tutorials/call-citrate-rpc) |\n| Read the BlockDAG | developer | [tutorial](/chain/tutorials/read-the-dag) |\n| Deploy a contract with the CLI | developer | [tutorial](/chain/tutorials/deploy-a-contract-with-the-cli) |\n| Sign in with a passkey | developer, customer | [tutorial](/aa/tutorials/sign-in-with-a-passkey) |\n| Explore a transaction in CitrateScan | customer | [tutorial](/apps/tutorials/explore-a-transaction) |\n| Install the Citrate Keyring extension | customer | [tutorial](/apps/tutorials/install-the-wallet-extension) |\n\nCommercial and operator procedures (commercial tier; operator depth is identity-gated where noted):\n\n| Procedure | Audience | Where it lives |\n|---|---|---|\n| Run a node | operator | [Citrate Node](/operators/run-a-node) |\n| Sell compute end to end | operator (verified) | [node agent](/compute/node-agent) |\n| Run a training worker | operator | [compute pool](/compute/pool) |\n| Rewards, reputation, and slashing protection | operator | [Citrate Node](/operators/run-a-node) |\n| Request a verification packet | enterprise customer | enterprise and compliance (commercial) |\n| District onboarding | enterprise customer (verified) | [district registration](/apps/district-registration) |\n\nEntries without a live link are tracked stubs in the registry; this index is the checklist for filling\nthem.\n\nHow procedures are authored, numbered, reviewed, and retired is defined in the federation SOP standard\n(`ops/04_SOP_STANDARD.md`). That standard and its commitment template are internal and confidential: the\nstandard governs how the team writes procedures, and it is not part of the public build. What is public is\nthe outcome, the customer, developer, and operator procedures linked above.\n\n## Access and canon\n\nThis index is public. The individual operator and enterprise procedures are commercial (some identity-\ngated) and gate at the linked page. Internal procedures are confidential and excluded from the build. The\ninternal set, served only through the entitlement-gated `/internal/sops` route to admins and issued\nauditors, covers incident response, access review, key and secret rotation, hardware disposal, the FIPS\nmodule tracker, and auditor onboarding. The split is deliberate: public-good procedures stay public, and\nthe procedures for the network's own trust boundary are gated. Nothing is hidden by obscurity, and no\nprocedure on any tier contains a credential.\n\n## Source and verification\n\nProcedures are sourced from the per-surface tutorials and repository READMEs (linked, not copied, per\nRule 9). The SOP-authoring standard is `ops/04_SOP_STANDARD.md` (confidential, not transcribed here). At\nSHA `cd729ed`. Status: Implemented.\n"},"/methodology/workflow":{"slug":"/methodology/workflow","title":"The Agentile sprint workflow","tier":"public","orgId":null,"sourceKind":"linked","source":"docs/AGENTILE_WORKFLOW.md","syncedSha":"cd729ed","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"How work moves from idea to active to completed across the federation, and why that leaves a clean audit\ntrail. This page summarizes; the full choreography lives in `docs/AGENTILE_WORKFLOW.md`. Per Rule 9, the\ncanonical file governs where the two differ. Its companion is [the 13 rules](/methodology/rules).\n\n## What it is\n\nWhere the [rules](/methodology/rules) constrain what can ship, the workflow constrains when and how. The\nunit of work is a **sprint file**: a dated Markdown document, under Rule-12 frontmatter, that is the single\nsource of truth for a workstream's status (Rule 4). A federation sprint spans repositories and lives in\n`citrate-federation/agentile/sprints/`; a repo sprint touches one repository and lives in\n`citrate-federation/repos//sprints/`. Same format, same lifecycle.\n\n## How to use it\n\nThe lifecycle, step by step:\n\n1. **Kickoff.** Create `sprints/active/.md` with Rule-12 frontmatter and fill in the goal, scope,\n out-of-scope, and the plan table. Update `CURRENT.md` so observers can see it is active.\n2. **Daily updates.** Each session that advances the sprint appends one dated line to the daily-updates\n section. The sprint file is the record, not a chat thread (Rule 4).\n3. **Decisions become ADRs.** Architectural choices and trade-offs get a short ADR at\n `adrs/ADR-YYYY-MM-DD-.md` under Rule-12 frontmatter, linked from the sprint's decisions section.\n4. **Cross-repo and manifest.** If SHA pins shift, bump `citrate-federation/manifest.toml`, run\n `./scripts/pin-bump.sh ` to open the consumer PRs, merge on green, and let the nightly drift check\n verify (Rules 11 and 12).\n5. **Close.** When the exit criteria are met, move (do not copy) the file to\n `completed//.md`, set `status: archived`, write the close note recording any delta from\n the plan, and remove it from `CURRENT.md`.\n6. **Audit hand-off.** Completed sprints are immutable (Rule 3) and form the audit-evidence chain. Auditors\n read the `created:` dates and cross-reference the ADRs, the `audits/` directory, and the manifest.\n\n## Reference\n\nWhere each kind of work lives:\n\n| Work type | Home |\n|---|---|\n| Cross-repo workstream | `citrate-federation/agentile/sprints/active/.md` |\n| Single-repo workstream | `citrate-federation/repos//sprints/active/.md` |\n| Architectural decision | `…/adrs/ADR-YYYY-MM-DD-.md` (federation or repo) |\n| Audit report | `audits/YYYY-MM-DD-.md` in the affected repo |\n\nThe common anti-patterns, each of which breaks a rule: status kept in chat (Rule 4); two sprints for one\nworkstream (Rule 9); editing a closed sprint (Rule 3); a `TODO:` in production (Rule 1); a dependency with\nno manifest entry (Rules 11 and 12); a force-push taken without asking (Rule 10).\n\nConvenience skills (`/sprint kickoff|daily|close|status`, `/journal`, `/case-study`, `/audit-drive`)\nautomate the file-shuffling, but the methodology works with nothing more than git and an editor.\n\n## Access and canon\n\nPublic. This is process documentation and holds no secrets. Sprint contents for confidential work, such as\naudit, operations, and funding, live in private repositories and gate there; the workflow itself is public.\n\n## Source and verification\n\nThe canonical sources are `docs/AGENTILE_WORKFLOW.md` and the federation control plane under\n`citrate-federation/agentile/`, at SHA `cd729ed`. Status: Implemented.\n"},"/operators/rewards":{"slug":"/operators/rewards","title":"Rewards, reputation, and slashing","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/core/economics/, citrate-chain/contracts/src/NematocystSlashing.sol, citrate-node-agent","syncedSha":"9d5959e","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"The block reward and its four pools","anchor":"the-block-reward-and-its-four-pools"},{"depth":3,"text":"Institutional operators","anchor":"institutional-operators"},{"depth":3,"text":"Reputation","anchor":"reputation"},{"depth":3,"text":"Slashing","anchor":"slashing"},{"depth":3,"text":"What the node-agent does to keep you safe","anchor":"what-the-node-agent-does-to-keep-you-safe"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This is how an operator earns on the Citrate Network, how reputation tracks the work performed, and how\nmisbehavior is penalized. It is for operators who need to reason about what a node is paid, what raises or\nlowers its standing, and what the node-agent does on their behalf to keep an honest operator out of a slash.\nSALT settles the work; rewards, reputation, and slashing all point at contribution, not at holding.\n\n## What it is\n\nThe economics live in one crate, `core/economics/`. A node earns SALT for the work it performs: sealing\nblocks, hosting models, and completing compute jobs. The amounts, the reputation score, and any penalties\nare accounted on chain at chain id 40204, and the [node-agent](/compute/node-agent) keeps a local operator\ninside the safe envelope automatically. This page is consistent with [network economics](/chain/economics);\nwhere that page describes the supply and the reward schedule, this one describes what reaches the operator.\n\nThe reward for a sealed block is a base reward plus four bonus pools, each a percentage of that base reward.\nThe pools recognize four kinds of contribution, so a node that does more of the work the network values\nearns a larger share. The base reward halves every 2,100,000 blocks, so early seasons are more generous than\nlate ones. We describe the base reward as a configurable default rather than a fixed number, because\ngovernance can move it; what does not move is the halving cadence and the fixed one trillion supply.\n\n## How to use it\n\nYou read these values to project earnings and to understand why a score moved; you rarely set them.\n\n1. **Read your standing.** Call `citrate_getReputationScore` for an address's reputation and\n `citrate_getStakedBalance` for its stake over JSON-RPC. Both are documented in the\n [chain RPC reference](/chain/rpc).\n2. **Project a block reward.** Expect the base reward plus your earned share of the four pools, adjusted for\n where the chain sits in its halving schedule. See [network economics](/chain/economics) for the supply\n side.\n3. **Let the node-agent protect you.** The agent's bidder only accepts jobs it can finish on time and within\n a commitment cap, so it does not over-commit into a penalty. You read its alerts; you do not have to\n compute the envelope yourself.\n\n## Reference\n\n### The block reward and its four pools\n\nVerified in `core/economics/src/enhanced_rewards.rs` (`EnhancedRewardConfig`). The pool percentages are\ndefaults in source; treat them as defaults, not certified values, since governance can move them.\n\n| Element | Default | Source |\n|---|---|---|\n| Base block reward | configurable | `base_block_reward` |\n| Validator performance pool | 30% of base | `performance_bonus_pool` |\n| AI contribution pool | 25% of base | `ai_contribution_pool` |\n| Network health pool | 20% of base | `network_health_pool` |\n| Long-term staking pool | 25% of base | `staking_bonus_pool` |\n| Halving interval | 2,100,000 blocks | `calculate_total_reward_pool` |\n| Minimum validator stake | 32,000 SALT | `min_validator_stake` |\n\nA validator's share of the performance and staking pools is proportional to a score built from uptime,\nconsensus participation, validation efficiency, and a quality score, less a penalty for any prior slash. A\nnode below the minimum validator stake earns no share of those pools. The AI contribution pool is shared by\nscore across models deployed, inferences served, compute provided, and community standing.\n\n### Institutional operators\n\nSchool and institutional operators run under their own parameters, verified in\n`config/institutional_rewards.toml` and `core/economics/src/institutional.rs`.\n\n| Parameter | Value | Source |\n|---|---|---|\n| Block validation base | 150 SALT per month | `institutional_rewards.toml` |\n| Uptime bonus | 1.2x above the 90% uptime threshold | `institutional_rewards.toml` |\n| Model hosting | 25 SALT per model per 30-day epoch | `institutional_rewards.toml` |\n| Minimum uptime to earn | 0.90 | `institutional_rewards.toml` |\n\nSchools are not penalized for scheduled downtime, since their schedules are irregular by design.\n\n### Reputation\n\nReputation is tracked on chain and read with `citrate_getReputationScore`\n(`core/api/src/economics_rpc.rs`), expressed in basis points from 0 to 10,000. It rises with the work a node\nperforms and falls with missed liveness or a slash. The node-agent observes its own reputation each poll and\nraises a latched alert on a drop of more than five percent or on any decrease in stake, so an operator sees a\nproblem before it compounds (`citrate-node-agent`, supervision state).\n\n### Slashing\n\nSlashing categories live on chain in `NematocystSlashing.sol` (see [contracts security](/contracts/security)\nfor the category model). The institutional penalty schedule, verified in `core/economics/src/slashing.rs`,\npenalizes three offenses as a percentage of stake.\n\n| Offense | Penalty | Source |\n|---|---|---|\n| Equivocation (signing two blocks at one height) | 10% of stake | `equivocation_penalty_pct` |\n| Invalid state transition | 15% of stake | `invalid_state_penalty_pct` |\n| Transaction censorship | 5% of stake | `censorship_penalty_pct` |\n\nA first offense inside the grace window is recorded at zero penalty. A cooldown follows each slash, and an\noperator whose cumulative slash reaches 50% of stake is deactivated. Downtime is not a slashable offense for\ninstitutional operators.\n\n```rust\n// core/economics/src/slashing.rs, institutional defaults\nequivocation_penalty_pct: 10,\ninvalid_state_penalty_pct: 15,\ncensorship_penalty_pct: 5,\nfirst_offense_grace_epochs: 2,\ncooldown_epochs: 1,\nmax_cumulative_slash_pct: 50,\npenalize_downtime: false,\n```\n\n### What the node-agent does to keep you safe\n\nVerified in `citrate-node-agent`. The agent holds no keys: every write it wants to make, a heartbeat, a\nresult, a reward claim, is emitted as an unsigned signature request that a signing surface signs and\nbroadcasts, and the agent advances only on observed on-chain truth.\n\n- **Commitment cap and capacity check.** The bidder refuses a job at or above a commitment cap, and refuses\n new work once it is at roughly 80% of its concurrent-job capacity, so it does not accept work it cannot\n finish and slide into a penalty (`crates/bidder/src/lib.rs`).\n- **Deadline safety.** It only accepts a job when the time to the deadline is at least twice the estimated\n execution time.\n- **Liveness.** It sends a heartbeat on a 30-second cadence, and counts only broadcast heartbeats as\n liveness, never optimistically queued ones (`crates/heartbeat/src/lib.rs`).\n- **Reward claims.** It reads the claimable balance from the accounting contract and only emits a claim once\n the balance crosses a dust threshold, and never twice for the same claim in flight\n (`crates/earnings/src/lib.rs`).\n\n## Design rationale\n\nSplitting the block reward into four pools rather than paying a flat amount lets the network pay for the\nbehaviors it actually depends on, uptime, useful compute, a healthy peer set, and committed stake, instead\nof paying the same whether or not a node contributed beyond sealing the block. The trade is more parts to\nreason about; the benefit is that the reward points at the work. Slashing is the mirror: it penalizes the\nspecific harms, equivocation, invalid state, censorship, and leaves honest downtime alone for institutions\nthat cannot run around the clock. The node-agent's caps exist so that an operator who simply runs the daemon\nis kept inside the safe envelope without having to model it.\n\n## Failure modes\n\nThe honest invariant here is that rewards and penalties settle work, not promises.\n\n- **Over-commitment.** Left unprotected, an operator could accept more work than it can finish and be slashed\n for the misses. The agent's commitment cap, capacity check, and deadline safety factor are the guard, and\n they fail toward refusing work rather than accepting it.\n- **Silent reputation decay.** A drop in reputation or a slash to stake is easy to miss. The agent latches an\n alert on a greater-than-five-percent reputation drop or any stake decrease, so the alert cannot be polled\n past.\n- **Treating defaults as guarantees.** The base reward and the four pool percentages are governance-\n configurable. The load-bearing invariants are the halving cadence and the supply cap, not any single\n reward number; treat a published figure as a default.\n\n## Access and canon\n\nCommercial tier, operator implementation depth. SALT settles the work the network performs; reputation and\nslashing reward contribution and penalize misbehavior, and none of them is a speculative instrument. A node runs on hardware the operator controls. Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity. No keys appear here: rewards accrue to the operator's account, and the node-agent holds no keys,\nemitting unsigned requests for a separate signing surface to sign.\n\n## Source and verification\n\n- Source: `citrate-chain/core/economics/`, reward schedule in `src/enhanced_rewards.rs`, institutional\n parameters in `src/institutional.rs` and `config/institutional_rewards.toml`, slashing schedule in\n `src/slashing.rs`; slashing categories on chain in `contracts/src/NematocystSlashing.sol`; reputation and\n stake reads in `core/api/src/economics_rpc.rs` (`citrate_getReputationScore`, `citrate_getStakedBalance`).\n- Operator-side guards: `citrate-node-agent` (`crates/bidder`, `crates/heartbeat`, `crates/earnings`,\n supervision state), audited at `0e63363`.\n- Audited against SHA: `9d5959e` (chain), `0e63363` (node-agent).\n- Status: Implemented (testnet), internally tested, pre external audit. The reward pool percentages and base\n reward are configurable defaults in source, not certified values.\n"},"/operators/run-a-node":{"slug":"/operators/run-a-node","title":"Run a Citrate Node","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/README.md, citrate-chain/docs/OPERATIONS.md, citrate-chain/node-app/README.md, citrate-chain/docker-compose.yml, citrate-chain/config/","syncedSha":"9d5959e","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"Configuration","anchor":"configuration"},{"depth":3,"text":"Producer memory health","anchor":"producer-memory-health"},{"depth":3,"text":"Monitoring","anchor":"monitoring"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This is the operator's runbook for running a Citrate Node, the daemon that joins the Citrate Network and\nkeeps a copy of the ledger live. It is for anyone bringing up a node on their own hardware, from a single\nlocal instance to a node on testnet, chain id 40204.\n\n## What it is\n\nA Citrate Node is built from the `citrate-node` binary in the `citrate-chain` workspace. It wires storage,\nexecution, a mempool, peer management, and the RPC service into one process: a RocksDB-backed state store,\nan EVM-compatible executor, the GhostDAG consensus engine, and a JSON-RPC, WebSocket, and REST surface with\nPrometheus metrics. You run it on your own machine, on-premise by default, and it talks to other nodes over\nlibp2p. The node software is the same whether you run a local instance or join testnet, and it does not check operator identity.\n\nThe node binds its RPC surface to loopback by default, so the read and write surface is something you expose\ndeliberately behind your own reverse proxy, not by accident. The settlement and reward side of operating,\nhow a node earns SALT for the work it performs, is covered in [rewards](/operators/rewards); selling that\ncapacity into the marketplace is covered in [sell compute](/operators/sell-compute).\n\nNetwork parameters, verified against the chain README at SHA `9d5959e`:\n\n| Parameter | Value |\n|---|---|\n| Chain id | 40204 (testnet beta) |\n| Token | SALT, one trillion supply, 18 decimals |\n| Consensus | GhostDAG, k = 18, max-parents 10 |\n| Block production | a single block producer operated by Citrate today; ECVRF-P256-SHA256 (RFC 9381) proofs are checked; stake-gated eligibility is staged and off by default |\n| Finality | probabilistic confirmation, about 2 s per block; checkpoint finality is specified, not running (target design: a 100-member committee, 67 quorum, 50-block interval) |\n| Default JSON-RPC | `127.0.0.1:8545` |\n| Default WebSocket | `127.0.0.1:8546` |\n| Default REST | `127.0.0.1:3000` |\n| Default metrics | `0.0.0.0:9100` |\n\n## How to use it\n\nFollow these steps to bring up a node and confirm it is healthy.\n\n1. **Install the toolchain.** You need a Rust toolchain; the repository pins Rust 1.96.0 in\n `rust-toolchain.toml`, and the Docker build tracks that channel with `rust:stable`. Clone the\n `citrate-chain` workspace.\n\n2. **Build the binary.** From the workspace root:\n\n ```bash\n cargo build --release # builds the citrate-node binary\n ```\n\n3. **Choose how you run.** A local single node is the quickest path; testnet joins the public network at\n chain id 40204.\n\n ```bash\n # Local single node, block production on, fast blocks\n cargo run --bin citrate-node -- devnet\n\n # Join testnet\n cargo run --bin citrate-node -- --network testnet\n\n # Explicit config file and data directory\n cargo run --bin citrate-node -- --config /path/to/node.toml --data-dir /custom/path\n ```\n\n4. **Or run a small local network.** The helper script brings up three local nodes:\n\n ```bash\n ./scripts/launch_local_testnet.sh # preserve data\n ./scripts/launch_local_testnet.sh --clean # fresh start\n ./scripts/launch_local_testnet.sh --status # check status\n ```\n\n5. **Or run in Docker.** Compose profiles cover a single local node, testnet, and a five-node cluster.\n\n ```bash\n docker compose -f docker-compose.yml --profile devnet up --build\n docker compose -f docker-compose.yml --profile testnet up --build\n docker compose -f docker-compose.yml --profile cluster up --build # 5-node\n ```\n\n Inside a container the RPC binds to `0.0.0.0`, and the host exposure is set by the compose port mappings:\n the local profile maps host `8545`, `8546`, `30303`, and `9100`; the testnet profile maps host `18545`,\n `18546`, `30304`, and `19100`.\n\n6. **Confirm the node is answering.** Ask it for its current height:\n\n ```bash\n curl -s -X POST -H 'Content-Type: application/json' \\\n --data '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_blockNumber\",\"params\":[]}' \\\n http://127.0.0.1:8545\n ```\n\n7. **Watch producer memory.** A producing node should sit near 1 GB of resident memory. Sample it and read\n the thresholds below before you leave it unattended.\n\n ```bash\n ps -o rss= -p \"$(pgrep -f citrate-node | head -1)\" # resident memory, KiB\n ```\n\n## Reference\n\n### Configuration\n\nThe node reads its configuration from environment variables, verified in `node-app/README.md` and the\ncompose files. The defaults are deliberately conservative: the RPC binds to loopback, not all interfaces.\n\n| Variable | Default | Purpose |\n|---|---|---|\n| `CITRATE_DATA_DIR` | `/data` (Docker) | RocksDB storage directory |\n| `CITRATE_RPC_ADDR` | `127.0.0.1:8545` | JSON-RPC listen address |\n| `CITRATE_METRICS_ADDR` | `0.0.0.0:9100` | Prometheus metrics endpoint |\n| `CITRATE_METRICS` | `1` | metrics on |\n| `RUST_LOG` | `info,citrate=info` | log filter |\n| `CITRATE_OPERATOR_TOKEN` | unset | required for admin RPC on a non-loopback bind (fails closed) |\n\nNetwork bootstrap peers are listed by network in `config/bootstrap-nodes.json`. Testnet ships four bootstrap\nnodes across regions; the public RPC hostname is `https://rpc.citrate.ai`.\n\n### Producer memory health\n\nThe runbook in `docs/OPERATIONS.md` is the canon for producer memory. A healthy producing node sits near\n1.05 GB resident memory. The thresholds and the circuit-breaker below come from that runbook.\n\n| Resident memory | Meaning | Action |\n|---|---|---|\n| up to ~1.2 GB | healthy steady state | none |\n| 1.2 to 2 GB | elevated, watch | sample every 15 minutes, correlate with indexer or beacon load |\n| over 2 GB | leak-class behavior | trip the circuit-breaker below before the OOM killer acts |\n| over 3 GB | alert fires (`ProducerMemoryHigh`, critical) | trip the breaker immediately |\n\nThe memory-heavy startup path only runs when block production is on. The circuit-breaker is to turn it off:\nset `[mining] enabled = false` in the node config and restart. The node then serves RPC reads near 1 GB\nindefinitely, which is the safe degraded mode; only writes stop, the public read surface stays up.\n\n```bash\n# 1. Confirm you are in leak territory (resident memory, KiB):\nps -o rss= -p \"$(pgrep -f citrate-node | head -1)\"\n\n# 2. Trip the breaker: turn block production off, then restart.\n# edit the [mining] section of your node config: enabled = false\nsystemctl restart citrate-node\n\n# 3. Verify degraded but healthy: memory near 1 GB, RPC still answering.\ncurl -s -X POST -H 'Content-Type: application/json' \\\n --data '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_blockNumber\",\"params\":[]}' \\\n http://127.0.0.1:8545\n```\n\nRe-enable production only after the cause is identified, and watch memory for the first five minutes after\nrestart; the original leak fired during startup, within thirty seconds. Capture `ps` and `smaps` evidence\nbefore any restart if you can, and keep the previous release binary so you can roll back.\n\n### Monitoring\n\nThree permanent guards protect the producer, verified in `docs/OPERATIONS.md`:\n\n| Guard | Where | Trips when |\n|---|---|---|\n| `producer_steady_state` test | `core/sequencer/tests/producer_steady_state.rs` | a change re-materializes cumulative blue ancestry on the eager-load path (CI) |\n| `ProducerMemoryHigh` alert | `node/monitoring/alerts/citrate-alerts.yml` | resident memory over 3 GB sustained two minutes |\n| memory gauge sampler | `node/src/main.rs` (15s cadence) | feeds the alert |\n\nThe Docker monitoring profile brings up Prometheus and Grafana against the node's metrics endpoint.\n\n## Design rationale\n\nThe node binds to loopback by default because the safe state is the closed one: exposing the RPC surface is\na step you take deliberately, behind your own TLS terminator and access controls, not the default a fresh\ninstall hands you. Block production is gated behind a single flag so that the one memory-heavy path has a\nclean off switch, and turning it off degrades the node to a read-only server rather than taking it down. The\ntrade is that an operator who wants a public, writable endpoint has to do the reverse-proxy and token work\nthemselves; the benefit is that an unconfigured node cannot leak its admin surface onto the network.\n\n## Failure modes\n\nThis is where running a node is security relevant, so the defaults fail closed.\n\n- **No transport security on the raw RPC.** The RPC ships with no TLS and no authentication, and on bare\n metal it binds to loopback. If you bind to `0.0.0.0`, you must set `CITRATE_OPERATOR_TOKEN` and front the\n endpoint with a TLS-terminating reverse proxy and an explicit CORS allow-list. The admin methods refuse to\n serve on a non-loopback bind without the operator token.\n- **Memory leak under production load.** If resident memory climbs past 2 GB, trip the production circuit-\n breaker before the OOM killer acts; the node keeps serving reads.\n- **Reused development keys.** The local coinbase account is a well-known public test key. Never use it on\n testnet or in production, and never reuse the example development keys.\n- **Development-only switches left on.** The relaxed switches that disable signature checks or allow\n plaintext peer traffic must stay off outside local development.\n\n## Access and canon\n\nPublic. Running a node is public-good operator material, and the front door of the network. The node runs on hardware you control. Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity. No secrets, operator tokens, or private node\naddresses appear here. The public RPC hostname `https://rpc.citrate.ai` is the only network endpoint named.\nFor genesis and the network layout, see [the network](/chain/network) and [genesis](/chain/genesis).\n\n## Source and verification\n\n- Source: `citrate-chain`. Network parameters in `README.md`; the operator runbook and producer-memory\n thresholds in `docs/OPERATIONS.md`; environment variables in `node-app/README.md`; run profiles and port\n mappings in `docker-compose.yml`; bootstrap peers and institutional parameters in `config/`.\n- Audited against SHA: `9d5959e`.\n- Status: Implemented (testnet). The node, the run modes, the producer-memory guards, and the monitoring\n profile exist and run; the chain is live on testnet at chain id 40204 and has not had an external audit.\n Note: there is no `docs/PRIVATE_NETWORK.md` at this SHA; the canonical runbook is `docs/OPERATIONS.md`.\n"},"/operators/sell-compute":{"slug":"/operators/sell-compute","title":"Sell compute","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-node-agent (README.md, crates/)","syncedSha":"0e63363","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"1. Write your participation policy","anchor":"1-write-your-participation-policy"},{"depth":3,"text":"2. Self-check offline","anchor":"2-self-check-offline"},{"depth":3,"text":"3. Start the daemon","anchor":"3-start-the-daemon"},{"depth":3,"text":"4. Wire up the signer","anchor":"4-wire-up-the-signer"},{"depth":3,"text":"5. Operate","anchor":"5-operate"},{"depth":3,"text":"6. Understand the bidding so your bids win and stay profitable","anchor":"6-understand-the-bidding-so-your-bids-win-and-stay-profitable"},{"depth":3,"text":"7. Job lifecycle, experimental","anchor":"7-job-lifecycle-experimental"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This is the standing procedure for selling compute on Citrate Market with Citrate Node, end to end, on\nchain id 40204. It is written for operators who run their own hardware and want idle\nGPU hours to earn while the machine is otherwise unsupervised.\n\n## What it is\n\nYou sell compute by running Citrate Node as a daemon on your own host. The daemon watches Citrate Market,\ndecides which jobs to bid on by your policy, proves it is alive with a heartbeat, and drives a won job to\ncompletion. It holds no keys: every on-chain write is emitted as an unsigned `SignatureRequest` that your\nseparate signing surface, the operator's Citrate Keyring or a signing relay, signs and broadcasts. Plan\nfor two roles, the agent that decides and the signer that holds keys. The work you perform settles in SALT.\n\nThe field reference for every flag, route, and gate is [Citrate Node](/compute/node-agent), audited against\nthe same SHA. Bringing the node online at all is covered in [run a node](/operators/run-a-node), and the\nidentity step is covered under [verified identity](/aa/identity).\n\n## How to use it\n\n### 1. Write your participation policy\n\nCreate `compute.json`:\n\n```json\n{ \"enabled\": true, \"allocation_percent\": 50, \"schedule\": \"always\" }\n```\n\n`schedule` is `always`, `nights` (22:00 to 05:59 local), or `weekends`. Start with `enabled: false` to\ndry-run the wiring, then flip it to `true` (`crates/config`).\n\n### 2. Self-check offline\n\n```bash\nnode-agent path/to/compute.json\n```\n\nThis confirms your policy parses and prints the heartbeat calldata. No RPC is contacted.\n\n### 3. Start the daemon\n\n```bash\nexport CITRATE_RPC_URL=https:// # https or loopback http only\nexport CITRATE_PROVIDER_ADDRESS=0x\nexport CITRATE_NODE_AGENT_DAEMON=1\nnode-agent path/to/compute.json\n```\n\nThe daemon brings up the loopback supervision API on `127.0.0.1:19600`, reads chain state each tick, runs\nthe bidder, and beats every 30 seconds.\n\n### 4. Wire up the signer\n\nThe agent emits unsigned requests; your signer pulls, signs, broadcasts, then acknowledges:\n\n```bash\nTOKEN=$(cat ~/.citrate/node-agent/supervision.token)\ncurl -H \"Authorization: Bearer $TOKEN\" http://127.0.0.1:19600/signature-requests\n# sign and broadcast externally, then:\ncurl -X POST -H \"Authorization: Bearer $TOKEN\" \\\n -d '{\"tx_hash\":\"0x...\"}' \\\n http://127.0.0.1:19600/signature-requests//observed\n```\n\n### 5. Operate\n\n```bash\ncurl http://127.0.0.1:19600/health # liveness, no auth\ncurl -H \"Authorization: Bearer $TOKEN\" http://127.0.0.1:19600/status # idle|bidding|executing|paused\ncurl -X POST -H \"Authorization: Bearer $TOKEN\" http://127.0.0.1:19600/pause # stop new bids\ncurl -X POST -H \"Authorization: Bearer $TOKEN\" http://127.0.0.1:19600/resume\n```\n\n`pause` stops new bids but lets in-flight jobs finish; use it for maintenance.\n\n### 6. Understand the bidding so your bids win and stay profitable\n\nThe bidder skips a job unless it is enabled, inside the schedule window, under the 10-SALT Commitment cap,\na Commitment-tier job, under 80% capacity, deadline-feasible, and priced off a fresh oracle. It then bids\n`cost x 1.15`, capped at `0.9 x maxPrice`, and skips if that would fall below cost. Scoring on-chain is\n40% price, 30% reputation, 20% load, and 10% verification tier, so a low bid alone does not win; reputation\nearned by completing jobs matters. Tune `allocation_percent`, `schedule`, and `CITRATE_NODE_PFLOPS_1E18`,\nyour throughput, accordingly. See [rewards and reputation](/operators/rewards).\n\n### 7. Job lifecycle, experimental\n\nWhen the execution path is enabled (SELL-S2, set `CITRATE_IPFS_GATEWAY`, `CITRATE_LLAMA_URL`, and\n`CITRATE_JOB_INPUT_DIR`), a won job walks: `Assigned` → `startExecution` → run inference (weights fetched\nby model CID, digest recomputed locally, served) → `submitCommitment` → `submitResult` → `completeJob`.\n`submitResult` is refused past the execution deadline, an anti-slash guard. Earnings auto-claim once\nclaimable reaches `CITRATE_CLAIM_THRESHOLD_WEI`. Treat this path as experimental until SELL-S2 lands fully.\n\n## Reference\n\n| Surface | Where |\n|---|---|\n| Every flag, route, env var, and gate | [Citrate Node](/compute/node-agent) |\n| The on-chain marketplace functions | [compute contracts](/contracts/compute) |\n| Reputation, scoring, and slashing-protection | [rewards and reputation](/operators/rewards) |\n| Bringing the node online | [run a node](/operators/run-a-node) |\n| Identity verification through VERI | [verified identity](/aa/identity) |\n\n## Design rationale\n\nThe agent never holds a key because an unattended process reacting to live prices on a GPU host is the\nlast place a signing key belongs. Unsigned requests plus an external signer mean a daemon compromise cannot\nmove stake or funds. The conservative bidder, the 80% capacity cap, the night and weekend schedules, and\nthe twice-execution-time margins all exist so an operator can sell idle hours without watching the machine\nand without taking on work it cannot finish before a slashable deadline.\n\n## Failure modes\n\n- The daemon exits at startup if `CITRATE_NODE_AGENT_ADDR` is not loopback; the supervision surface is\n localhost-only by design.\n- No bids usually means a bidder gate fired: check `enabled`, the schedule window, capacity under 80%,\n deadline feasibility, oracle freshness, and the 10-SALT cap. `/status` and `/health` report the live\n state.\n- An RPC refused at startup is the outbound TLS gate; plaintext HTTP to a non-loopback host is rejected.\n Use https. Never set `CITRATE_NODE_AGENT_ALLOW_INSECURE_OUTBOUND` on a production node; it is a dev-only\n LAN escape hatch that exposes you to a network attacker rewriting chain state, oracle prices, and job\n state.\n- A late result is refused rather than submitted, so the lifecycle aborts cleanly instead of being slashed.\n\n## Access and canon\n\nTier commercial.kyc: operator-depth marketplace know-how, gated on identity verification through Citrate's in-house verification (VERI), not\non a seat. Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity. Compute is sold from your own hardware,\non-premise by default, and SALT settles the work performed; it is the unit you count in, not a product to\nhold. No secrets here: the supervision token is generated locally at mode 0600 and never transcribed, bind\nthe supervision API to loopback only, and key custody stays in your external signer. The agent never holds\nkeys.\n\n## Source and verification\n\nVerified against `citrate-node-agent` at `0e63363` (`README.md` and `crates/`). The policy fields against\n`crates/config`, the bidder gates and cost-plus pricing against `crates/bidder`, the lifecycle and unsigned\nsigning seam against `crates/lifecycle`, the supervision routes against `crates/supervision`, and the\nmarketplace scoring and fee split against `ComputeMarketplace.sol`. Status: SELL-S1 (settings, bidder,\nheartbeat, supervision, live reads) Implemented, pre-audit; SELL-S2 (execution and earnings) Specified and\nexperimental.\n"},"/operators/tutorials/become-a-seller":{"slug":"/operators/tutorials/become-a-seller","title":"Become a seller","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-node-agent (README.md, crates/) + ComputeMarketplace.sol","syncedSha":"0e63363","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Step 1, complete membership verification","anchor":"step-1-complete-membership-verification"},{"depth":3,"text":"Step 2, build the agent","anchor":"step-2-build-the-agent"},{"depth":3,"text":"Step 3, write a policy","anchor":"step-3-write-a-policy"},{"depth":3,"text":"Step 4, self-check offline","anchor":"step-4-self-check-offline"},{"depth":3,"text":"Step 5, register as a provider","anchor":"step-5-register-as-a-provider"},{"depth":3,"text":"Step 6, run live","anchor":"step-6-run-live"},{"depth":3,"text":"Step 7, drive it","anchor":"step-7-drive-it"},{"depth":3,"text":"Step 8, win a job","anchor":"step-8-win-a-job"},{"depth":3,"text":"Step 9, execute and settle","anchor":"step-9-execute-and-settle"},{"depth":3,"text":"Step 10, verify you are selling","anchor":"step-10-verify-you-are-selling"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"A walkthrough from a fresh checkout to a live seller on Citrate Market, chain id 40204. You will complete membership verification, build and configure Citrate Node, register as a provider on the marketplace contract,\nlet the agent bid and win a job, and walk that job to payment, settled in SALT. It is written for operators\nwho run their own hardware. Allow roughly twenty minutes for the local steps; verification and on-chain\nconfirmations take their own time.\n\n## What it is\n\nCitrate Node decides; you sign. The daemon reads your policy, watches the market, and produces unsigned\nwrites; your signing surface, the operator's Citrate Keyring or a relay, signs and broadcasts them. The\nmarketplace itself, `ComputeMarketplace` on Citrate Network, is the contract that holds escrow, runs the\nbidding, and releases payment. This tutorial touches both: the daemon for decisions, the contract for the\nmoney. Bringing the node online first is covered in [run a node](/operators/run-a-node), and the full\ncontract surface in [compute contracts](/contracts/compute).\n\n## How to use it\n\n### Step 1, complete membership verification\n\nMembership includes identity verification through VERI, Citrate's in-house verification. Node and consensus code do not check it.\nComplete verification through [verified identity](/aa/identity). Citrate keeps the verification result, not the\npersonal data behind it. You cannot register as a provider without it.\n\n### Step 2, build the agent\n\n```bash\n# from the citrate-node-agent workspace root\ncargo build --release\n# the binary is target/release/node-agent\n```\n\n### Step 3, write a policy\n\nCreate `compute.json`:\n\n```json\n{\n \"enabled\": true,\n \"allocation_percent\": 25,\n \"schedule\": \"nights\"\n}\n```\n\nThis allots 25% of the GPU, at night only (22:00 to 05:59 local). Fields are validated against\n`crates/config`; `allocation_percent` must be 0 to 100.\n\n### Step 4, self-check offline\n\n```bash\nnode-agent compute.json\n```\n\nThe agent prints your enabled, allocation, and schedule values, the current clock, the heartbeat calldata,\nand a self-check. No RPC is contacted. If you set `enabled: false`, it reports disabled, a safe way to\nconfirm wiring.\n\n### Step 5, register as a provider\n\nRegistration is an on-chain write to `ComputeMarketplace.registerProvider(bytes32[] supportedModels)`. It\nis payable and requires a stake: `MIN_PROVIDER_STAKE` is 1000 SALT (`ComputeMarketplace.sol`). The stake is\nyour collateral; the contract slashes it if you take a job and miss the deadline. Pass the model hashes you\nwill serve, and your profile starts at full reputation (10000 basis points) with a default of 10 concurrent\njobs. You sign and broadcast this from your Citrate Keyring, not from the daemon. Add more stake later with\n`addStake()`.\n\n### Step 6, run live\n\n```bash\nexport CITRATE_RPC_URL=https:// # https or loopback http only\nexport CITRATE_PROVIDER_ADDRESS=0x\nexport CITRATE_NODE_AGENT_DAEMON=1\nnode-agent compute.json\n```\n\nThe daemon starts the supervision API on `127.0.0.1:19600` and begins reading chain state, bidding, and\nbeating every 30 seconds.\n\n### Step 7, drive it\n\nIn a second terminal:\n\n```bash\nTOKEN=$(cat ~/.citrate/node-agent/supervision.token)\n\ncurl http://127.0.0.1:19600/health # no auth\ncurl -H \"Authorization: Bearer $TOKEN\" http://127.0.0.1:19600/status # idle|bidding|executing|paused\n\n# pull unsigned writes for your signer to sign and broadcast\ncurl -H \"Authorization: Bearer $TOKEN\" http://127.0.0.1:19600/signature-requests\n\n# after signing and broadcasting externally, acknowledge:\ncurl -X POST -H \"Authorization: Bearer $TOKEN\" \\\n -d '{\"tx_hash\":\"0x...\"}' \\\n http://127.0.0.1:19600/signature-requests//observed\n\n# maintenance: stop new bids (in-flight jobs finish), then resume\ncurl -X POST -H \"Authorization: Bearer $TOKEN\" http://127.0.0.1:19600/pause\ncurl -X POST -H \"Authorization: Bearer $TOKEN\" http://127.0.0.1:19600/resume\n```\n\n### Step 8, win a job\n\nWhen a requester posts a job with `postJob`, the contract locks their `maxPrice` in escrow and opens the\nbidding window. The agent's bidder evaluates it, and if every gate passes it queues a `bidOnJob(jobId,\nprice, estimatedLatency)` write for your signer. After the bid deadline, anyone can call `assignBestBid`,\nwhich scores the bids (40% price, 30% reputation, 20% load, 10% verification tier) and assigns the winner.\nA low price alone does not win; the reputation you earn by completing jobs is what moves you up.\n\n### Step 9, execute and settle\n\nOnce assigned, the lifecycle planner walks the job through the contract, one signed write at a time:\n\n```text\nAssigned -- startExecution(jobId) -------------------------> Executing\nExecuting -- submitCommitment(jobId, SHA3(in||out||nonce)) -> commitment recorded\nExecuting -- submitResult(jobId, outputHash, proof) --------> Verifying (verifies inline)\nVerifying -- completeJob(jobId) ----------------------------> Completed (releases payment)\n```\n\n`submitResult` is refused past the execution deadline, so a late result aborts cleanly rather than being\nslashed. On `completeJob` the escrow is released: 95% to you, 2.5% burned, 2.5% to the treasury\n(`BME_BURN_DIVISOR` and `TREASURY_DIVISOR` are both 40 in `ComputeMarketplace.sol`). The execution path\nruns only when `CITRATE_IPFS_GATEWAY`, `CITRATE_LLAMA_URL`, and `CITRATE_JOB_INPUT_DIR` are set; this is\nSELL-S2, experimental. Earnings sweep with `claimRewards()` once claimable reaches your threshold.\n\n### Step 10, verify you are selling\n\n- `/status` shows `bidding` or `executing` when there is matching demand.\n- `/health` shows a recent heartbeat age.\n- Your provider address shows broadcast transactions on the network explorer, and `getProvider` reflects\n your stake, active jobs, and reputation.\n\n## Reference\n\nThe contract functions you touch, audited against `citrate-chain/contracts/src/ComputeMarketplace.sol` at\n`e6f11ef`:\n\n| Function | What it does |\n|---|---|\n| `registerProvider(bytes32[])` | Register as a provider; payable, requires `MIN_PROVIDER_STAKE` (1000 SALT). |\n| `addStake()` | Add collateral to a registered provider. |\n| `bidOnJob(uint256,uint256,uint256)` | Place a bid at or below the job's `maxPrice`. |\n| `assignBestBid(uint256)` | Score the bids and assign the winner; callable by anyone after the bid deadline. |\n| `startExecution(uint256)` | Assigned provider confirms work has begun. |\n| `submitCommitment(uint256,bytes32)` | Record `SHA3(input || output || nonce)` before the result. |\n| `submitResult(uint256,bytes,bytes)` | Submit the output hash and tier proof; refused past the deadline. |\n| `completeJob(uint256)` | Release escrow: 95% provider, 2.5% burn, 2.5% treasury. |\n| `getProvider(address)` | Read a provider profile (stake, active jobs, reputation). |\n| `getJob(uint256)` | Read a job's state and parameters. |\n\nSlashing on timeout is 5% of stake (`TIMEOUT_SLASH_BPS = 500`). A disputed result requires a 10-SALT bond\n(`DISPUTE_BOND`) that is burned if the dispute fails. See [compute contracts](/contracts/compute) and\n[rewards and reputation](/operators/rewards) for the rest.\n\n## Design rationale\n\nThe stake-and-slash design is what lets a requester trust an unknown provider: your 1000 SALT is collateral\nthat you will finish what you bid on, and the scoring formula rewards a record of completed jobs over a\nsingle cheap bid. The agent never signs, so the daemon reacting to live prices on your GPU host cannot move\nthat stake; only your Citrate Keyring can. The conservative bidder keeps you on the safe side of the\ndeadline that the slash protects.\n\n## Failure modes\n\n- The daemon exits immediately if `CITRATE_NODE_AGENT_ADDR` is not loopback; the supervision surface is\n localhost-only by design.\n- No bids usually means a bidder gate fired: check `enabled`, the schedule window, capacity under 80%,\n deadline feasibility, oracle freshness, and the 10-SALT cap.\n- An RPC refused at startup is the outbound TLS gate rejecting plaintext HTTP to a non-loopback host. Use\n https. Do not set `CITRATE_NODE_AGENT_ALLOW_INSECURE_OUTBOUND` on a production node; it is a dev-only LAN\n escape hatch that exposes you to a network attacker rewriting chain state.\n- `registerProvider` reverts below 1000 SALT, with no supported models, or if you are already registered.\n- A missed execution deadline slashes 5% of your stake; the agent aborts before submitting late to avoid\n exactly this.\n\n## Access and canon\n\nTier commercial.kyc. No secrets in this tutorial: the supervision token is generated locally at mode 0600\nand read from its file, keys live only in your external signer, and the dev-only insecure-outbound flag is\ncalled out as forbidden in production. Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity. Compute is sold\nfrom your own hardware, on-premise by default, and SALT settles the work performed; it is the unit you\ncount in, not a product to hold. The agent holds no keys.\n\n## Source and verification\n\nVerified against `citrate-node-agent` at `0e63363` (the agent, `crates/`) and\n`citrate-chain/contracts/src/ComputeMarketplace.sol` at `e6f11ef` (the marketplace, which lives in the\n`citrate-chain` repository, not the node-agent). The build, policy, self-check,\ndaemon, and supervision steps against `crates/config`, `crates/node-agent`, and `crates/supervision`; the\nbid decision against `crates/bidder`; the lifecycle writes and the unsigned signing seam against\n`crates/lifecycle`; registration, stake, scoring, the fee split, the timeout slash, and the dispute bond\nagainst `ComputeMarketplace.sol`. Status: SELL-S1 (register, bid, heartbeat, supervision, live reads)\nImplemented, pre-audit; SELL-S2 (execution and earnings) Specified and experimental.\n"},"/research/atis":{"slug":"/research/atis","title":"ATIS, Analog Token Importance Scoring","tier":"public","orgId":null,"sourceKind":"linked","source":"citrate-docs/gradient_papers_v3/Gradient_Papers_No5_ATIS_v3.md","syncedSha":"cd729ed","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"ATIS is a research direction, not a feature. It asks whether the decision of which tokens a transformer\nshould attend to could be made in analog hardware, before the digital arithmetic begins, to push the\nenergy cost of inference below the digital floor. This page is for researchers; it summarizes Gradient\nPaper V and is clear about what does not exist: there is no prototype, no simulation, and no code in the\nCitrate Network for any of it.\n\n## What it is\n\nPruning tokens that contribute little to the next layer is a well-studied way to speed up attention,\nusually two to four times. ATIS, Analog Token Importance Scoring, proposes computing the importance score\nitself in a Field-Programmable Analog Array placed before the digital query and key projection, so the\ncheap analog stage decides which tokens are worth the expensive digital stage. The appeal is energy:\nattention grows with the square of sequence length, and on long contexts that quadratic term dominates\nboth memory bandwidth and power.\n\nThis is theoretical work. The paper is honest that the naive version does not pay off, and its value is\nthe framing, filter before the expensive operation, together with a clear account of why the obvious\ndesign fails. We carry it here in the same spirit: as a direction a researcher might pursue on the\nCitrate substrate, not as anything the network does.\n\n## How to use it\n\nThere is nothing to run. Read the paper if you work on inference hardware or efficient attention, and\ntreat the page as orientation. Where ATIS touches the network is only conceptual: the network pays for\ninference by work performed, so any genuine energy saving would flow to the operator who earned it, which\nis the economic reason a researcher might build on the substrate at all. The relevant built surfaces are\nthe hardware-agnostic inference router and the attestation gates, described under\n[verifiable inference](/research/verifiable-inference), neither of which depends on ATIS.\n\n## Reference\n\nA summary of the paper's structure, not a copy. The honest core is the second table: the naive design\nspends almost all its energy converting digital signals to analog.\n\n| Section | Claim |\n|---|---|\n| The problem | Attention is quadratic in sequence length; on 8K-token contexts the matrix dominates energy and bandwidth. |\n| The proposal | Insert an analog filter between embedding and digital attention: convert to analog, approximate dot products against a learned query prototype, compare to a threshold, and run the digital pipeline only on the surviving tokens. |\n| The bottleneck | The digital-to-analog conversion, not the analog compute, dominates the budget, so the naive design costs more than the attention it was meant to avoid. |\n| The honest conclusion | ATIS does not pay off without an architecture that removes the conversion step. |\n\nThe paper sketches three conversion-free directions, all multi-year hardware research: charge-domain\ncompute inside memory sense amplifiers, mixed-signal stores that keep embeddings analog from training\ntime, and photonic dot products driven by a laser modulator. It also notes a purely digital fallback: a\nsmall importance-predicting network run before attention captures most of the framing's value, because\npruning the token set shrinks the attention matrix quadratically across layers, without any analog\nhardware at all.\n\n## Design rationale\n\nThe paper earns its place in the series by being candid rather than promising. An earlier revision named\nthe conversion bottleneck; this one sharpens it into the conclusion that the naive approach should not be\nbuilt. That is the right altitude for a research page: state the idea plainly, state why it is hard, and\ndo not let the framing's appeal stand in for a result. The connection to Citrate is economic, not\ntechnical. Because the network settles work performed, an operator who found a real efficiency would keep\nthe gain, which is the incentive that makes hardware research on the substrate rational. The network does\nnot require ATIS, and ATIS does not require the network.\n\n## Access and canon\n\nAcademic tier. Nothing here is sensitive; the paper cites public literature and commercial datasheets,\nand there is no Citrate Network surface, contract, or endpoint involved. This is the soil intelligence\ncould one day run on more cheaply, described as research and not as a claim.\n\n## Source and verification\n\n- Paper, linked, not copied: `citrate-docs/gradient_papers_v3/Gradient_Papers_No5_ATIS_v3.md`.\n- Code anchor: none. This is hardware research with no implementation in `citrate-chain`, and the series\n index correctly lists the paper with no code anchor.\n- Status: Theoretical. There is no Field-Programmable Analog Array prototype, no SPICE simulation, and no\n measured energy figure. The digital importance-predicting fallback is a researcher's option, not a\n shipped feature, and the custom-hardware attestation extension is specified pending the attestation\n surface. This page frames a direction honestly; it does not describe a feature of the network.\n- Audited against SHA: `cd729ed` (citrate-docs).\n- Related: [the Gradient Papers](/research/gradient-papers), [verifiable inference](/research/verifiable-inference).\n"},"/research/bdd":{"slug":"/research/bdd","title":"The Gherkin acceptance library","tier":"public","orgId":null,"sourceKind":"linked","source":"citrate-chain/specs/gherkin/ + per-repo .agentile features","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This is the behavioral half of how Citrate pins down correctness: a library of Gherkin acceptance\nspecifications that say, in plain Given/When/Then steps, what each piece of the system must do before any\ncode is written. It is for researchers and reviewers who want to read the protocol as observable behavior.\nWe link the features here; we do not copy them.\n\n## What it is\n\nA Gherkin feature file states a behavior as a set of scenarios, each written in the same shape: a `Feature`\nthat names the surface, a `Background` that fixes the starting conditions, and one or more `Scenario` blocks\nof `Given` a situation, `When` an action, `Then` an expected outcome. At Citrate the feature file is the\nspecification, not a description written after the fact. A feature file that disagrees with the code is a\ncontinuous-integration failure, because the steps are run as integration tests on every commit.\n\nThe reason for working this way is that coding agents fail in recognizable patterns: they drift from the\nagreed scope, they forget earlier architecture, they let regressions through quietly, they leave stubs in\nplace, and they make assumptions about the platform. Stating a work package as concrete observable behavior\nblocks each of these. A scenario that begins `Given an empty pool` will fail any hardcoded stub return, and\na `Background` that pins the chain id and the RPC endpoint stops platform assumptions from drifting. The\nmethodology is argued in Gradient Paper No. 4, `Behavioral Issues`.\n\n## How to use it\n\nThe cycle ties each feature to code and to a test, in order.\n\n1. A person writes the Gherkin. The feature file is the contract between the operator and the agent; each\n scenario is a behavior to implement.\n2. An agent writes the step definitions so the scenarios become failing tests, for example under\n `core/execution/tests/` or `contracts/test/`. They fail first, on purpose.\n3. The agent writes the implementation until every step passes.\n4. The code is refactored while the tests stay green.\n5. The feature lands in the repository beside the implementation, and continuous integration runs the steps\n as integration tests from then on.\n\nTo read a feature, open the `.feature` file under the relevant repo's `specs/gherkin/` or `.agentile`\nfeatures directory and read its scenarios top to bottom; each one is a behavior the running system commits\nto. To check that the code still honors a feature, run that repo's test suite, which executes the step\ndefinitions.\n\n## Reference\n\nThe largest single library is in `citrate-chain/specs/gherkin/`, with 31 feature files. A representative\nscenario, abridged from `mentor_matching.feature`:\n\n```gherkin\nFeature: Mentor-mentee matching + adapter verification flow\n Background:\n Given the Citrate testnet (chain id 40204) is live\n And the inference-proof-verify precompile is dispatched at 0x0108\n And the trust floor is set to accuracy >= 0.30 (Q16: 19661)\n\n Scenario: Standard mentor match with adequate accuracy gap\n Given my profile shows a weak score on dimension AdapterCreation\n When LearningCycleManager.advanceCycle() is called\n Then the protocol selects the top candidates by AdapterCreation score\n And filters by blue_score above the floor\n And emits MentorAssigned(me, mentor, AdapterCreation)\n```\n\nThe chain library spans these areas, named from the real feature files:\n\n| Area | Example features |\n|---|---|\n| Credits and accounts | `token_transfer.feature`, `wallet_integration.feature` |\n| Contracts and deploy | `contract_deploy.feature`, `model_deploy.feature` |\n| Learning and mentorship | `learning_daemon.feature`, `mentor_matching.feature`, `belnap_aggregation.feature`, `dataparallel_training.feature` |\n| Inference and routing | `model_inference.feature`, `inference_pool.feature`, `routing_model.feature`, `pipeline_parallel_inference.feature` |\n| Gateway and billing | `gateway_inference.feature`, `gateway_batch.feature`, `gateway_api_key.feature`, `gateway_usage.feature`, `credit_billing.feature`, `x402_payment.feature` |\n| Compute settlement | `computepool_settlement.feature` |\n| Desktop flows | `assistant_pane_flow.feature`, `drawer_lifecycle.feature`, `modal_lifecycle.feature`, `toast_lifecycle.feature`, `scope_switch_flow.feature`, `batch_operation.feature` |\n| Governance and safety | `role_escalation_timer.feature`, `school_safety.feature`, `listing_visibility.feature` |\n| Research hypotheses | `hypothesis_h1.feature`, `hypothesis_h2.feature`, `hypothesis_h3.feature` |\n\nThe convention reaches across the federation, each repo keeping its behavior contracts next to its code:\n\n- `citrate-chain/specs/gherkin/`: 31 features; step definitions in `core/execution/tests/` and\n `contracts/test/`.\n- `citrate-agentile-archive/bdd/agent/`: the agent-harness contracts, grouped as `approval/`, `audit/`,\n `break_glass/`, `capsule_install/`, and `data_class/` (for example\n `low_risk_auto_approve.feature`, `single_security_officer.feature`, `no_read_up.feature`).\n- `citrate-explorer/.agentile/specs/features/`: 17 features covering the explorer surfaces (for example\n `live-dag.feature`, `search.feature`, `authentication.feature`, `data-privacy-storage.feature`).\n- `citrate-federation/.agentile/gtm-spine/features/`: 25 features for identity, console, sell, and\n inference sprints (for example `IDP-S3-wallet-linking.feature`, `SELL-S1-node-agent-mvp.feature`).\n- `citrate-agent-runtime/capsules/*/gherkin/`: one feature per capsule (for example `hello`,\n `anchor-session`, `revoke-role`, `verify-provenance-chain`).\n- `nist-agent/features/`: features grouped under `core/`, `chain/`, `capsule/`, `distribution/`,\n `overlays/`, and `surfaces/`.\n\nA feature describes behavior, run as an executable acceptance test, while the [TLA+ corpus](/research/tla)\nproves state-machine invariants with a model checker. Many features have a TLA+ counterpart for the same\nsurface; `belnap_aggregation.feature` lines up with `BelnapLattice.tla` and `ParaconsistentAggregation.tla`,\nand `computepool_settlement.feature` with `GatewayBatchLifecycle.tla`. The engineering rules that make a\nfeature file mandatory are in [the rules](/methodology/rules).\n\n## Design rationale\n\nWriting the behavior first, then the test, then the code, is slower at the start of a work package and\ncheaper across its life. The feature file gives the operator and the agent one artifact to agree on before\nwork begins, and because it is executed on every commit, it cannot quietly fall out of step with the code\nthe way prose documentation can. The cost is discipline: a behavior that is hard to state as a scenario is\nusually a behavior that is not yet well understood, and the method forces that to surface early rather than\nlate.\n\n## Access and canon\n\nAcademic tier. The features describe behavior over public addresses and the public testnet RPC; no keys or\ncredentials appear in them or on this page. We link the `.feature` files and their step definitions rather\nthan copy them, and show only an abridged illustrative scenario, so the files in the repositories remain the\ntruth.\n\n## Source and verification\n\n- Source: `citrate-chain/specs/gherkin/` (31 features) plus the per-repo libraries named above under\n `bdd/`, `.agentile/`, and `features/` directories. Methodology: Gradient Paper No. 4,\n `gradient_papers_v3/Gradient_Papers_No4_Behavioral_Issues_v3.md` (linked, not copied).\n- Audited against SHA: `e68af83` (citrate-chain); per-repo libraries pinned at each repo's HEAD.\n- Status: Specified, the features are written and run as acceptance tests in continuous integration; a\n feature with passing steps in CI is Verified for the surface it covers. The `.feature` files and their\n step definitions are the truth; this page links them.\n"},"/research/gradient-papers":{"slug":"/research/gradient-papers","title":"The Gradient Papers (v3)","tier":"public","orgId":null,"sourceKind":"linked","source":"citrate-docs/gradient_papers_v3/","syncedSha":"cd729ed","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The Gradient Papers are the research corpus the Citrate Network grew from, an eleven-part working\ndissertation plus a series index. This page is for researchers, engineers, and reviewers who want the\nreasoning behind the design. It indexes the papers and points each one at the surface in Almanac that\ntreats it; following Rule 9, it summarizes and links the papers, it does not copy them.\n\n## What it is\n\nThe papers argue one thesis: a public ledger and a learning network are the same shape. A ledger is many\nmachines agreeing on one view of truth; a learning network is many machines converging on one\nrepresentation of the world. Both are gradient processes, one over disagreement, one over loss. The\nseries makes that identity load-bearing, so every paper that claims to learn points at a contract that\nrecords contribution, and every paper that claims to reach consensus points at a finality mechanism that\ntreats disagreement as data.\n\nThe papers are research, not a product manual. Some describe surfaces that run on testnet today, some\ndescribe designs written down but not yet built, and one describes a hardware direction with no code at\nall. We keep those honest by carrying a maturity tag on each paper and, in the table below, naming the\nAlmanac page where the work actually lives when it has been built.\n\nThe v3 revision was written in April 2026 against the v0.5.0 testnet on chain id 40204, so every\nmechanical claim traces to a file path or a public contract address in the source. The series index,\n`Gradient_Papers_No0_Series_Index_v3.md`, carries the v2 to v3 change log and the reading paths by role.\n\n## How to use it\n\nRead the index first, then the paper your role calls for. The suggested orders, taken from the No.0\nindex, are:\n\n| Reader | Suggested order |\n|---|---|\n| Engineers | I, X, XI, IV, II, III |\n| Researchers | XI, II, V, X, III, I |\n| Operators | I, IV, X, IX, VIII |\n| Community | VIII, VII, VI, IX, I |\n\nWhen a paper has a surface in Almanac, read the Almanac page for what is built and the paper for why it is\nbuilt that way. When a paper is theoretical, the paper is all there is, and the page says so.\n\n## Reference\n\nEleven papers numbered No.1 through No.11, plus the No.0 series index. The maturity column is the paper's\nown header tag. The Almanac page column links to the surface that treats the work; where a paper is\nresearch with no built surface, that is stated instead.\n\n| No. | Title | One line | Treated in Almanac |\n|---|---|---|---|\n| 0 | Series Index | The map: change log, maturity tags, and reading paths by role. | this page |\n| 1 | Citrate Technical Paper | The foundational specification, a Layer-1 BlockDAG with the EVM-compatible Lattice VM and AI-native precompiles that make models first-class on the ledger. | [Lattice VM](/chain/lvm), [precompiles](/chain/precompiles) |\n| 2 | Paraconsistent Consensus | Treats disagreement as information; runs federated meta-learning over GhostDAG and BFT checkpoints, combining views with Belnap four-valued logic. | [paraconsistent consensus](/research/paraconsistent) |\n| 3 | The Mentorship Protocol | The social layer of learning: how a weaker node finds a stronger one to learn from, who earns mentor standing, and how a mentorship is told apart from extraction. | [mentorship](/research/mentorship) |\n| 4 | Behavioral Issues | Catalogues five reproducible agent failure modes and the behavior-driven development discipline, Gherkin contracts, that blocks each one. | [behavior-driven development](/research/bdd) |\n| 5 | ATIS | Proposes computing transformer attention's token-importance score in analog hardware before the digital projection; carries an honest analysis of why the naive version does not pay off. | [ATIS](/research/atis) |\n| 6 | The Memetic Money Portal | Moving value in and out of the network; replaces the earlier automated bridge with a contracted market-maker model governed on the ledger. | [the bridge](/chain/bridge) |\n| 7 | The Cooperative Model (conceptual) | A conceptual, currently-tabled research direction: a third path between concentrated ownership and flat cooperatives, where standing accrues in proportion to contribution, recorded on the ledger. Not a current legal entity. | [marketplace economics](/contracts/economics) |\n| 8 | The BR1J Constitution | The constitutional law of the Citrate organization, the boundaries no proposal can cross, enforced through the treasury governor. | [governance](/contracts/governance) |\n| 9 | The Medusa Paradigm | Derives the architecture from cnidarian biology, nerve nets, siphonophore colonies, and Turritopsis, mapping the motifs to slashing and attestation surfaces. | research only; motifs surface in [security](/contracts/security) |\n| 10 | The Substrate of Verifiable Inference | How on-chain verification of off-chain model work is mechanized: Halo2-KZG proofs, deterministic Q16 compute, and attestation gates. | [verifiable inference](/research/verifiable-inference) |\n| 11 | The Neuroarchitectural Transformer (NAT) | Verifiable-by-construction model architecture: the hidden representation is partitioned into named zones wired over a declared, model-checkable topology, merged on a deterministic Q16.16 path, with a provenance trace emitted as a first-class output of every forward pass. | research only; the public `nat` architecture |\n\n## Design rationale\n\nThe series is written to be auditable, not persuasive. v3 added a Verified tag for claims with on-chain\nor audit evidence, switched code references from filenames to file and line against a pinned commit, and\nreplaced hand-waved statistics with measured benchmarks. The stated honesty principle is that each paper\nsays plainly whether a thing works today, is specified, or is conjectural, and a continuous-integration\ncheck moves a paper out of draft only once every numeric claim either cites code or is tagged as a\nhypothesis. We index them the same way: the table above does not promote a theoretical paper to a built\nfeature, and the maturity column is the paper's own, not ours.\n\n## Access and canon\n\nAcademic tier. The papers cite public contract addresses and the public testnet RPC only; no keys,\nrecovery phrases, or private endpoints appear in them or here. The network is a public ledger paired with\nprivate on-premise instances, and the research describes the public half. The papers are authored by Larry Klosowski\nand Lauren Mendenhall, Citrate Inc.\n\n## Source and verification\n\n- Source: `citrate-docs/gradient_papers_v3/` in this repository, eleven numbered papers plus\n `Gradient_Papers_No0_Series_Index_v3.md`.\n- Audited against SHA: `cd729ed` (citrate-docs).\n- Rule 9: this page is an annotated index. The papers are the source of truth; Almanac links them and does\n not duplicate their text.\n- Status: Specified. The papers are a written corpus; the maturity of each described surface is the\n paper's own tag, shown above and detailed on the linked Almanac pages.\n"},"/research/learning":{"slug":"/research/learning","title":"Citrate Orchard, federated learning cycles","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/core/learning/","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"The learning cycle and its four phases","anchor":"the-learning-cycle-and-its-four-phases"},{"depth":2,"text":"How the cycle records its result","anchor":"how-the-cycle-records-its-result"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Citrate Orchard is the part of the network where models learn together without their training data leaving the machines that hold it. This page explains the learning cycle, how its result is recorded, and where the engine ends and the on-chain wiring is still ahead of us. It is written for researchers and protocol engineers.\n\n## What it is\n\nA model on Citrate does not learn by gathering everyone's data into one place. Each node serves a reference input through its own local model and publishes the result of that, an embedding vector with a per-dimension confidence, never the data behind it. Many nodes do this; their published vectors are combined into one shared learning state. The raw material stays on the hardware that produced it, in keeping with the on-premise default that holds across the network.\n\nThe combining happens on a rhythm. The chain reaches a checkpoint on a fixed cadence, and each checkpoint is the moment the published vectors are gathered and reduced. The engine that does this work lives in the `citrate-learning` crate. It runs alongside the chain: it reads from consensus, blue scores and finalized embeddings, and it writes nothing back into transaction execution or block ordering.\n\nThe single load-bearing property is that the learning result is recorded separately from the ledger's state. At each checkpoint the engine produces a `learning_root`, a hash of the aggregated learning state. That root is independent of the `state_root`: changing one cannot change the other. We return to this below, because it is what lets learning ride alongside consensus without ever endangering it.\n\n## How to use it\n\nYou do not call the learning engine directly the way you call an RPC method; it runs inside a node at checkpoint boundaries. To exercise it yourself, the most direct path is to drive the pipeline in a short Rust program, which is exactly what the companion tutorial walks through.\n\n1. Read this page for the model, then [reproduce a learning round](/research/tutorials/reproduce-a-learning-round) to run the four phases end to end against the real crate.\n2. To understand how a checkpoint becomes a synchronization point for both blocks and learning, read [the checkpoint mechanism](/chain/consensus), where the `state_root` independence invariant (INV-4) is defined.\n3. To see how disagreement between nodes is preserved rather than averaged away, read [paraconsistent aggregation](/research/paraconsistent).\n4. To see where the published embeddings come from in production, read [the compute pool](/compute/pool) and [the operator dashboard](/apps/dashboard).\n\n## The learning cycle and its four phases\n\nA learning round moves through four phases, named in the code as the `OodaPhase` enum: Observe, Orient, Decide, Act. They are the four phases of the learning cycle, and they run once per checkpoint.\n\n| Phase | What happens | Code |\n|---|---|---|\n| Observe | Each participating node serves a reference input through its local model and submits an embedding with a per-dimension confidence. | `phases.rs::OodaPhase::Observe`, `orchestration.rs::PeerEmbedding` |\n| Orient | The submitted embeddings are combined by the dual-output aggregator into an aggregated embedding, a Belnap state vector, and a confidence. | `phases.rs::LearningPipeline::orient`, `aggregation.rs::ParaconsistentAggregator` |\n| Decide | A small multilayer-perceptron router reads the query, the aggregated embedding, and the state vector, and chooses a destination. | `phases.rs::LearningPipeline::decide`, `routing.rs::MlpRouter` |\n| Act | If the network has matured enough, a LoRA adapter is produced from the aggregated embedding; otherwise nothing is emitted. | `phases.rs::LearningPipeline::act`, `adapters.rs::AdapterFactory` |\n\nAbove this per-checkpoint cycle sits a slower, network-wide progression, the macro-phase: `Collection`, then `RoutingActive`, then `FullSystem` (`phases.rs::NetworkLearningPhase`). The network only starts routing once it has accumulated confident embeddings across enough checkpoints, and only starts producing adapters once the router's loss has settled. The Act phase produces an adapter only in `FullSystem`. The transition rule is in `MacroPhaseManager::evaluate_checkpoint`: a fixed number of consecutive checkpoints must clear a confidence threshold to advance to `RoutingActive`, then clear a loss threshold to reach `FullSystem`. `FullSystem` is terminal.\n\n## How the cycle records its result\n\nWhen a checkpoint height is reached, `LearningOrchestrator::run_checkpoint_aggregation` gathers the local embedding and the peer embeddings, drops any that fail validation, and checks quorum. If fewer than the configured minimum of valid embeddings are present, it returns a zero `learning_root` rather than an error, which matches the quorum invariant (INV-5) in the formal spec. With quorum met, it runs the aggregation and computes:\n\n```text\nlearning_root = SHA3-256( aggregated_embedding (f32 LE) || state_vector (1 byte each) || checkpoint_height (u64 LE) )\n```\n\nThe `learning_root` is a separate field on the block header (`core/consensus/src/types.rs`). The block's own hash, `Block::compute_hash`, deliberately excludes it: it hashes the header, the `state_root`, the transaction root, the receipt root, and the artifact root, and not `learning_root`. This is the independence property in concrete terms, two blocks identical except for their `learning_root` produce the same `compute_hash`, so the learning result can never alter the ledger's state or the ordering of blocks. The consensus crate labels this invariant INV-4, StateRootIndependent, and verifies it against the TLA+ spec `StrobilationCheckpoint.tla`.\n\nOne honest note on the hash. An earlier draft of this page described `learning_root` as MiMC-hashed. The code uses SHA3-256, chosen specifically to avoid a circular dependency with the execution crate's MiMC implementation; the determinism guarantee, same inputs always yield the same root, is identical either way (`orchestration.rs::compute_learning_root`).\n\n## Reference\n\nThe surface of the `citrate-learning` crate, with source paths. All paths are relative to `citrate-chain/core/learning/src/`.\n\n| Item | Kind | Source |\n|---|---|---|\n| `OodaPhase` | enum, the four phases Observe / Orient / Decide / Act | `phases.rs` |\n| `PhaseManager` | per-checkpoint phase transitions | `phases.rs` |\n| `NetworkLearningPhase`, `MacroPhaseManager` | network-wide macro-phase progression | `phases.rs` |\n| `LearningPipeline` | coordinates orient, decide, act | `phases.rs` |\n| `ParaconsistentAggregator::aggregate_paraconsistent` | dual-output aggregation | `aggregation.rs` |\n| `AggregationResult` | aggregated embedding, state vector, confidence | `aggregation.rs` |\n| `BelnapValue`, `classify_belnap`, `reduce_belnap_states` | four-valued logic, see [paraconsistent](/research/paraconsistent) | `belnap.rs` |\n| `EmbeddingVector` | a fixed-dimension vector with L2 norm and cosine similarity | `embeddings.rs` |\n| `LearningOrchestrator::run_checkpoint_aggregation` | gather, validate, aggregate, hash | `orchestration.rs` |\n| `compute_learning_root` | the SHA3-256 root | `orchestration.rs` |\n| `LearningCheckpoint` | the checkpoint record and its learning fields | `checkpoint.rs` |\n| `SafetyGuard`, `LearningMode` | enforces the state-root invariant; modes Disabled / Passive / Active | `safety.rs` |\n\nThe defaults from `config.rs`: embedding dimension 768, minimum 3 participants, confidence thresholds 0.8 and 0.3, softmax temperature 1.0, LoRA rank 16, and 3 consecutive checkpoints to advance a macro-phase.\n\n## Design rationale\n\nMost learning systems move the data to the model. For a school or a hospital that is not an option, so Citrate moves only the result of local learning, an embedding, and combines those. Tying the combining to consensus checkpoints means learning inherits the chain's safety and liveness for free, and computing a separate `learning_root` rather than folding the result into the `state_root` means a bug or a disagreement in learning can never corrupt the ledger. That separation is the price and the point: learning is a passenger on consensus, never a driver of it.\n\n## Failure modes\n\n- Below quorum, the orchestrator returns a zero `learning_root` rather than aggregating thin data, so a checkpoint with too few participants is recorded as having learned nothing rather than something unreliable.\n- Embeddings with the wrong dimension, with non-finite values, with a confidence vector of the wrong length, or with a negative blue score are filtered out before aggregation and logged (`orchestration.rs::validate_embeddings`).\n- The `SafetyGuard` keeps learning in one of three modes and audits every transition; in `Disabled` no embeddings are collected, so a node can run consensus with learning fully off and produce a bit-identical `state_root`.\n\n## Access and canon\n\nAcademic tier. The learning engine is a research contribution and its on-chain orchestration is not yet a finished product surface, which is why this page sits here rather than under a public surface. No keys, endpoints, or credentials appear on this page. The on-premise default holds: a node publishes embeddings only when its operator has chosen to take part, and identity on the public network is verified through VERI, Citrate's in-house verification.\n\n## Source and verification\n\n- Source: `citrate-chain/core/learning/`, audited against SHA `e68af83`.\n- Key files: `phases.rs` (the four phases, macro-phase, pipeline), `aggregation.rs` (dual-output aggregation), `belnap.rs` (four-valued logic), `orchestration.rs` (`LearningOrchestrator`, `compute_learning_root`), `checkpoint.rs` (`LearningCheckpoint`), `safety.rs` (`SafetyGuard`), `embeddings.rs`.\n- The block header `learning_root` field and its exclusion from `Block::compute_hash` are in `core/consensus/src/types.rs`; the invariant is INV-4 (StateRootIndependent) against `specs/tla/StrobilationCheckpoint.tla`.\n- Status by surface. The `citrate-learning` crate is Implemented (pre-audit), with unit, property, and integration tests across the four phases, the four-valued lattice laws, and the safety invariant. The on-chain wiring, the orchestrator driven by a live block producer and federated rounds on testnet 40204, is Specified, not yet a production feature. Treat this page as documenting a real, tested engine whose chain integration is in progress.\n"},"/research/mentorship":{"slug":"/research/mentorship","title":"The Mentorship Protocol","tier":"public","orgId":null,"sourceKind":"linked","source":"citrate-docs/gradient_papers_v3/Gradient_Papers_No3_Mentorship_Protocol_v3.md","syncedSha":"03d7851","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The mentorship protocol is how a weaker node in the Citrate Orchard finds a stronger one to learn from.\nAt each checkpoint the network pairs nodes by their measured strengths and weaknesses, the stronger node\nproduces a small update that moves the weaker one toward it, and the whole exchange is recorded. This\npage is for researchers and operators; it documents the matching code that runs today and summarizes\nGradient Paper III for the protocol design around it.\n\n## What it is\n\nThink of the Orchard as a grove where some trees fruit well in one season and poorly in another. Rather\nthan let each tree learn alone, the network grafts: a node strong in some region of the model's behavior\nlends a weaker node an adapter, a small set of weights that nudges the weaker node's representation\ntoward the stronger one's. The paper's argument is that distributed model swarms fail the way human\norganizations fail, when knowledge transfer is implicit, unrecorded, and one-directional, so Citrate\nmakes each transfer an explicit, recorded action.\n\nTwo layers are worth keeping separate. The matching layer, which decides who learns from whom, runs in\nthe node software today. The fuller protocol around it, the trust gating and pricing and dispute handling\nthe paper describes, is partly on the ledger and partly still specified. The sections below say which is\nwhich.\n\n## How to use it\n\nMatching is not something an operator invokes by hand; it runs inside the learning cycle. The path it\ntakes each checkpoint is:\n\n1. Each participant carries a performance profile, its measured accuracy and the domains it works in.\n2. Participants are sorted by accuracy. The stronger half are candidate mentors, the weaker half candidate mentees.\n3. Each mentee is matched to the mentor with the highest complementarity that still has spare capacity, where complementarity rewards a wide accuracy gap and shared working domains.\n4. The chosen mentor produces a delta adapter, the element-wise difference between its embedding and the mentee's, which applied to the mentee moves its representation toward the mentor's.\n5. The adapter is wrapped with provenance and a content hash so the exchange can be checked later.\n\nTo follow the surrounding surfaces, read [federated learning cycles](/research/learning) for where\nmatching sits in the cycle, and [model contracts](/contracts/models) for the LoRAFactory registry that\nrecords adapters on the ledger.\n\n## Reference\n\nThe matching surface, anchored in `citrate-chain` at `03d7851`.\n\n| Surface | Where | What it does |\n|---|---|---|\n| `select_mentors` | `core/learning/src/mentor.rs` | Sorts participants by accuracy, splits into mentor and mentee halves, and pairs each mentee to the best uncapped mentor. |\n| `MentorPairing` | `core/learning/src/mentor.rs` | The record of one pairing: mentor, mentee, complementarity score, both accuracies, and shared domains. |\n| `generate_delta_adapter` | `core/learning/src/mentor.rs` | Computes the mentor-minus-mentee embedding delta, rejecting mismatched dimensions or non-finite values. |\n| `generate_adapter_for_mentee` | `core/learning/src/mentor.rs` | Wraps the delta in a `LearningAdapter` with metadata, provenance, and hash, ready to broadcast. |\n| `validate_pairing` | `core/learning/src/mentor.rs` | A pure predicate mirroring the `MentorMatcher.sol` contract check, in Q16.16 fixed point so the node and the contract agree exactly. |\n\nThe constants that bound matching are explicit in the code: `MIN_ACCURACY_GAP` is `0.05`, so a mentor\nmust be at least five points more accurate than its mentee, and `MAX_MENTEES_PER_MENTOR` is `3`, so no\nmentor can take more than three mentees in a cycle. The complementarity score is\n`max(1, shared_domains) * accuracy_gap`, which keeps zero-overlap pairs scorable while rewarding shared\nground. The `validate_pairing` helper enforces the on-ledger gate in lockstep with the contract, and its\nvariant order is load-bearing because it ABI-decodes from the Solidity enum:\n\n```rust\npub enum PairingValidity {\n Ok = 0,\n SelfMentor = 1,\n MentorBelowTrustFloor = 2,\n AccuracyGapTooSmall = 3,\n MentorAtCapacity = 4,\n MenteeAlreadyAssigned = 5,\n}\n```\n\nThe paper adds the protocol layer the matching code rides on. The `ContributionAccounting` contract\nweights seven contribution types, with adapter creation weighted highest at 2.0, so the candidate pool is\nnodes that have actually produced useful adapters. A node's standing in consensus, its blue score from\nGhostDAG, is reused as a necessary trust floor: a node that cannot keep up with consensus is an unlikely\nsource of good adapters, though a high blue score alone does not earn mentor standing. A first-time\nmentee can require a Halo2-KZG proof of adapter quality before integrating, described under\n[verifiable inference](/research/verifiable-inference). Fees are calibrated by a pricing oracle so\nmentoring is paid but not rent-extracting.\n\n## Design rationale\n\nThe matching code is deliberately fixed point, not floating point, on its on-ledger path. Float results\nare not guaranteed identical across processors, and the node and the contract must agree on whether a\npairing is valid; the Q16.16 representation in `validate_pairing` makes the two implementations produce\nthe same answer over the full input space, which the property tests in the file pin. The accuracy-gap\nfloor and the per-mentor capacity cap are the smallest set of rules that prevent the obvious failures: a\nnode mentoring itself, a node with no standing posing as a mentor, a pairing with no real gap to learn\nacross, and one strong node saturating all demand. The paper's wider counters, an open adapter registry\nso any mentee can use a published adapter, per-checkpoint rotation, usage-weighted scoring so spam\nadapters earn nothing, and slashing for adapters later proven adversarial, address mentor capture and\nadapter pollution at the protocol level.\n\n## Access and canon\n\nAcademic tier. The mentorship surface runs inside the on-premise node software in the Citrate Orchard;\nmatching operates over performance profiles and embeddings that stay on the operator's hardware, and only\nthe adapter and its provenance are published when an operator chooses to. Contract addresses cited in the\npaper are public; no keys or credentials appear here.\n\n## Source and verification\n\n- Paper, linked, not copied: `citrate-docs/gradient_papers_v3/Gradient_Papers_No3_Mentorship_Protocol_v3.md`.\n- Code anchor, citrate-chain at `03d7851`: `core/learning/src/mentor.rs` for matching, delta-adapter\n generation, and the `validate_pairing` mirror; surrounding surfaces in `core/learning/src/adapters.rs`,\n `contracts/src/ContributionAccounting.sol`, `contracts/src/LoRAFactory.sol`, and `contracts/src/MentorMatcher.sol`.\n- Status: Implemented for matching. `select_mentors`, the delta-adapter pipeline, and the\n `validate_pairing` predicate exist, run, and are covered by unit and property tests in `mentor.rs`; this\n is pre-audit. Specified for the full distillation pipeline: the wider protocol the paper describes,\n blue-score trust gating, on-ledger per-cycle assignment, priced mentoring, and the application-layer\n proof-of-quality flow, is designed and partly built but not yet shipped end to end. The delta adapter\n in the code is a real update vector, not the full low-rank LoRA decomposition the paper envisions.\n- Related: [federated learning cycles](/research/learning), [model contracts](/contracts/models),\n [paraconsistent consensus](/research/paraconsistent), [verifiable inference](/research/verifiable-inference),\n [the Gradient Papers](/research/gradient-papers).\n"},"/research/paraconsistent":{"slug":"/research/paraconsistent","title":"Paraconsistent aggregation, Belnap four-valued logic","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/core/learning/src/belnap.rs","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"The four values","anchor":"the-four-values"},{"depth":2,"text":"How the implementation works","anchor":"how-the-implementation-works"},{"depth":2,"text":"How it maps to the network","anchor":"how-it-maps-to-the-network"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"When nodes in Citrate Orchard disagree about what a model should have learned, the network records the disagreement as information rather than averaging it into a single answer nobody holds. It does this with Belnap's four-valued logic. This page documents the implementation and is written for researchers.\n\n## What it is\n\nClassical agreement treats a difference of opinion as a fault to be resolved: honest parties are expected to converge on one value. Paraconsistent aggregation declines that frame. If one node's data says a given dimension of an embedding should be strongly positive and another node's says it should be strongly negative, the mean sits near zero, a value that is right for neither and that quietly erases the fact that the two nodes saw different worlds. That difference is often the most useful thing in the data: it is what distinguishes a personalized model, a regional dialect, or a domain specialist from a generic one.\n\nSo the aggregator produces two outputs per dimension, computed independently. One is a numeric value, a confidence-and-trust-weighted mean over the consenting sources. The other is a Belnap state, a label that says whether the sources agreed, disagreed, or said nothing. The router downstream reads both, so a dimension marked as contradictory can be sent to several sources for cross-validation instead of being trusted as a fictional average.\n\n## The four values\n\nNuel Belnap's logic admits four truth values. In `belnap.rs` they are the variants of `BelnapValue`, and each carries a meaning for the network.\n\n| Value | In code | Reading | Network meaning |\n|---|---|---|---|\n| True | `BelnapValue::True` | known true | the trusted sources agree this dimension is positive |\n| False | `BelnapValue::False` | known false | the trusted sources agree it is negative |\n| Both | `BelnapValue::Both` | true and false at once | sources of comparable trust genuinely disagree |\n| Neither | `BelnapValue::Neither` | no information | no source spoke with enough confidence |\n\nA mean collapses Both and Neither into the True/False continuum and loses them. The four-valued reduction keeps them. A `Both` is the signal that a dimension is contested; a `Neither` is the signal that it is simply unknown.\n\n## How the implementation works\n\nThe four values form a bilattice with two orderings. `belnap.rs` implements both: a knowledge ordering, where Neither sits below True and False, which sit below Both, and a truth ordering, where False sits below Neither and Both, which sit below True. The operations are `join` (combine information), `meet` (keep only what both inputs agree on), and `negation` (swap True and False, leave Both and Neither unchanged). Property tests check that join and meet are commutative, associative, idempotent, satisfy absorption, and that negation is its own inverse.\n\nAggregation runs in three steps inside `ParaconsistentAggregator::aggregate_paraconsistent` (`aggregation.rs`):\n\n1. Trust weights come from consensus. Each source's blue score is turned into a softmax weight, `softmax(blue_score / temperature)`, so a node that cannot keep up with consensus carries little weight in learning (`belnap.rs::softmax_weights`, `blue_scores_to_trust_weights`).\n2. Each source is classified per dimension by the function `classify_belnap`. A source above the high-confidence threshold that agrees with the trust-weighted majority is True; one that disagrees alone is False; one that disagrees but has a comparably trusted ally on its side is Both; one below the threshold is Neither.\n3. The per-source classifications are reduced to one state vector by joining across sources (`reduce_belnap_states`). If any source is Both, or sources split True against False, the result is Both. If all agree, it is True. Neither is absorbed by any other value.\n\nThe numeric embedding is computed separately, as a weighted mean using each source's trust weight times its per-dimension confidence. The two outputs, the embedding and the state vector, never read each other, which is what lets a dimension be numerically near zero and still be labelled Both.\n\n## How it maps to the network\n\n- The aggregation is checkpoint-aligned. Validators co-sign learning roots at the checkpoint barrier rather than per block, so learning safety inherits from the chain's safety and learning liveness from its liveness. See [the checkpoint mechanism](/chain/consensus).\n- The state vector is carried into [the learning cycle](/research/learning): the router reads it, and the macro-phase progression uses the aggregation's confidence.\n- A verifiable, in-circuit form of this aggregation is proposed but not built. The plan is a Belnap reduction over a fixed-point representation so the result is bit-deterministic and can be proved, which would let the aggregation be checked rather than trusted. See [zero-knowledge precompiles](/chain/precompiles-zkp) and [verifiable inference](/research/verifiable-inference).\n\n## Reference\n\n| Item | Kind | Source |\n|---|---|---|\n| `BelnapValue` | the four values | `core/learning/src/belnap.rs` |\n| `join`, `meet`, `negation` | lattice operations | `core/learning/src/belnap.rs` |\n| `k_leq`, `t_leq` | knowledge and truth orderings | `core/learning/src/belnap.rs` |\n| `softmax_weights`, `blue_scores_to_trust_weights` | trust weights from blue scores | `core/learning/src/belnap.rs` |\n| `classify_belnap` | per-source per-dimension classification | `core/learning/src/belnap.rs` |\n| `reduce_belnap_states` | reduce sources to one state vector | `core/learning/src/belnap.rs` |\n| `ParaconsistentAggregator::aggregate_paraconsistent` | the dual-output aggregation | `core/learning/src/aggregation.rs` |\n\n## Design rationale\n\nAveraging is cheap and almost always wrong when the inputs come from different distributions. Treating disagreement as information costs a richer representation, a state vector alongside the numbers, and a router that knows how to read it. The return is that the network can tell the difference between a dimension everyone agrees on, one that is genuinely contested, and one nobody has an opinion on, and it can act differently in each case. The two outputs are kept independent so that the label is never quietly derived from the number it is meant to qualify.\n\n## Access and canon\n\nAcademic tier. No keys, endpoints, or credentials appear here. The logic is a research contribution; the in-circuit precompile that would make the aggregation verifiable is a design direction, labelled below.\n\n## Source and verification\n\n- Source: `citrate-chain/core/learning/src/belnap.rs` and `aggregation.rs`, audited against SHA `e68af83`. Adversarial tests in `core/learning/tests/belnap_adversarial.rs`.\n- Status by surface. The Belnap lattice, the classification function, the reduction, and the dual-output aggregator are Implemented (pre-audit), with property tests for the lattice laws. The fixed-point, in-circuit aggregation precompile and the proof tie-in are Specified, not yet built.\n- Related: [Citrate Orchard, federated learning cycles](/research/learning), [zero-knowledge precompiles](/chain/precompiles-zkp), [verifiable inference](/research/verifiable-inference).\n"},"/research/tla":{"slug":"/research/tla","title":"The TLA+ formal specification corpus","tier":"public","orgId":null,"sourceKind":"linked","source":"citrate-agentile-archive/formal/specs/ + per-repo specs/tla/","syncedSha":"f28358f","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This is the machine-checked half of how Citrate establishes that its protocol is correct: a body of TLA+\nspecifications, each stating the safety properties of one state machine and checked with the TLC model\nchecker. It is for researchers and reviewers auditing protocol correctness. We link the corpus here; we do\nnot copy specs into the docs.\n\n## What it is\n\nA TLA+ specification is the source of truth for a state machine. It declares the legal states and the legal\ntransitions, then asserts invariants that must hold no matter how the machine moves, for example that no\nblock finalizes without a valid quorum. The TLC model checker explores the reachable state space and, if an\ninvariant can be broken, returns the exact sequence of steps that breaks it. This catches a design error\nbefore it becomes code, which is cheaper than catching it after.\n\nCitrate keeps a large corpus of these specifications, organized by domain. The public, verifiable figure is\n100+ TLA+ specifications across the public repositories (citrate-chain, nat, nist-agent, coop, core,\nmemories, comms, explorer, and others), 102 today. Anyone can reproduce that number by cloning those repos\nand running `find -name '*.tla' | wc -l`; the specs sit under each repo's `specs/tla/` (or `formal/`,\n`specs/`) tree so they can be exercised in continuous integration next to the code they constrain. The\nfuller authored corpus (roughly 169 specs, with its consolidated index and the spec-to-code mapping,\n`tla_to_solidity.md` and `tla_to_slint.md`) lives in an internal archive that is not public, so it is not the\nnumber to cite publicly; count the specs in the public repos instead. For invariant totals and TLC outcomes,\nread each repo's `INDEX.md` and `VERIFICATION_REPORT.txt`, not older summary files.\n\n## How to use it\n\nThe specs are checked with TLC, which needs Java and `tla2tools.jar`. The convention across repos is a\n`run_all.sh` driver in `specs/tla/` that auto-downloads the checker if it is missing and runs every\n`.tla` that has a matching `.cfg` in the domain subdirectories.\n\n```bash\n# Run the local runnable subset (one config per spec, four workers)\ncd specs/tla && bash run_all.sh\n\n# Deep verification (more workers, long timeout), run on demand\ncd specs/tla && bash run_deep.sh\n\n# A single spec\njava -jar tla2tools.jar -config consensus/GhostDAGConsensus.cfg \\\n consensus/GhostDAGConsensus.tla\n```\n\n`run_all.sh` runs the standard sweep, one `.cfg` per spec. Additional parameter configs (for example\n`_medium.cfg` or `_liveness.cfg`) are deep verifications driven by `run_deep.sh` on demand, not\nby the standard sweep. The rule for when a spec is required lives in `FORMAL_VERIFICATION_RULES.md`: it must\nbe written for any change to consensus, finality, or proposer election; it should be written for\nstate-machine or economic-rule changes and new protocol flows; it may be written for complex data-structure\nor interface-state invariants.\n\n## Reference\n\nThe corpus is grouped by domain. The table below names real specifications you will find in the canonical\ntree; `INDEX.md` carries the full per-domain list and invariant counts.\n\n| Domain | What it constrains | Representative specs |\n|---|---|---|\n| consensus | Blue-set ordering, VRF proposer election, finality, the concurrent executor | `GhostDAGConsensus.tla`, `VRFElection.tla`, `VRFChainContinuity.tla`, `PrevrandaoPipeline.tla`, `ExecutorMVCC.tla`, `GhostDAGAuditAnchor.tla` |\n| zk and halo2 | Proof lifecycle, verifying-key management, verifier version monotonicity | `ZKProofLifecycle.tla`, `ZKKeyManagement.tla`, `Halo2VerifierVersionMonotonic.tla` |\n| learning | OODA cycle, adapter provenance, paraconsistent aggregation, mentor selection, checkpointing | `OODACycle.tla`, `AdapterProvenance.tla`, `ParaconsistentAggregation.tla`, `BelnapLattice.tla`, `MentorSelection.tla`, `StrobilationCheckpoint.tla` |\n| contracts | Trust scoring, the spec registry, inference-request lifecycle, role-escalation grants | `TrustScoring.tla`, `SpecRegistryLifecycle.tla`, `InferenceRequestLifecycle.tla`, `RoleEscalationGrant.tla` |\n| compute | Settlement, batch-inference escrow, data and pipeline parallel jobs, disputes | `X402FacilitatorSettle.tla`, `GatewayBatchLifecycle.tla`, `DataParallelTrainingJob.tla`, `DisputeResolution.tla` |\n| agent | Approval, break-glass, capability grants, emergency stop, append-only trails | `ApprovalStateMachine.tla`, `BreakGlass.tla`, `CapabilityGrantLifecycle.tla`, `EmergencyStopProtocol.tla`, `TrailAppendOnly.tla` |\n| gui | Desktop state machines: onboarding, account session, send and deploy flows, role-escalation timer | `OnboardingStateMachine.tla`, `WalletSessionLifecycle.tla`, `SendTransactionFlow.tla`, `ContractDeploymentFlow.tla`, `RoleEscalationTimer.tla` |\n| network | Peer handshake, block sync, mempool gossip and routing | `P2PPeerHandshake.tla`, `BlockSyncProtocol.tla`, `MempoolGossipProtocol.tla` |\n| iot | Inter-organizational envelope transfer, sub-secret derivation | `InterOrgEnvelopeChain.tla`, `HKDFSubSecretDerivation.tla` |\n| account | Key lifecycle, signing, recovery safety, session limits | `WalletKeyLifecycle.tla`, `TransactionSigningFlow.tla`, `MnemonicRecoverySafety.tla`, `SessionRateLimiting.tla` |\n\nThe archive also carries `legacy-gui` and `audit-archive` trees, historical specs from the 2026-03 security\ndeep audit, which are excluded from the authored count.\n\nPer-repo runnable subsets sit next to the code they govern:\n\n- `citrate-agentile-archive/formal/specs//`, the fuller authored corpus in a private internal\n archive (not public, so not the publicly countable figure).\n- `citrate-chain/specs/tla/{consensus,zk,learning,contracts,compute,gui,network}/`, with `run_all.sh`,\n `run_deep.sh`, and `VERIFICATION_REPORT.txt`; the chain README notes this is a runnable subset, not the\n authority for counts.\n- `citrate-explorer/specs/tla/` (for example `SelectedParentReconcile.tla`).\n- `citrate-memories/specs/` (`Authz.tla`, `Ingestion.tla`, `SupersededDag.tla`, with `check.sh`).\n- `citrate-comms/formal/` (`AuditChainIntegrity.tla`, `RelayCommitOrder.tla`).\n\nThe spec registry that records which spec governs which surface is documented under\n[governance](/contracts/governance); the methodology that says when to write a spec is in the\n[workflow](/methodology/workflow). The behavioral counterpart, what the system does rather than what states\nit may occupy, is the [BDD library](/research/bdd).\n\n## Design rationale\n\nWe separate two questions on purpose. A TLA+ spec answers \"can this state machine ever reach a bad state\",\nwhich a model checker can decide by exhaustive search of an abstract model. A behavior test answers \"does\nthe running code do the right thing on this input\". Keeping the abstract model in TLA+ lets us find ordering\nand concurrency bugs, the ones that hide between valid steps, before any code exists, and the spec-to-code\nmapping keeps the model honest about what it actually constrains. The cost is that a spec is an abstraction\nand can drift from the code; the mapping files and the per-repo runnable subsets exist to keep that drift\nvisible.\n\n## Access and canon\n\nAcademic tier. The specifications are abstract state machines; no keys, hostnames, or credentials appear in\nthe corpus or on this page. We link the specs and their indices rather than copy them, so the `.tla` and\n`.cfg` files and the TLC run artifacts remain the truth in their repositories.\n\n## Source and verification\n\n- Source: `citrate-agentile-archive/formal/specs/` (canonical) plus the per-repo `specs/tla/` runnable\n subsets named above.\n- Audited against SHA: `f28358f` (citrate-agentile-archive); per-repo subsets pinned at each repo's HEAD,\n for example citrate-chain at `e68af83`.\n- Status: a subset of specs carry recorded TLC runs - for example `ExecutorMVCC.tla` (checked at Small,\n Liveness, and Medium configurations, with a deep run reported clean) and `Halo2VerifierVersionMonotonic.tla`\n (four invariants). These are **bounded** model checks over abstract, finite configurations, not exhaustive\n proofs over the unbounded system, and the largest specs (the `cit-agent` domain) are checked at bounded\n parameters only. The corpus as a whole is Specified and being checked spec by spec; do not read a global\n \"N verified\" figure into it - consult `INDEX.md` and each repo's `VERIFICATION_REPORT.txt` for the current,\n reproducible outcome of any one spec.\n"},"/research/tutorials/reproduce-a-learning-round":{"slug":"/research/tutorials/reproduce-a-learning-round","title":"Reproduce a learning round","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/core/learning/","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Step 1, build and run the learning suite","anchor":"step-1-build-and-run-the-learning-suite"},{"depth":3,"text":"Step 2, run one round yourself","anchor":"step-2-run-one-round-yourself"},{"depth":3,"text":"Step 3, read what happened","anchor":"step-3-read-what-happened"},{"depth":3,"text":"Step 4, see the learning root","anchor":"step-4-see-the-learning-root"},{"depth":3,"text":"Step 5, see the safety invariant","anchor":"step-5-see-the-safety-invariant"},{"depth":3,"text":"Step 6, the on-chain path, Specified, not yet wired","anchor":"step-6-the-on-chain-path-specified-not-yet-wired"},{"depth":2,"text":"What you reproduced","anchor":"what-you-reproduced"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"A walk-through of one complete learning round, run locally against the real `citrate-learning` crate, so you can watch the four phases produce an aggregated embedding, a Belnap state vector, and a deterministic learning root. For researchers. The crate-level steps run today; the on-chain steps are marked where they are not yet wired.\n\n## What it is\n\nThe learning engine that ships in `citrate-chain` is exercised by the crate's own tests, so you can reproduce a round without a running node. You will build the crate, run its end-to-end suite, then write one short test that walks the four phases by hand and prints what each produces. The concepts map one to one to [Citrate Orchard](/research/learning) and [paraconsistent aggregation](/research/paraconsistent).\n\n## How to use it\n\nYou need a Rust toolchain (`rustup`, stable; `cargo --version` should work), a checkout of `citrate-chain` at SHA `e68af83` or later, and about five minutes.\n\n### Step 1, build and run the learning suite\n\nThe crate already contains the full pipeline as tests. Confirm it builds and the Observe-through-Act path passes.\n\n```bash\ncd citrate-chain\ncargo test -p citrate-learning\n```\n\nThe rounds that matter live in these tests:\n\n- `core/learning/tests/e2e_ooda_pipeline.rs`, a full cycle across three participants covering aggregation, Belnap classification, routing, LoRA, safety, and Byzantine detection.\n- `core/learning/tests/belnap_adversarial.rs`, disagreement handling.\n- `core/learning/tests/lora_provenance.rs`, the adapter provenance chain.\n\nTo run just the end-to-end cycle:\n\n```bash\ncargo test -p citrate-learning --test e2e_ooda_pipeline\n```\n\n### Step 2, run one round yourself\n\nAdd the following as `core/learning/tests/my_round.rs`. The API is taken directly from `core/learning/src/phases.rs`. Three participants submit embeddings; participant three disagrees on dimension 0.\n\n```rust\nuse citrate_learning::aggregation::AggregationInput;\nuse citrate_learning::config::LearningConfig;\nuse citrate_learning::embeddings::EmbeddingVector;\nuse citrate_learning::phases::{LearningPipeline, MacroPhaseManager};\n\n#[test]\nfn reproduce_a_learning_round() {\n let dim = 4;\n\n // Tiny config so the round is fast. One good checkpoint reaches FullSystem.\n let config = LearningConfig {\n embedding_dimensions: dim,\n lora_rank: 2,\n macro_confidence_threshold: 0.5,\n macro_loss_threshold: 0.5,\n macro_consecutive_checkpoints: 1,\n ..LearningConfig::default()\n };\n\n let mut pipeline = LearningPipeline::new(&config);\n let mut macro_mgr = MacroPhaseManager::new(config.clone());\n\n // --- Observe: three participants each submit an embedding plus confidence ---\n let e1 = EmbeddingVector::new(vec![0.9, 0.8, 0.7, 0.6]).expect(\"e1\");\n let e2 = EmbeddingVector::new(vec![0.85, 0.75, 0.65, 0.55]).expect(\"e2\");\n let e3 = EmbeddingVector::new(vec![-0.8, 0.7, 0.66, 0.50]).expect(\"e3\"); // dim 0 disagrees\n let conf = vec![0.9; dim];\n let query = EmbeddingVector::new(vec![0.5; dim]).expect(\"query\");\n\n let input = AggregationInput {\n embeddings: &[&e1, &e2, &e3],\n confidences: &[&conf, &conf, &conf],\n blue_scores: &[1.0, 1.0, 1.0], // trust weights from consensus\n temperature: 1.0,\n theta_high: 0.8,\n theta_low: 0.3,\n };\n\n // --- Orient: dual output, aggregated embedding plus Belnap state vector ---\n let agg = pipeline.orient(&input).expect(\"aggregate\");\n println!(\"aggregated embedding: {:?}\", agg.embedding);\n println!(\"Belnap state vector : {:?}\", agg.state_vector); // expect Both on dim 0\n\n // --- Decide: the router reads the state vector, not just the mean ---\n let decision = pipeline.decide(&query, &agg).expect(\"route\");\n println!(\"routed to destination: {}\", decision.selected);\n\n // --- Act: advance the macro-phase to FullSystem, then produce an adapter ---\n macro_mgr.evaluate_checkpoint(0.8, Some(0.2)); // drive toward FullSystem\n let result = pipeline\n .execute_cycle(&query, &input, macro_mgr.can_adapt(), [1u8; 32], 100)\n .expect(\"cycle\");\n\n if let Some(adapter) = result.adapter {\n println!(\"LoRA adapter dim={} rank={}\", adapter.dim, adapter.rank);\n assert_eq!(adapter.dim, dim);\n }\n}\n```\n\nRun it:\n\n```bash\ncargo test -p citrate-learning --test my_round -- --nocapture\n```\n\n### Step 3, read what happened\n\n- Dimension 0 had one strongly negative contributor against two positive ones, so its Belnap state resolves to `Both`. The network records the disagreement instead of averaging it to a misleading near-zero. This is the point of [paraconsistent aggregation](/research/paraconsistent).\n- The router received the query, the aggregated embedding, and the state vector together, so it can send a contested dimension to several destinations rather than trusting a fictional mean.\n- The adapter is produced only once `macro_mgr.can_adapt()` is true, which is the `FullSystem` macro-phase. In `Collection` or `RoutingActive` the Act phase produces no adapter; pass `false` to `execute_cycle` to confirm.\n\n### Step 4, see the learning root\n\nThe orchestrator is what a checkpoint actually calls. Add this to the same file to see the deterministic `learning_root` and confirm it is stable across runs.\n\n```rust\nuse citrate_learning::orchestration::{compute_learning_root};\nuse citrate_learning::belnap::BelnapValue;\n\n#[test]\nfn learning_root_is_deterministic() {\n let embedding = vec![0.1f32, 0.2, 0.3, 0.4];\n let state = vec![\n BelnapValue::True,\n BelnapValue::Neither,\n BelnapValue::Both,\n BelnapValue::False,\n ];\n let root_a = compute_learning_root(&embedding, &state, 100);\n let root_b = compute_learning_root(&embedding, &state, 100);\n assert_eq!(root_a, root_b); // same inputs, same root (INV-2)\n assert_ne!(root_a, [0u8; 32]); // and non-trivial\n}\n```\n\nThe root is `SHA3-256(aggregated_embedding || state_vector || checkpoint_height)` (`core/learning/src/orchestration.rs::compute_learning_root`). It is the value a node would place in the block header's `learning_root` field, which is independent of the `state_root`; see [Citrate Orchard](/research/learning) for that invariant.\n\n### Step 5, see the safety invariant\n\nA learning round must never change execution state. The `SafetyGuard` (`core/learning/src/safety.rs`) enforces that the `state_root` is identical whether learning is on or off. The crate's safety tests check it:\n\n```bash\ncargo test -p citrate-learning safety\n```\n\n### Step 6, the on-chain path, Specified, not yet wired\n\nIn production the embeddings are not hand-written; they are gossiped from finalized blocks, and the round is driven by the block producer at a checkpoint height. That wiring is Specified, not yet a running feature, and the relevant surfaces are honest about it:\n\n- The chain id is 40204 (`0x9d0c`); confirm with `eth_chainId`, see [the JSON-RPC reference](/chain/rpc).\n- `citrate_getTrainingJob` reads a job from storage by id. `citrate_createTrainingJob` is present but returns a placeholder today (its handler responds with \"Training job creation not fully implemented yet\" in `core/api/src/server.rs`), so do not expect it to enqueue real work yet.\n- The on-chain learning cycle contract `AILearningCycleCorePortable` in `contracts/src/edu/ai-gateway/` models the same shape on chain, `openCycle`, `joinCycle`, `startCollecting`, `submitCommitment`, `startAggregating`, `recordAdapter`, `finalizeCycle`, and is the intended home for the cycle state once the node wiring lands.\n\n## What you reproduced\n\nYou ran the four phases a live checkpoint runs, Observe, Orient with paraconsistent dual output, Decide, and Act, and you computed the deterministic `learning_root` the same way the orchestrator does. The difference from a live network is the source of the embeddings, hand-written here against gossiped on chain, and the node wiring, which is the integration frontier described on [Citrate Orchard](/research/learning).\n\n## Source and verification\n\n- Engine: `citrate-chain/core/learning/` at SHA `e68af83`. Pipeline API in `src/phases.rs`; aggregation in `src/aggregation.rs`; four-valued logic in `src/belnap.rs`; the root in `src/orchestration.rs`.\n- Reference tests: `tests/e2e_ooda_pipeline.rs`, `tests/belnap_adversarial.rs`, `tests/lora_provenance.rs`.\n- On-chain surfaces named above: `core/api/src/server.rs` (`citrate_createTrainingJob`, `citrate_getTrainingJob`) and `contracts/src/edu/ai-gateway/AILearningCycleCorePortable.sol`.\n- Status by surface. The crate-level round (Steps 1 through 5) is Implemented (pre-audit) and runs as shown. The on-chain path (Step 6) is Specified, with `citrate_getTrainingJob` and the cycle contract present and `citrate_createTrainingJob` not yet functional.\n- No keys, endpoints, or credentials appear in this tutorial.\n- Related: [Citrate Orchard](/research/learning), [paraconsistent aggregation](/research/paraconsistent), [education contracts](/contracts/edu), [JSON-RPC reference](/chain/rpc).\n"},"/research/verifiable-inference":{"slug":"/research/verifiable-inference","title":"The substrate of verifiable inference","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/core/execution/src/precompiles/{verify.rs,inference.rs}","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This is the research-angle account of how a contract on the Citrate Network can trust the result of an AI\ncomputation it did not run. It is a summary for researchers and reviewers; the proving internals that make\nit work are public in the `citrate-chain` repository and are linked rather than reproduced here.\n\n## What it is\n\nA model that runs off-chain produces a number, and a contract on-chain wants to act on that number without\npaying to run the model itself. The problem is trust: the contract has no reason to believe the result\nunless it can check it. Citrate answers this with a small set of precompiles that let the chain verify a\nclaim about off-chain work rather than repeat it, and it rests on three building blocks we can name in the\nopen.\n\nThe first is determinism. Floating-point math gives different answers on different hardware, depending on\nrounding modes and instruction ordering, so it cannot be the basis of a result every node must agree on.\nCitrate uses Q16.16 fixed-point arithmetic instead: a number is a 32-bit integer read as 16 integer bits\nand 16 fractional bits, computed with saturating integer operations. The same input gives the same bytes\non every machine, which is what makes a computation reproducible and therefore checkable.\n\nThe second is proof verification. An inference proof is checked by a Halo2-KZG verifier, a pairing-based\nsystem that lets the chain confirm a proof in one verification step instead of re-running the computation\nthe proof stands for. The contract sees a yes or no, not the work behind it.\n\nThe third is attestation. Some computations, a large language model on a GPU, cannot be made bit-identical\nand so cannot be proven this way. For those, the chain consults a hardware-attestation gate that decides\nwhether a non-deterministic path is allowed to run at all. Today that gate refuses by default; see the\nstatus below.\n\nThese three building blocks back the determinism that [paraconsistent consensus](/research/paraconsistent)\nand the [learning cycle](/research/learning) depend on, and they are summarized for builders on the chain\npage, [verification, inference, and attestation precompiles](/chain/precompiles-zkp). This page is the companion to that one and does\nnot contradict it.\n\n## How to use it\n\nYou meet this substrate through precompile addresses, the same way you would call any contract on the chain.\n\n1. To commit to a tensor, call `0x0107`. It returns a 32-byte field element that binds the tensor's data\n and its shape, so two payloads with the same bytes but different shapes commit differently.\n2. To check an inference proof, call `0x0108`. It returns a 32-byte boolean, one for valid and zero for\n invalid.\n3. To check that a single element belongs to a committed tensor, call `0x0109` with a Merkle path. It\n returns a 32-byte boolean.\n4. To run inference itself, call into the hosted-inference family, `0x0100` to `0x0106`. This path is\n model-runtime-backed and returns a signed receipt over the result, gated by hardware attestation - an\n attestable statement about what ran, not a cryptographic proof that the output is correct. The\n non-deterministic paths, `0x0101` and `0x0102`, first consult the attestation gate, which on a default\n validator binary refuses them and returns a discoverable error rather than a fabricated result. For a\n result that is verifiable on-chain, verify a proof through `0x0108` instead.\n\n## Reference\n\nThe verification surface, named from the precompiles that implement it. The deterministic verification\nfamily verifies claims; the inference family runs and registers models.\n\n| Address | Name | What it does |\n|---|---|---|\n| `0x0107` | `TENSOR_COMMIT` | Commitment over a canonical-format tensor; returns a 32-byte field element |\n| `0x0108` | `INFERENCE_PROOF_VERIFY` | Halo2-KZG verification of an inference proof; returns a 32-byte boolean |\n| `0x0109` | `MERKLE_VERIFY_TENSOR` | Merkle inclusion check over a committed tensor; returns a 32-byte boolean |\n| `0x0100` to `0x0106` | hosted inference family | Model deployment, single and batch inference, metadata, benchmarking, model encryption; returns a signed, attestation-gated receipt, not a proof of correctness |\n\nThe verification family at `0x0107` to `0x0109` is deterministic by construction, hash and pairing and\ninteger math only, and its byte-level output is frozen: any drift would fork the chain and invalidate every\nprior commitment. The compute family at `0x010A` to `0x010F`, six Q16.16 tensor primitives (matmul, dot,\nsoftmax, relu, linear, transpose), gives the in-circuit math its deterministic floor.\n\n## Design rationale\n\nThe whole design follows from one decision: check a proof instead of repeating the work. That is only sound\nif every step is reproducible, which is why commitments and proofs sit on deterministic primitives and why\nthe result bytes are frozen rather than versioned in place. The one place determinism cannot reach is\nfloating-point inference on a GPU, and there the choice is to refuse rather than trust. The attestation gate\ndefaults to rejecting an unattested non-deterministic path, with no allow-by-omission, so a missing or stale\nattestation fails closed. Failing closed is the safe trade for a path that touches non-deterministic\ncompute.\n\n## Access and canon\n\nThis is a summary. It names the verification surface and the three building blocks, Q16.16\nfixed-point determinism, Halo2-KZG proof verification, and hardware attestation. The proving-system\ninternals - circuit construction, prover and verifier internals, any structured reference string or setup\nmaterial, and the exact proof wire formats - are public in the `citrate-chain` repository (Apache-2.0);\nthis page summarizes and links to them rather than reproducing them. No setup seed, ceremony material,\nkeys, or credentials appear on this page. Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity. For the full internals, read the `verify.rs`, `inference.rs`, and `attestation/` precompile\nsources in `citrate-chain`.\n\n## Source and verification\n\n- Source: `citrate-chain/core/execution/src/precompiles/{verify.rs,inference.rs}`, with the compute\n primitives in `core/execution/src/precompiles/compute.rs` and the attestation gate in\n `core/execution/src/precompiles/attestation/`. Research context: Gradient Paper No. 10,\n `gradient_papers_v3/Gradient_Papers_No10_Substrate_of_Verifiable_Inference_v3.md` (linked, not copied).\n- Audited against SHA: `e68af83`.\n- Status: the deterministic verification path (`0x0107` to `0x0109`) and the Q16.16 compute primitives are\n Implemented (pre-audit) on testnet 40204. The `0x0108` verifier is Implemented behind a build feature so\n nodes that do not host it stay lean. The attestation gate is Implemented in its always-reject default;\n live hardware-attestation verification is Specified, not yet enabled.\n"},"/sdks/bundler":{"slug":"/sdks/bundler","title":"Citrate Bundler","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-bundler/gate/src/server.ts, citrate-bundler/gate/src/precheck.ts, citrate-bundler/gate/src/config.ts, citrate-bundler/Caddyfile, citrate-bundler/Dockerfile","syncedSha":"e1aa264","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"The paymaster precheck","anchor":"the-paymaster-precheck"},{"depth":3,"text":"citrate_getUserAddress","anchor":"citrate_getuseraddress"},{"depth":3,"text":"Examples","anchor":"examples"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The Citrate Bundler is the ERC-4337 service that accepts UserOperations for Citrate Keyring accounts on\nthe Citrate Network, chain id 40204, and submits them on chain. It is the piece that lets a person act\nthrough a smart-contract account without holding native SALT for gas, because a paymaster sponsors the\nwork. This page is for integrators wiring an account-abstraction flow against it.\n\n## What it is\n\nThe bundler is two parts on one host. The first is an eth-infinitism v0.7 reference bundler, vendored as a\nDocker image and run unchanged. The second is a thin Citrate gate written in TypeScript that sits in front\nof it. A client never talks to the reference bundler directly; it talks to the gate, and the gate proxies\nthe call upstream after it has checked the request.\n\nThe gate does three things, in order, before it forwards a call: it validates an API key, it applies a\nper-IP and a per-key rate limit, and on `eth_sendUserOperation` it runs a paymaster precheck against the\nchain. Every other JSON-RPC method passes through to the reference bundler unchanged, so a standard\nERC-4337 SDK treats this as an ordinary bundler. The gate is the only Citrate-specific code in the path;\nthe method surface is the standard one.\n\nThe bundler is one corner of the Citrate Keyring account-abstraction topology. A Citrate Keyring account\nis a smart-contract account, often controlled by a passkey rather than a stored secret, described in\n[passkeys](/aa/passkeys). When that account wants to act, it builds a UserOperation, names the\n[CitratePaymaster](/aa/paymaster) to cover gas, and sends it to this bundler. The bundler hands the\noperation to the EntryPoint, the EntryPoint validates it and pulls gas from the paymaster's deposit, and\nthe account's intent lands on chain. The JavaScript helpers that build and sign those operations live in\nthe [JavaScript SDK](/sdks/js).\n\n## How to use it\n\n1. Point your ERC-4337 client at the bundler host. The public endpoint is a JSON-RPC POST to `/rpc`, with\n TLS terminated at the edge by the reverse proxy. Use a placeholder host such as `` until\n you have the deployed name.\n2. Confirm you are talking to chain 40204 by calling `eth_chainId`. It returns `0x9d0c`. This call needs\n no API key.\n3. Obtain an API key. Keys are issued by the operator and carry a `bk_` prefix. Send it as\n `Authorization: Bearer ` on any call that submits work.\n4. Build a UserOperation in your SDK, name the CitratePaymaster, and submit it with\n `eth_sendUserOperation`. The gate runs its precheck, then forwards to the reference bundler, which\n bundles and submits it.\n5. Poll for the result with `eth_getUserOperationReceipt`, passing the hash returned by the send call.\n\n## Reference\n\nThe surface is `API-BUNDLER`, the JSON-RPC methods reachable at `POST /rpc`. The gate special-cases only\n`eth_sendUserOperation`; the rest are served by the eth-infinitism v0.7 upstream, so the standard v0.7\nmethod set applies.\n\n| Method | Params | Returns | Handled by |\n|---|---|---|---|\n| `eth_chainId` | `[]` | hex chain id, `0x9d0c` for 40204 | proxied to upstream |\n| `eth_supportedEntryPoints` | `[]` | array of configured EntryPoint addresses | proxied to upstream |\n| `eth_sendUserOperation` | `[userOp, entryPoint]` | UserOperation hash | gate precheck, then upstream |\n| `eth_estimateUserOperationGas` | `[userOp, entryPoint]` | gas estimate | proxied to upstream |\n| `eth_getUserOperationByHash` | `[hash]` | UserOperation and its location | proxied to upstream |\n| `eth_getUserOperationReceipt` | `[hash]` | receipt | proxied to upstream |\n\nThe host also answers two non-RPC routes. `GET /health` returns the string `ok` and is served by the\nreverse proxy without touching the bundler, a cheap liveness probe. `GET /healthz` returns a composite\nstatus served by the gate, `{ \"status\": \"ok\", \"redis\": true, \"upstream\": true }`, which checks Redis and\nthe upstream bundler. The `/metrics` route is Prometheus exposition and is not public; the edge returns\n`403` and operators scrape it from inside the host.\n\n### The paymaster precheck\n\nWhen a UserOperation names the CitratePaymaster and a paymaster is configured on the gate, the gate runs an\noff-chain precheck before it forwards the call (`gate/src/precheck.ts`). The precheck is an optimization,\nnot a security boundary; the EntryPoint re-validates everything on chain regardless. Its three steps:\n\n1. The category byte, the first byte of `paymasterData`, must be a known category, `0`, `1`, or `2`.\n2. The sender must be registered on the paymaster, read as `CitratePaymaster.isRegistered(sender)`.\n3. The paymaster must hold a non-zero EntryPoint deposit, read as `EntryPoint.balanceOf(paymaster)`,\n because a zero deposit would surface as an AA31 revert.\n\nA failed precheck returns JSON-RPC error code `-32002` with a reason. If the chain itself is unreachable\nthe precheck fails open with a logged reason, since on-chain validation remains authoritative. Operations\nthat pay their own gas, with no paymaster named, skip the precheck entirely.\n\n### citrate_getUserAddress\n\nA method named `citrate_getUserAddress(userId)`, which would predict the smart-contract account address\nfor a Citrate user id, is described in the repository README. It is **not implemented**. We searched the\ngate and the upstream method surface at the audited SHA and found no handler for it; the README documents\nan intended method that has not landed. Do not call it on the public endpoint. To predict an account\naddress today, use the on-chain factory through the [JavaScript SDK](/sdks/js). Status for this method:\nSpecified.\n\n### Examples\n\n```bash\n# Confirm chain connectivity, no API key required.\ncurl -s -X POST https:///rpc \\\n -H 'content-type: application/json' \\\n -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_chainId\",\"params\":[]}'\n# {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x9d0c\"}\n```\n\n```bash\n# List the configured EntryPoints.\ncurl -s -X POST https:///rpc \\\n -H 'content-type: application/json' \\\n -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_supportedEntryPoints\",\"params\":[]}'\n```\n\n```bash\n# Submit a UserOperation, API key required.\ncurl -s -X POST https:///rpc \\\n -H 'content-type: application/json' \\\n -H 'Authorization: Bearer ' \\\n -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_sendUserOperation\",\"params\":[{ /* userOp */ }, \"\"]}'\n```\n\n## Design rationale\n\nThe gate is deliberately thin. The reference bundler is a known quantity, audited upstream and run without\nmodification, so the only Citrate logic in the path is the gate, which is small enough to read in one\nsitting. The split also isolates failure: the bundler lives on its own host, so a bundler outage cannot\ntake down the identity service or the inference gateway.\n\nThe reference bundler runs in `--unsafe` mode (`Dockerfile`). That mode skips the `debug_traceCall`\nfull-validation step, which the Citrate Network RPC does not yet expose, a method that exists only on\ncertain client implementations. For the current single-tenant deployment, where every UserOperation\noriginates from verified Citrate clients rather than an open mempool, signature, nonce, and paymaster\nvalidation are sufficient. The flag is dropped once the chain gains the trace method. We note this here so\nthe trade-off is visible, not buried.\n\n## Failure modes\n\nThis surface is security relevant, and it fails closed where it matters.\n\n- **Missing or invalid API key.** When the gate requires a key, a missing key returns `-32001` and a bad\n key returns `-32001`; neither reaches the bundler. The key requirement is configurable, and the default in\n `gate/src/config.ts` is now on (`GATE_REQUIRE_API_KEY` defaults to `true`). An operator who wants an open\n endpoint must set `GATE_REQUIRE_API_KEY=false`, and in production must also set `GATE_ALLOW_ANONYMOUS=true`\n to accept the risk, or the gate warns.\n- **Rate limit exceeded.** A per-IP limit, default 60 per minute, and a per-key limit, default 600 per\n minute, both return `-32005`. If the backing Redis store is unavailable the rate limiter fails closed,\n rejecting rather than waving traffic through.\n- **Doomed sponsored operation.** The paymaster precheck rejects an unregistered sender, a bad category\n byte, or an empty paymaster deposit with `-32002`, before a bundler slot is spent. If the chain is\n unreachable the precheck fails open and the EntryPoint catches the same conditions on chain.\n- **Upstream unreachable.** If the gate cannot reach the bundler it returns `-32003` rather than hanging.\n- **Production config gaps.** In production the gate refuses to boot if a security-relevant value is unset,\n for example a missing Redis URL or an unset paymaster address; in development the same gaps degrade to\n logged warnings with safe defaults.\n\n## Access and canon\n\nTier: commercial. The method surface itself is standard ERC-4337, but operating against the bundler, API\nkeys, the precheck semantics, the rate-limit behavior, the EntryPoint configuration, is integration depth\nfor contracted builders and is gated from anonymous scraping.\n\nNo secrets appear here. The deployment runbook holds operator material, an operator account mnemonic and a\ngenerated store password, which live only in a `0600` `.env` on the host and are never transcribed into\ndocumentation. Examples use a placeholder host and ``. Do not paste any key, password,\nmnemonic, or private host detail into an example.\n\n## Source and verification\n\n- Source: `citrate-bundler`. Routing, auth, and rate limits in `gate/src/server.ts`; the paymaster\n precheck in `gate/src/precheck.ts`; the fail-closed config in `gate/src/config.ts`; public routing and\n the `/health` versus `/healthz` split in `Caddyfile`; the `--unsafe` upstream invocation in `Dockerfile`.\n The `citrate_getUserAddress` reference is in `README.md`. Operator secrets live in `DEPLOY.md` and are\n not reproduced here.\n- Audited against SHA: `e1aa264`.\n- Status: Implemented (pre-audit). The gate, the precheck, the rate limits, and the standard method surface\n exist and run; this slice has not had an external audit. The `citrate_getUserAddress` method is\n Specified, declared in the README but not implemented at this SHA.\n"},"/sdks/entitlements":{"slug":"/sdks/entitlements","title":"Entitlements and capabilities","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-sdk-js/src/entitlements/capabilities.ts","syncedSha":"9664fa8","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Citrate Identity mints a signed entitlement claim, `https://citrate.ai/entitlement`, that carries one of five\ntiers: `public`, `commercial`, `commercial.kyc`, `academic`, `confidential`. The SDK ships one canonical way to\nread that claim so relying parties stop disagreeing about what a tier means. It is available in the TypeScript\nSDK (`@citratelabs/sdk`, the `entitlements` namespace) and the Python SDK (`citrate_sdk.entitlements`).\n\n## What it is\n\nThe model is capabilities, not a global rank. There is no \"tier A outranks tier B\" comparison anywhere.\n`normalizeTier` collapses any unknown value to `public` and never escalates; `capabilities` returns an explicit\nset of what a principal may do. This matters because a bare ordinal is fragile. An unmapped tier that sorts as\n`undefined` once took a relying party's whole app down, and two apps that ordered the tiers differently reached\nopposite authorization decisions on the same signed claim.\n\nThe one decision worth stating plainly: `commercial.kyc` is the tier every KYC-verified principal receives, and\nit opens ecosystem transactions but not confidential content. Passing KYC lets you transact; it does not buy a\ncontent seat. `commercial.kyc` is not above `commercial`; it carries the same content capabilities.\n\n## How to use it\n\n```typescript\nimport { entitlements } from '@citratelabs/sdk';\n\nentitlements.normalizeTier('made-up'); // \"public\", unknown never escalates\nentitlements.capabilities('commercial.kyc').ecosystemTx; // true\nentitlements.capabilities('commercial.kyc').confidentialDocs; // false\n\n// can() applies expiry and the role bypass, matching the authority's resolveEntitlementClaim.\nentitlements.can(claim, 'confidentialDocs');\n```\n\n```python\nfrom citrate_sdk import entitlements\n\nentitlements.normalize_tier(\"made-up\") # \"public\"\nentitlements.capabilities(\"commercial.kyc\").ecosystem_tx # True\nentitlements.capabilities(\"commercial.kyc\").confidential_docs # False\nentitlements.can(claim, \"confidential_docs\")\n```\n\n## Reference\n\n| Name | What it does |\n|---|---|\n| `normalizeTier(value)` | Return one of the five tiers; anything unknown collapses to `public`. Never escalates. |\n| `capabilities(tier, overrides?)` | The capability set: `ecosystemTx`, `gatewayKeys`, `academicData`, `confidentialDocs`. |\n| `can(claim, capability, opts?)` | Whether a claim grants a capability, applying expiry and the `citrateRole` bypass. |\n| `DEFAULT_CAPABILITIES` | The canonical tier to capability map an RP can override. |\n\nThe default map: `public` grants nothing; `commercial` and `commercial.kyc` grant `ecosystemTx` and\n`gatewayKeys`; `academic` adds `academicData`; `confidential` adds `confidentialDocs`. A role-bearing principal\n(`citrateRole` set) bypasses the gate, and an expired claim collapses to `public`.\n\n## Design rationale\n\nShipping a single default map is the point: relying parties import it instead of each inventing an ordering, so\nthe federation cannot disagree by accident. An RP with a genuinely different policy passes an override map,\nwhich keeps the divergence explicit and local rather than silent and global.\n\n## Access and canon\n\nPublic. The tiers and capabilities are authorization facts, not secrets, and are derivable from public on-chain\nentitlement claims.\n\n## Source and verification\n\n- Source: `citrate-sdk-js/src/entitlements/capabilities.ts`, `citrate-sdk-python/citrate_sdk/entitlements.py`.\n- Status: Implemented (pre-audit); the JavaScript and Python matrices are unit-tested to give identical answers.\n"},"/sdks/identity":{"slug":"/sdks/identity","title":"Identity and the embedded Keyring account","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-sdk-js/src/identity/index.ts","syncedSha":"9664fa8","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Sign in with OIDC (PKCE)","anchor":"sign-in-with-oidc-pkce"},{"depth":3,"text":"The embedded account","anchor":"the-embedded-account"},{"depth":3,"text":"Read claims and capabilities","anchor":"read-claims-and-capabilities"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"Sign-in","anchor":"sign-in"},{"depth":3,"text":"ID-token verification","anchor":"id-token-verification"},{"depth":3,"text":"The embedded account","anchor":"the-embedded-account"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The identity module is the turnkey authorization spine for Citrate Network. It lets an app sign a user in\nagainst `auth.citrate.ai`, verify the resulting token safely, give that user a Citrate Keyring account they own\nwithout ever handling a private key, and read their entitlement, all from one typed surface. It ships in both\nthe TypeScript SDK (`@citratelabs/sdk`, the `identity` namespace) and the Python SDK (`citrate_sdk.identity`),\nwith the same behavior and the same account address on both.\n\n## What it is\n\nThree things sit behind the module, and they compose:\n\n- **Sign-in.** OIDC Authorization Code flow with PKCE, and Sign-In With Ethereum (EIP-4361). Either way you\n get an ID token that the SDK verifies before you trust a single claim.\n- **The embedded account.** An OIDC subject deterministically owns a counterfactual ERC-4337 Citrate Keyring\n account. The SDK computes that address locally and can verify it against the on-chain factory. You never see\n or hold a key.\n- **Claims and capabilities.** After sign-in, `userInfo` returns the subject, account address, KYC status, and\n a normalized entitlement tier with a capability set (see [entitlements](/sdks/entitlements)).\n\nEndpoints, scopes, chain addresses, and the entitlement claim URI are all read from the generated federation\ncontract, so nothing is hand-typed and nothing goes stale against a chain reroll.\n\n## How to use it\n\n### Sign in with OIDC (PKCE)\n\n```typescript\nimport { identity } from '@citratelabs/sdk';\n\nconst client = new identity.IdentityClient({\n clientId: 'your-app',\n redirectUri: 'http://127.0.0.1:8899/auth/callback',\n});\n\n// 1. Send the user to the authorize URL; keep the PKCE verifier.\nconst { url, pkce } = client.authorizeUrl({ state, nonce });\n// ...redirect the browser to `url`, receive `code` on the callback...\n\n// 2. Exchange the code. The returned ID token is already verified.\nconst tokens = await client.exchangeCode({ code, codeVerifier: pkce.verifier, nonce });\nconsole.log(tokens.claims.sub);\n```\n\nIn Python the shape is identical:\n\n```python\nfrom citrate_sdk.identity import IdentityClient\n\nclient = IdentityClient(client_id=\"your-app\", redirect_uri=\"http://127.0.0.1:8899/auth/callback\")\nurl, pkce = client.authorize_url(state=state, nonce=nonce)\ntokens = client.exchange_code(code=code, code_verifier=pkce.verifier, nonce=nonce)\n```\n\n### The embedded account\n\nAn app never derives the address from the authority's `/aa/address` endpoint. It computes the address locally\nand verifies it against the factory, which is the deployer and therefore the only ground truth.\n\n```typescript\nimport { identity } from '@citratelabs/sdk';\n\nconst userId = identity.uuidToUserId(tokens.claims.sub); // keccak256(utf8(lowercase uuid))\nconst address = identity.predictWalletAddress(userId); // local, offline\n\n// Where an RPC is available, confirm against the on-chain factory:\nconst confirmed = await identity.verifyWalletAddressOnChain(userId, provider);\n```\n\nTo stand the account up, request a factory deploy permit. The authority signs it with its identity-signer, and\nthe SDK never signs.\n\n```typescript\nconst permit = await client.requestDeployPermit({ userId, initData, expiresAt }, tokens.accessToken);\n```\n\n### Read claims and capabilities\n\n```typescript\nconst info = await client.userInfo(tokens.accessToken);\ninfo.tier; // normalized: unknown values collapse to \"public\"\ninfo.capabilities.ecosystemTx; // what this principal may do\ninfo.walletAddress; // the Keyring account, if the claim carried one\n```\n\n## Reference\n\nEach name below is exported from `@citratelabs/sdk` (`identity` namespace) and mirrored in\n`citrate_sdk.identity`.\n\n### Sign-in\n\n| Name | What it does |\n|---|---|\n| `IdentityClient(config)` | The client. Config: `clientId`, `redirectUri`, optional `scopes`, injectable `fetch`. |\n| `discover()` | Fetch and cache the OIDC discovery document; the issuer is pinned to the artifact. |\n| `authorizeUrl({state, nonce, pkce?})` | Build the PKCE S256 authorize URL; returns the URL and the PKCE pair. |\n| `exchangeCode({code, codeVerifier, nonce?})` | Exchange a code for tokens; the ID token is verified before return. |\n| `refresh(refreshToken)` | Rotate tokens with a refresh token. |\n| `siweChallenge(address)` / `siweVerify({message, signature})` | EIP-4361 sign-in with a single-use nonce. |\n| `userInfo(accessToken)` | Fresh claims plus a normalized tier and capability set. |\n| `logout(accessToken)` | End the session; fires the cross-instance revocation cascade. |\n\n### ID-token verification\n\n`verifyIdToken(token, { issuer, audience, jwks })` is the trust boundary and is called for you by\n`exchangeCode` and `refresh`. It accepts only `RS256`, verifies the signature before reading any claim, and\nrejects `alg:none`, algorithm confusion, a wrong audience or issuer, an expired or not-yet-valid token, a\ntampered payload, and a token whose `kid` matches no key.\n\n### The embedded account\n\n| Name | What it does |\n|---|---|\n| `uuidToUserId(uuid)` | `keccak256(utf8(lowercase uuid))`, the 32-byte AA userId for a UUID subject. |\n| `addressToUserId(address)` | Left-pad a 20-byte EOA to a 32-byte userId (SIWE subjects). |\n| `predictWalletAddress(userId, opts?)` | The counterfactual Citrate Keyring address. Pure and offline. |\n| `verifyWalletAddressOnChain(userId, provider, opts?)` | Verify the local prediction against the factory; throws on mismatch. |\n| `requestDeployPermit({userId, initData, expiresAt}, accessToken)` | Ask the authority to sign a factory deploy permit. |\n| `listValidators(userId)` / `guardianConfig(accessToken)` | Read installed validators and the guardian nomination. |\n\n## Design rationale\n\nThe module holds no keys and verifies every token, so a claim is never trusted before its signature. Account\naddress prediction is duplicated byte-for-byte across the TypeScript SDK, the Python SDK, the Rust `wallet-aa`\ncrate, and the on-chain factory, on purpose: the address a user funds must be identical no matter which surface\ncomputes it. The SDK computes it locally and checks it against the factory rather than trusting a service,\nbecause a service can drift out of sync with the chain while still answering confidently.\n\n## Failure modes\n\nThis surface guards money and identity, so it fails closed.\n\n- `verifyIdToken` throws on any verification failure. There is no best-effort path; an unverifiable token is\n rejected.\n- `predictWalletAddress` throws on a malformed userId, and `verifyWalletAddressOnChain` throws if the local\n address and the on-chain factory disagree, with a do-not-fund message. Prefer the on-chain check before\n showing a deposit address to a user.\n- The client is fail-closed without a `fetch` implementation, and the module never returns or logs a private\n key, seed, or bearer secret.\n\n## Access and canon\n\nPublic. This is open SDK reference and carries no secrets. Access tokens, refresh tokens, and gateway keys are\nthe caller's to hold; the module never persists them. The hostnames named here, `auth.citrate.ai` and\n`rpc.citrate.ai`, are public production endpoints, not credentials.\n\n## Source and verification\n\n- Source repos: `citrate-sdk-js` (`src/identity/`), `citrate-sdk-python` (`citrate_sdk/identity/`).\n- Zero new runtime dependencies: TypeScript uses `node:crypto` and `ethers`; Python uses `cryptography`,\n `eth_utils`, and `requests`.\n- Status: Implemented (pre-audit), unit-tested including the OIDC attack rejections and the on-chain\n account-address parity vector.\n"},"/sdks/inference-gateway":{"slug":"/sdks/inference-gateway","title":"Citrate Inference Gateway","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-inference-gateway/gateway/src/, citrate-inference-gateway/crates/x402-axum/src/","syncedSha":"603fe92","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"OpenAI-compatible client pattern","anchor":"openai-compatible-client-pattern"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"REST routes (`API-GW-rest`)","anchor":"rest-routes-api-gw-rest"},{"depth":3,"text":"Model name resolution, a current limit","anchor":"model-name-resolution-a-current-limit"},{"depth":3,"text":"x402 payment (`API-GW-x402`)","anchor":"x402-payment-api-gw-x402"},{"depth":3,"text":"Configuration, environment variable names only","anchor":"configuration-environment-variable-names-only"},{"depth":3,"text":"Examples","anchor":"examples"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":3,"text":"Known limits at this SHA","anchor":"known-limits-at-this-sha"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The Citrate Inference Gateway is an OpenAI-compatible HTTP service in front of Citrate Market on the\nCitrate Network, chain id 40204. You point an existing OpenAI client at it, call the standard `/v1/*`\nroutes, and the gateway selects a provider, meters the work, and settles payment per request. This page is\nfor application developers and integrators who want to run inference against the marketplace without\nwriting new client code.\n\n## What it is\n\nThe gateway speaks the OpenAI REST shape, so an existing client works by changing one setting, its base\nURL. Behind that familiar surface it resolves the requested model against on-chain registries, picks a\nprovider, dispatches the call, counts tokens, and settles the cost. Settlement uses x402, an HTTP `402`\npayment handshake described below and shared with the [Marketplace SDK](/sdks/marketplace#x402).\n\nThe service runs in one of two modes, selected at boot by `CITRATE_GATEWAY_MODE`.\n\n- **Marketplace mode**, the default. The gateway reads the on-chain `ModelRegistry`,\n `ComputePricingOracle`, and `InferenceRouter`, selects a provider for the requested model, and gates paid\n routes behind x402. This is the mode this page documents.\n- **Local-proxy mode.** A leaner build that fronts a resident inference server, for example a llama-server\n on a single machine, gated by a `cgk_` API key rather than x402, with no chain calls. It exists so a\n single-node deployment can serve the same OpenAI surface; it is named here for completeness and is not\n the marketplace surface.\n\nThe two read paths fit Citrate Market as follows. A model's price comes from the\n[x402 pricing contract](/contracts/x402) and the pricing oracle; the provider that runs the work is one of\nthe operators selling [compute](/compute/pool). The gateway is the thin layer that turns an OpenAI request into\na metered, paid marketplace job and an OpenAI response.\n\nThere are two documented surfaces:\n\n- **`API-GW-rest`**, tier public, the OpenAI-compatible routes and their request and response shapes, what\n a developer needs to call it.\n- **`API-GW-x402`**, tier commercial, the x402 payment handshake that gates the paid routes, shared with\n the [Marketplace SDK](/sdks/marketplace#x402).\n\n## How to use it\n\n1. Set your OpenAI client's base URL to the gateway, `https:///v1`. The API key field is not\n used for payment on the paid routes; x402 settles those.\n2. List the available models with `GET /v1/models`. This route is free and needs no payment. The `id` it\n returns is what you pass back as the `model` field.\n3. For a chat completion, send `POST /v1/chat/completions` with the standard OpenAI body. The first attempt\n without a payment header returns an HTTP `402` with a payment challenge.\n4. Sign the challenge and resend. The plain OpenAI client does not produce a payment header, so use the\n [Marketplace SDK `X402Client`](/sdks/marketplace#x402), which catches the `402`, signs, and retries for\n you.\n5. To see what you have spent, call `GET /v1/usage` with your API key as a bearer token.\n\n### OpenAI-compatible client pattern\n\nBecause the routes match the OpenAI shape, the official SDKs work by overriding the base URL. The free\nroutes work straight away; the paid routes need the x402 client.\n\n```python\nfrom openai import OpenAI\n\nclient = OpenAI(base_url=\"https:///v1\", api_key=\"not-used-for-x402\")\nprint(client.models.list()) # GET /v1/models, free, no payment\n```\n\n```typescript\nimport OpenAI from \"openai\";\nconst client = new OpenAI({ baseURL: \"https:///v1\", apiKey: \"unused\" });\nawait client.models.list(); // GET /v1/models, free\n```\n\n## Reference\n\n### REST routes (`API-GW-rest`)\n\nHandlers live under `gateway/src/`. The gateway listens on `127.0.0.1:9800` by default\n(`CITRATE_GATEWAY_LISTEN_ADDR`), and production runs behind a TLS reverse proxy.\n\n| Route | Method | Handler | Auth |\n|---|---|---|---|\n| `/v1/chat/completions` | POST | `gateway/src/chat.rs` | x402, paid |\n| `/v1/batch` | POST | `gateway/src/batch.rs` | x402, paid |\n| `/v1/batch/{id}` | GET | `gateway/src/batch.rs` | submitter-bound read |\n| `/v1/batch/{id}/output` | GET | `gateway/src/batch.rs` | submitter-bound read |\n| `/v1/models` | GET | `gateway/src/models.rs` | free |\n| `/v1/usage` | GET | `gateway/src/usage.rs` | API-key bearer |\n| `/health` | GET | `gateway/src/health.rs` | free liveness |\n| `/metrics` | GET | `gateway/src/metrics.rs` | Prometheus exposition |\n\n`POST /v1/chat/completions` (`gateway/src/chat.rs`) takes the OpenAI `ChatCompletionRequest`,\n`{ model, messages, max_tokens?, stream? }`, and returns a `ChatCompletionResponse` with `choices` and a\n`usage` block. It streams over server-sent events when `stream` is true. The handler resolves the model,\nquotes the cost on chain, dispatches to a selected provider with up to three failover attempts, and\nverifies the provider's signed result before returning it. `max_tokens` is clamped to a ceiling,\n`CITRATE_GATEWAY_MAX_TOKENS`, default 8192; an unset value falls back to a per-request default of 512.\n\n`POST /v1/batch` (`gateway/src/batch.rs`) takes `{ requests: [ChatCompletionRequest, ...] }` up to a\nmaximum of 1000 and returns a batch record with a `batch_id` and a `status` in the set\n`submitted | running | completed | partial_failure | failed`. A detached processor walks the batch through\nits states, dispatches each request, and settles the escrow at the end, releasing the cost of completed\nslots and refunding the cost of failed ones. The status and output reads are bound to the submitter.\n\n`GET /v1/models` (`gateway/src/models.rs`) reads the on-chain `ModelRegistry` and the compute pools and\nreturns `{ object: \"list\", data: [...] }`. It includes individual active models and pools, with pool ids\nprefixed `pool-`. Inactive models are filtered out.\n\n`GET /v1/usage` (`gateway/src/usage.rs`) requires an API-key bearer token and returns totals and a daily\nbreakdown of requests, tokens, and SALT spent. The accounting is held in process memory at this SHA;\ndurable storage of usage is planned for a later slice. Status for durable usage: Specified.\n\n### Model name resolution, a current limit\n\nThe gateway resolves a model identifier to an on-chain hash in `gateway/src/queries.rs`. A caller may pass\na pinned 32-byte hash directly, which always works. A caller may also pass a friendly name, which the\ngateway resolves by enumerating the registry, since the on-chain `ModelRegistry` does not expose a\nname-to-hash view, the hash being derived from creator, name, timestamp, and a nonce. A dedicated\n`getModelByName` view, or an off-chain name registry, is the intended way to close this gap. Status for the\nname view: Specified.\n\n### x402 payment (`API-GW-x402`)\n\nPaid routes sit behind the `X402Layer` middleware (`crates/x402-axum/src/layer.rs`). The handshake is\nimplemented end to end; settlement is a real on-chain transaction, not a mock.\n\n1. The client `POST`s without an `x-payment` header.\n2. The gateway returns HTTP `402` with a payment challenge, `{ version, facilitator, token (the wSALT\n address), chain_id (40204), amount (wei as a decimal string), nonce, valid_after, valid_before,\n recipient, digest }`. The challenge has a limited lifetime, and the nonce is single-use, minted and\n tracked by this gateway.\n3. The client signs the EIP-712 `transferWithAuthorization` digest, builds a payment payload, encodes it as\n URL-safe base64 without padding, and resends with the `x-payment` header.\n4. The gateway verifies the signature and the nonce, checks the recipient binds to the configured treasury,\n settles `X402Facilitator.settlePayment` on chain, waits for the receipt, and runs the handler, returning\n the OpenAI-shaped response.\n\nThis is the server side of the [Marketplace SDK x402 codec](/sdks/marketplace#x402). Use that client rather\nthan re-implementing the codec.\n\n### Configuration, environment variable names only\n\nThe gateway reads its configuration from environment variables. Names and purposes follow; no values are\nshown. `CITRATE_GATEWAY_MODE` (`marketplace` or `local-proxy`), `CITRATE_GATEWAY_CHAIN_ID` (default 40204),\n`CITRATE_GATEWAY_RPC_URL`, `CITRATE_GATEWAY_LISTEN_ADDR` (default `127.0.0.1:9800`),\n`CITRATE_GATEWAY_MODEL_REGISTRY`, `CITRATE_GATEWAY_PRICING_ORACLE`, `CITRATE_GATEWAY_INFERENCE_ROUTER`\n(contract addresses), `CITRATE_GATEWAY_KEYSTORE_PATH` (the durable store, required in production),\n`CITRATE_GATEWAY_DEV_MODE`, `CITRATE_GATEWAY_OPEN_CHAT`, `CITRATE_GATEWAY_MAX_TOKENS`, and the operator\nsigner family (`CITRATE_GATEWAY_OPERATOR_KEYSTORE*`, `CITRATE_GATEWAY_KMS_KEY_ID`, and the spend-cap\nvariables). For local-proxy mode, `CITRATE_GATEWAY_UPSTREAM_URL` lists one or more upstreams, tried in\norder. Observability uses `LOG_FORMAT` and `RUST_LOG`. The repository `gateway/RUNBOOK.md` is the full\noperator reference.\n\n### Examples\n\n```bash\n# Free: list models, no payment.\ncurl -s https:///v1/models\n```\n\n```bash\n# Paid route without payment returns a 402 challenge.\n# Then sign and retry; use the SDK X402Client.\ncurl -s -X POST https:///v1/chat/completions \\\n -H 'content-type: application/json' \\\n -d '{\"model\":\"\",\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}]}'\n# HTTP 402 with a payment challenge body\n```\n\n## Design rationale\n\nThe gateway speaks the OpenAI shape on purpose. A developer who already has working code should be able to\nmove to Citrate Market by changing a base URL, not by learning a new client, and the OpenAI compatibility\nis a deliberate public good. The payment surface is the part that differs, and it is kept behind one\nmiddleware so the rest of the service reads as an ordinary inference proxy. Settlement happens per request\nrather than against a held balance, so a caller pays for the work it asks for and nothing is escrowed\nbeyond a single request or batch.\n\n## Failure modes\n\nThe paid surface is where the gateway is security relevant, and it fails closed.\n\n- **No payment.** A paid route without a valid `x-payment` header returns `402`, never the work.\n- **Bad or replayed payment.** The gateway verifies the signature, checks the nonce is one it minted and\n has not seen, checks the payment window, and binds the recipient to the configured treasury. A nonce is\n single-use, so a replayed payment is rejected before it touches the chain.\n- **Settlement revert.** If the on-chain settlement transaction reverts, the gateway returns `402` with the\n transaction hash rather than running the handler.\n- **Oversized request.** `max_tokens` is clamped to the ceiling before pricing or dispatch, and a batch\n larger than 1000 requests is rejected, so a caller cannot price a request small and then demand a large\n response.\n- **Open chat in production.** The unauthenticated chat path is gated behind two development flags,\n `CITRATE_GATEWAY_OPEN_CHAT` and `CITRATE_GATEWAY_DEV_MODE`, and the gateway refuses to enable it on a\n non-loopback bind, so the open path cannot be left exposed by accident.\n- **Provider result.** A provider result is accepted only if it is non-empty and carries a valid binding\n signature; a failed verification moves to the next provider in the failover loop.\n\n### Known limits at this SHA\n\nSome pieces are designed and partly built but not yet complete. Pool dispatch is selected by the scoring\nlogic but returns a `503` when a pool wins, the per-provider path being the one that runs today; pool\ndispatch is a later slice. Durable storage of usage accounting is in process memory at this SHA. On-chain\njob posting per request, rather than direct dispatch to the provider, and streaming directly from a\nprovider, are not yet wired. Status for these pieces: Specified for pool dispatch and durable usage,\nTheoretical for on-chain per-request posting and provider streaming.\n\n## Access and canon\n\nThe REST surface, `API-GW-rest`, is public; it is the open API a developer needs to integrate, and the\nOpenAI compatibility is a deliberate public good. The x402 handshake, `API-GW-x402`, is commercial,\npayment-integration depth shared with contracted builders and gated from anonymous scraping.\n\nNo API keys or secrets appear here. Examples use a placeholder host and an unused key value, since x402\nsettles payment rather than a bearer key. Operator account material is loaded from a keystore or KMS and is\nnever documented; the repository source contains no hardcoded credentials at the audited SHA.\n\n## Source and verification\n\n- Source: `citrate-inference-gateway`. Routes and handlers in `gateway/src/` (`chat.rs`, `batch.rs`,\n `models.rs`, `usage.rs`, `queries.rs`, `health.rs`, `metrics.rs`, `config.rs`, `main.rs`, `lib.rs`); the\n payment middleware in `crates/x402-axum/src/` (`layer.rs`, `challenge.rs`, `client.rs`). Operator detail\n in `gateway/RUNBOOK.md`.\n- Audited against SHA: `603fe92`.\n- Status: Implemented (pre-audit). The REST routes, on-chain read queries, provider dispatch with failover,\n and the full x402 settlement path exist and run; this slice has not had an external audit. Specified:\n pool dispatch and durable usage accounting. Theoretical: on-chain per-request job posting and streaming\n from a provider.\n"},"/sdks/js":{"slug":"/sdks/js","title":"Citrate JavaScript SDK","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-sdk-js/src/index.ts","syncedSha":"2f8da46","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"Client, `src/client/CitrateClient.ts`","anchor":"client-srcclientcitrateclientts"},{"depth":3,"text":"Account abstraction, `src/aa/`","anchor":"account-abstraction-srcaa"},{"depth":3,"text":"Cryptography, `src/crypto/`","anchor":"cryptography-srccrypto"},{"depth":3,"text":"React hooks, `src/react/hooks.ts`","anchor":"react-hooks-srcreacthooksts"},{"depth":3,"text":"Constants and errors","anchor":"constants-and-errors"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"`@citratelabs/sdk` is the canonical TypeScript SDK for building on Citrate Network. It wraps the chain's JSON-RPC,\nthe model and inference operations, and the account-abstraction stack into a typed API, so a Node or browser\napp can talk to Citrate without hand-rolling calldata. This page is for the developer writing that app.\n\n## What it is\n\nThe SDK is the typed front door to Citrate Network from JavaScript and TypeScript. The package is named\n`@citratelabs/sdk` (formerly `@citratelabs/citrate-js`, retained as a deprecated alias) and the version of record is\n`0.2.0` in `package.json`. It is the canonical SDK; the other language SDKs follow it and stay non-canonical\nuntil a pilot integrator needs parity.\n\nSeveral surfaces sit behind one import, and most apps only ever touch the first:\n\n- The client, `CitrateClient` and `WebSocketClient`, for reading chain state, deploying models, and running\n inference against the chain (`src/client/`).\n- The account-abstraction helpers, exported under the `aa` namespace, for Citrate Keyring accounts built on\n Kernel v3 and ERC-4337 v0.7: counterfactual address derivation, passkey and EOA signing, UserOperation\n building, guardian recovery, and a bundler client (`src/aa/`).\n- The **identity spine and embedded Keyring account**, exported under the `identity` namespace: OIDC PKCE and\n SIWE sign-in, hardened ID-token verification, and smart-account address prediction verified against the\n on-chain factory. See [identity and the embedded Keyring account](/sdks/identity).\n- The **entitlement capabilities**, exported under the `entitlements` namespace: the canonical `normalizeTier`\n and capability map. See [entitlements](/sdks/entitlements).\n- The **inference gateway client**, exported under the `gateway` namespace: an OpenAI-compatible client for\n `infer.citrate.ai`.\n- The **memory client**, exported under the `memory` namespace: a typed client for a `citrate-memories`\n gateway (`src/memory/client.ts`), with `MemoryClient` over the OIDC REST surface (recall, search, neighbors,\n verify, review, assert) and `ByomMemoryClient` for the bring-your-own-model MCP path.\n- The cryptography utilities, `CryptoManager`, `KeyManager`, and Shamir secret sharing (`src/crypto/`).\n- The optional React hooks (`src/react/hooks.ts`), which are not re-exported from the package root.\n\nThe mental model is two layers. Reach for `CitrateClient` when you hold a key and want to read or write the\nchain directly. Reach for `aa` when you want a Citrate Keyring account that a passkey controls and a paymaster\ncan sponsor, instead of a raw key-pair account. Passkeys are covered under [passkeys](/aa/passkeys) and\nsponsorship under [the paymaster](/aa/paymaster).\n\nThe `aa` module is labeled EW-S1 WP-7 in source and points at infrastructure that is live but still moving\n(`bundler.citrate.ai`, `auth.citrate.ai`). Treat it as in development. The exported `VERSION` constant now\nreads `0.2.0` (`src/index.ts`), matching `package.json`.\n\n## How to use it\n\nThe package targets Node 16 and newer (`engines.node`), and runs in modern browsers through the Web Crypto\nAPI.\n\n1. Install the package.\n\n ```bash\n npm install @citratelabs/sdk\n ```\n\n The runtime dependencies are `ethers ^6.17`, `axios ^1.20`, and `eventemitter3 ^5.0`. The React hooks need\n `react >=16.8` and `react-dom >=16.8`, which are optional peer dependencies; install them only if you use\n the hooks.\n\n2. Construct a client. The defaults for testnet, chain id `40204`, live in `src/utils/constants.ts`.\n\n ```typescript\n import { CitrateClient, CHAIN_IDS, DEFAULT_RPC_URLS } from '@citratelabs/sdk';\n\n const client = new CitrateClient({\n // DEFAULT_RPC_URLS[40204] is ['https://rpc.citrate.ai']. An array\n // enables fallback across endpoints; a bare string is the single-RPC form.\n rpcUrl: DEFAULT_RPC_URLS[CHAIN_IDS.TESTNET],\n // privateKey: process.env.CITRATE_PRIVATE_KEY, // optional; from env, never inline\n });\n ```\n\n The constructor validates every RPC URL and any private key before it builds a provider, so a typo fails\n immediately with a `ValidationError` rather than at first use.\n\n3. Read chain state. Read methods need no key.\n\n ```typescript\n console.log(await client.getChainId()); // 40204\n console.log((await client.getBalance('0xYourAddress'))); // bigint, wei\n ```\n\n4. Write to the chain. Methods that send a transaction, `deployModel`, `inference`, and `batchInference`,\n require a `privateKey` in the config. Without one they throw. To send a sponsored write through a Citrate\n Keyring account instead of a raw key, build a UserOperation with the `aa` helpers and submit it through the\n bundler client; the [first-app tutorial](/sdks/tutorials/first-app-with-sdk-js) walks the whole path.\n\nFor a step-by-step build, follow [build your first app](/sdks/tutorials/first-app-with-sdk-js).\n\n## Reference\n\nEach item names its export and source path in `citrate-sdk-js` so a reader can check it against the code.\n\n### Client, `src/client/CitrateClient.ts`\n\nThe constructor takes a `CitrateClientConfig`: `rpcUrl` as a string or string array, and optional\n`privateKey`, `timeout`, `retries`, `headers`, and `ipfsApiUrl`. Exported from `src/index.ts` alongside\n`WebSocketClient` (`src/client/WebSocketClient.ts`) for streaming and subscriptions.\n\n| Method | Signature | What it does |\n|---|---|---|\n| `getRpcUrls()` | `(): readonly string[]` | The current fallback list, primary first. |\n| `getChainId()` | `(): Promise` | The chain id reported by the provider. |\n| `getBalance(address?)` | `(): Promise` | Native balance in wei. |\n| `getNonce(address?)` | `(): Promise` | Pending transaction count. |\n| `getAddress()` | `(): string \\| undefined` | The configured account address, if a key was given. |\n| `deployModel(modelData, config)` | `(): Promise` | Deploy a model artifact. Needs a key. |\n| `inference(request)` | `(): Promise` | One inference call. Needs a key. |\n| `batchInference(request)` | `(): Promise` | Batched inference. Needs a key. |\n| `getModelInfo(modelId)` | `(): Promise` | Model metadata, via `citrate_getModel`. |\n| `listModels(owner?, limit=100)` | `(): Promise` | List models, via `citrate_listModels`. |\n| `purchaseModelAccess(modelId, amount)` | `(): Promise` | Disabled, fails closed. See failure modes. |\n\n### Account abstraction, `src/aa/`\n\nImported as a namespace, `import { aa } from '@citratelabs/sdk'`; the module index is `src/aa/index.ts`. The flow\nit documents is: derive a userId, predict the address, enroll a validator, then build, sign, and send a\nUserOperation. The market side of this is covered in [the marketplace SDK](/sdks/marketplace).\n\n- Address derivation (`src/aa/address.ts`): `uuidToUserId`, `accountIdToAaUserId`, `predictWalletAddress`,\n `erc1967MinimalInitCodeHash`, and `AddressPredictionError`. The derivation is the cross-surface seam that\n gives one user the same account address on every device. `uuidToUserId` is `keccak256(utf8(lowercase\n uuid))`; `predictWalletAddress(factory, implementation, userId)` returns the CREATE2 address the factory\n deploys the Kernel proxy to.\n- Kernel v3 encoding (`src/aa/kernel.ts`): nonce helpers `rootValidatorNonce`, `validatorNonceKey`, and\n `composeNonce`; `encodeExecuteSingle` and `encodeExecuteBatch`; module management\n `encodeInstallModule`, `encodeUninstallModule`, and `encodeChangeRootValidator`; and the install-data\n builders `kernelInitializeCalldata`, `webauthnInstallData`, `ecdsaInstallData`, and `guardianInstallData`.\n The guardian builder enforces a count in [2, 7] and a threshold in [1, count].\n- UserOperation building (`src/aa/userop.ts`): `buildPackedUserOp`, `getUserOpHash`, `encodeDeployFor`,\n `packInitCode`, `packCitratePaymasterAndData`, `toRpcUserOperation`, and the gas-packing helpers\n `packAccountGasLimits` and `packGasFees`. `getUserOpHash` is verified against the live EntryPoint v0.7 on\n chain 40204.\n- Passkey signing (`src/aa/webauthn.ts`): `signUserOpWithPasskey` drives the browser's\n `navigator.credentials.get()`; the pure encoders `encodeWebauthnValidatorSignature`,\n `parseDerEcdsaSignature`, `normalizeP256S`, and `base64UrlEncode` are testable without a browser. The\n validator rejects high-s signatures, so `normalizeP256S` folds `s` into the lower half of the curve order.\n- EOA signing (`src/aa/eoa.ts`): `signUserOpWithEoa(signer, userOpHash)` signs with any ethers `Signer` and\n produces the EIP-191 shape the ECDSA validator accepts.\n- Guardian recovery (`src/aa/recovery.ts`): `guardianRecoveryDigest`, `packGuardianSignatures`,\n `buildRotateSignerCall`, and `RecoveryError`. The recovery op rotates the account's root validator toward a\n fresh passkey, validated by M-of-N guardian signatures bound to the account.\n- Bundler client (`src/aa/bundler.ts`): `BundlerClient` with `sendUserOperation`, `estimateUserOperationGas`,\n `getUserOperationReceipt`, `waitForUserOperationReceipt`, `supportedEntryPoints`, and `chainId`. The default\n endpoint is `CITRATE_BUNDLER_URL`, `https://bundler.citrate.ai/rpc`, a public RPC, not a secret. Errors\n surface the bundler's `AAxx` codes through `BundlerRpcError`.\n- Types (`src/aa/types.ts`): `PackedUserOperation`, `RpcUserOperation`, `UserOperationReceipt`,\n `CitrateAaConfig`, and the `PaymasterCategory` enum (`Standard`, `Recovery`, `FirstOp`).\n\n### Cryptography, `src/crypto/`\n\n- `CryptoManager` (`src/crypto/CryptoManager.ts`): SHA-256 hashing, AES-256-GCM, and PBKDF2 over the Web\n Crypto API. The default work factor is `PBKDF2_DEFAULT_ITERATIONS`, 600,000 iterations.\n- `KeyManager` (`src/crypto/KeyManager.ts`): key handling and model encryption, returning\n `EncryptedModelResult`. Encryption ECDH-wraps the symmetric key to the recipient. Options include\n `accessControl` (default `true`) and threshold key sharing (`thresholdShares`, `totalShares`).\n- Shamir secret sharing (`src/crypto/FiniteField.ts`): `splitSecretBytes`, `reconstructSecretBytes`, `GF256`,\n and `ShamirSecretSharing`. Share coefficients are drawn from a cryptographic RNG and fail closed if none is\n available.\n\n### React hooks, `src/react/hooks.ts`\n\nThe hooks are not re-exported from the package root, because React is an optional peer dependency. Import them\nfrom the build path; each hook throws if React is not installed.\n\n```typescript\nimport { useCitrateClient } from '@citratelabs/sdk/react/hooks';\n```\n\nThe hooks are `useCitrateClient`, `useModelDeployment`, `useInference`, `useModelInfo`, and `useModelList`.\n\n### Constants and errors\n\n- `src/utils/constants.ts`: `CHAIN_IDS` (`TESTNET: 40204`; releases up to 0.2.x also carry `MAINNET: 1`, which is Ethereum mainnet's chain id, not Citrate's, so do not use it: Citrate's network is 40204 and mainnet keeps that id), `DEFAULT_RPC_URLS`,\n `DEFAULT_WS_URLS`, `PRECOMPILE_ADDRESSES`, `GAS_LIMITS`, `TIMEOUTS`, `MODEL_LIMITS`, `ENCRYPTION`, `EVENTS`,\n and `API_ENDPOINTS`.\n- `src/errors/CitrateError.ts`: `CitrateError`, `ModelNotFoundError`, `InsufficientFundsError`, and\n `ValidationError`.\n\n## Design rationale\n\nThe client holds the RPC fallback list and rotates through it on transport errors rather than wrapping\nethers' automatic failover, which would double the connection budget. The trade is a little manual rotation\nin exchange for a predictable connection count. The `aa` helpers are written as pure functions: chain reads,\nnonces and deploy status, are passed in as arguments, so the same code that runs in a browser against\n`auth.citrate.ai` and the bundler also runs against pinned test vectors. Address derivation is duplicated\nbyte-for-byte across the JavaScript, identity-TS, and Rust implementations on purpose, because the address a\nuser controls must be identical no matter which surface computes it.\n\n## Failure modes\n\nThis is the surface where a mistake costs money or leaks data, so several methods fail closed.\n\n- A write method called without a configured key throws rather than silently doing nothing. `deployModel`,\n `inference`, and `batchInference` all require `privateKey` in the config.\n- When a model deployment or inference asks for encryption but no key manager is configured, the call throws\n rather than uploading in plaintext.\n- `purchaseModelAccess` is disabled and throws. The canonical precompile table has no access-purchase\n operation; the earlier implementation routed buyer funds to the verification precompile, where access was\n never granted and value was never credited. It stays closed until a node-confirmed precompile exists.\n- Shamir share generation throws if no cryptographic RNG is present, because the scheme's secrecy depends on\n unpredictable coefficients.\n- A bundler rejection surfaces as a `BundlerRpcError` carrying the `AAxx` code verbatim, for example `AA21`\n for an unfunded prefund or `AA31` for a paymaster deposit too low, so the caller can act on the real cause.\n\n## Access and canon\n\nPublic. This is open SDK reference a developer needs to build on Citrate Network, and it carries no secrets.\nPrivate keys, mnemonics, and bundler API keys are never inline; they come from the caller's environment\n(`config.privateKey`, `BundlerClientOptions.apiKey`). The hostnames named here, `rpc.citrate.ai`,\n`bundler.citrate.ai`, and `auth.citrate.ai`, are public production endpoints already shipped as defaults in\nthe source, not credentials. The SDK holds no identity data.\n\n## Source and verification\n\n- Source repo: `citrate-sdk-js`, package `@citratelabs/sdk@0.2.0` (`package.json`; `@citratelabs/citrate-js` retained as a deprecated alias).\n- Audited against SHA: `2f8da46`.\n- Audited paths: `src/index.ts`, `src/client/CitrateClient.ts`, `src/client/WebSocketClient.ts`,\n `src/aa/{index,address,kernel,userop,webauthn,eoa,recovery,bundler,types}.ts`,\n `src/crypto/{CryptoManager,KeyManager,FiniteField}.ts`, `src/identity/`, `src/entitlements/capabilities.ts`,\n `src/gateway/client.ts`, `src/memory/client.ts`, `src/react/hooks.ts`, `src/utils/constants.ts`, and\n `src/errors/CitrateError.ts`.\n- Status: Implemented (pre-audit). The client and cryptography surfaces are built and run against testnet 40204.\n The `aa` module is Implemented but in development (EW-S1 WP-7) and depends on still-moving bundler and auth\n infrastructure. The `identity`, `entitlements`, `gateway`, and `memory` namespaces are Implemented and\n unit-tested; account-address prediction is verified byte-for-byte against the on-chain factory.\n"},"/sdks/marketplace":{"slug":"/sdks/marketplace","title":"Marketplace SDK","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-sdk-marketplace/src/index.ts","syncedSha":"5cc1f39","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"MarketplaceClient","anchor":"marketplaceclient"},{"depth":3,"text":"X402Client","anchor":"x402client"},{"depth":3,"text":"Account integration","anchor":"account-integration"},{"depth":3,"text":"ABIs and calldata builders","anchor":"abis-and-calldata-builders"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The TypeScript client for Citrate Market. It reads marketplace state, pays per inference request over the\nx402 path, signs from a Citrate Keyring or a browser-injected account, and builds the calldata for posting\ninference and training jobs and buying compute credits, all on chain id `40204`. For integrators and app\nbuilders working buyer-side.\n\n## What it is\n\n`@citratelabs/marketplace-sdk` (version `0.1.0`) wraps the on-chain compute marketplace into a typed API\nso a buyer-side app can list providers, estimate cost, pay for inference, and post jobs without hand-rolling\ncalldata. It is built on [viem](https://viem.sh) (`^2.56.0`, a direct dependency) and presents four surfaces:\n\n- `MarketplaceClient`, read-only marketplace queries (`src/client.ts`).\n- `X402Client`, an auto-pay-on-402 HTTP client and the x402 payment codec (`src/x402.ts`).\n- `CitrateWallet` and `InjectedSigner`, the account integration: a native passphrase keystore or a\n browser-injected signer (`src/wallet/`). In prose we call this the Citrate Keyring integration; the code\n symbols keep their historical names.\n- ABIs and calldata builders for jobs, credits, and training (`src/contracts.ts`, with `src/jobs.ts`,\n `src/credits.ts`, `src/training.ts`).\n\nThe mental model: read state with `MarketplaceClient`; pay for an inference request with `X402Client`; sign\nwith a `CitrateWallet` (key held in the browser) or an `InjectedSigner` (an extension such as MetaMask or\nRabby); and for on-chain actions, posting a job, buying credits, requesting training, build calldata with the\nbuilder functions and send it through your signer and viem. The compute marketplace it talks to is described\nunder [Citrate Market](/compute/pool) and [the compute contracts](/contracts/compute); the per-request payment\nhandshake is the [x402 contract path](/contracts/x402); account recovery and passkeys are covered under\n[accounts](/aa/identity).\n\nThe package classifies itself Tier 1 in `AUDIT_TIER.md`: a full external audit of its cryptographic\nprimitives, key handling, and supply chain is required before any `v1.0.0` stable release, and no stable\nrelease ships without written attestation against an exact commit. At `0.1.0` it is pre-audit. Treat every\nsurface as experimental, and note three known gaps for a later slice: model-name resolution (slice 1 accepts\npinned hashes only), event subscription on a posted job, and key export from the keystore.\n\n## How to use it\n\n1. Install the package and its peer. It is published to the public npm registry\n (`https://registry.npmjs.org/`, per `publishConfig` in `package.json`) and needs Node 20 or newer.\n\n ```bash\n npm install @citratelabs/marketplace-sdk viem\n ```\n\n2. Build a read-only client. Contract addresses for testnet (`40204`) are vendored, so `defaultAddresses()`\n returns a working set.\n\n ```ts\n import { createPublicClient, http } from \"viem\";\n import { MarketplaceClient, defaultAddresses } from \"@citratelabs/marketplace-sdk\";\n\n const publicClient = createPublicClient({ transport: http(\"\") });\n const market = new MarketplaceClient({ publicClient, addresses: defaultAddresses() });\n ```\n\n3. To pay per request, construct an `X402Client` with a signer and a hard cap. There is no uncapped mode;\n `maxPayWei` is required and the client signs at most once per request.\n\n ```ts\n import { X402Client, unlockWallet } from \"@citratelabs/marketplace-sdk\";\n\n const account = await unlockWallet(userPassphrase); // key stays in the browser\n const x402 = new X402Client({\n signer: account,\n chainId: 40204,\n maxPayWei: 10n ** 16n, // 0.01 SALT cap per request\n allowedTokens: [wsaltAddress],\n });\n\n const res = await x402.send(`${gatewayBaseUrl}/v1/chat/completions`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ model: \"\", messages: [{ role: \"user\", content: \"hi\" }] }),\n });\n ```\n\n4. To post a job, build calldata and send it with your signer. The end-to-end version is in\n [post a marketplace job](/sdks/tutorials/post-a-marketplace-job).\n\n## Reference\n\nVerified against `citrate-sdk-marketplace` at `5cc1f39`. The public surface is re-exported from\n`src/index.ts`.\n\n### MarketplaceClient\n\n`src/client.ts`. Constructed with `new MarketplaceClient(opts)` where `opts` is\n`{ publicClient: PublicClient; addresses?: MarketplaceAddresses }`. Read-only; it never holds keys, and\nexposes `publicClient` and `addresses` as readonly properties.\n\n| Method | Signature | Purpose |\n|---|---|---|\n| `resolveModelHash` | `(input: string) => Hex` | Validate a pinned `0x`+64-hex model hash. Slice 1 requires a full hash; there is no name lookup. |\n| `listProviders` | `(modelHash: Hex) => Promise` | Active providers for a model from `InferenceRouter`, with endpoint, stake, load, and lifetime inference count. |\n| `estimateCost` | `({ modelHash, inputTokens, outputTokens, tier }) => Promise` | Cost in grains via `ComputePricingOracle.estimateJobCost`. |\n| `getCreditBalance` | `(institution: Address) => Promise` | Compute-credit balance from `BulkComputeGateway` (18 decimals); `0n` if the gateway is unset. |\n| `fetchCreditsLogs` | `(institution, fromBlock, toBlock) => Promise` | Raw `CreditsPurchased` and `CreditsSpent` logs for a history view; parse them with `parseCreditsEvents`. |\n| `erc20Allowance` | `(token, owner, spender) => Promise` | ERC-20 allowance; `0n` on read failure. |\n| `erc20BalanceOf` | `(token, account) => Promise` | ERC-20 balance for form validation; `0n` on read failure. |\n| `minPurchaseUsd` | `() => Promise` | `MIN_PURCHASE_USD`, defaulting to `10_000_000` (= $10 at 6 decimals). |\n| `getProviderProfile` | `(address: Address) => Promise` | Full provider profile; `null` if unregistered. |\n\n`ProviderInfo` carries `address`, `endpoint`, `stake`, `currentLoad`, `totalInferences`, and `isActive`.\n`ProviderProfile` adds `isRegistered`, `totalJobsCompleted`, `totalJobsFailed`, `reputationScore` (basis\npoints), `currentActiveJobs`, and `maxConcurrentJobs`.\n\n### X402Client\n\n`src/x402.ts`. Constructed with `new X402Client(opts)`:\n\n```ts\nnew X402Client({\n signer, // Signer (required)\n maxPayWei, // bigint hard cap per request (required; no uncapped mode)\n chainId, // number (required)\n allowedTokens, // Address[] (required, at least one)\n allowedRecipients, // Address[] (optional payee pin)\n fetch, // optional fetch override, for tests\n});\n```\n\n`send(input, init?) => Promise` makes the request and, on an HTTP `402` carrying a valid challenge,\nsigns and retries exactly once. Policy is checked before signing: `chain_id` must match `chainId`; the token\nmust be in `allowedTokens`; the recipient must be in `allowedRecipients` if that list is set; the amount must\nbe at most `maxPayWei`; and `valid_before` must still be in the future. Any failure returns the unsigned\n`402` instead of paying.\n\nThe wire types and codec live in the same file: `PaymentChallenge` (the server's `402` body),\n`PaymentPayload` (the client's signed reply), the `Signer` and `TxSigner` interfaces, `signChallenge`,\n`encodePaymentHeader` and `decodePaymentHeader` (the `x-payment` header, URL-safe base64),\n`paymentToBytes` and `bytesToPayment` (a fixed 233-byte form, `PAYLOAD_BYTES`), and the EIP-712 digest\nhelpers `wsaltDomainSeparator`, `transferWithAuthorizationStructHash`, and `eip712Digest`. The payment is a\n`transferWithAuthorization` on the wrapped-SALT token; browser accounts must sign it via `signEip712`\n(`eth_signTypedData_v4`), not `personal_sign`, because the EIP-191 prefix `personal_sign` adds would break\nrecovery. The server side of this handshake is the [x402 contract path](/contracts/x402).\n\n### Account integration\n\n`src/wallet/`. The directory keeps its historical name; in prose this is the Citrate Keyring integration.\n\n- `CitrateWallet` (`src/wallet/citrate.ts`), a native keystore account. Factories: `createWallet(passphrase)`\n generates a 32-byte key, encrypts it under the passphrase, and persists it to `localStorage` (the\n passphrase must be at least 12 characters); `unlockWallet(passphrase)` reopens it. Instance methods:\n `sign({ hash })`, `sendTransaction(tx)` (which needs `connect(chain, rpcUrl?)` first), `lock()`, and the\n `unlocked` getter. Helpers: `peekKeystoreAddress()`, `hasStoredKeystore()`, and `clearKeystore()` (which\n is unrecoverable).\n- `InjectedSigner` (`src/wallet/injected.ts`), a browser-extension adapter. `InjectedSigner.connect(opts?)`\n requests accounts and asserts or switches the chain. It implements `sign` (`personal_sign`), `signEip712`\n (`eth_signTypedData_v4`, required for x402), and `sendTransaction`, and `hasInjectedProvider()` detects an\n injected provider. It re-checks the chain before every sign and send, closing a time-of-check window.\n- Keystore (`src/wallet/keystore.ts`), Web3 Secret Storage v3: `encryptKeystore` and `decryptKeystore`,\n AES-128-CTR with PBKDF2-SHA256 at 262144 iterations and a constant-time MAC check. The format is portable\n to geth and to other v3 readers.\n\n### ABIs and calldata builders\n\n`src/contracts.ts`, `src/jobs.ts`, `src/credits.ts`, `src/training.ts`. Exported ABIs:\n`computeMarketplaceAbi`, `inferenceRouterAbi`, `computePricingOracleAbi`, `bulkComputeGatewayAbi`,\n`computePoolTrainingAbi`, `erc20Abi`. Addresses and config: `CITRATE_TESTNET_CHAIN_ID` (`40204`),\n`TESTNET_ADDRESSES`, `defaultAddresses()`, and the `MarketplaceAddresses` interface. Enums: `PaymentMethod`\n(`SALT` = 0, `BulkCredits` = 1) and `VerificationTier` (`Commitment` = 0, `ZKProof` = 1 at 1.5×, `TEE` = 2\nat 2.0×). Error type: `MarketplaceError`.\n\n| Builder | Signature | Notes |\n|---|---|---|\n| `postJobCalldata` | `(args: PostJobArgs) => { data: Hex; inputHash: Hex }` | Inference job. `SALT` sends `value = maxPriceGrains`; `BulkCredits` sends `value = 0n`. |\n| `erc20ApproveCalldata` | `(args) => { data: Hex }` | Approve a stablecoin for the credits flow. |\n| `purchaseComputeCreditsCalldata` | `(args) => { data: Hex }` | Buy compute credits; `amount` must be at least `MIN_PURCHASE_USD`. |\n| `requestTrainingJobCalldata` | `(spec) => { data: Hex; requiredEscrow: bigint }` | `requiredEscrow = perEpochBudget × epochCount`. |\n| `joinTrainingJobCalldata`, `closeRecruitmentCalldata`, `commitEpochCalldata`, `challengeStepCalldata`, `reassignCoordinatorCalldata`, `voteChallengeCalldata`, `finalizeTrainingJobCalldata` | various | Training lifecycle calldata. |\n| `parseJobEvents`, `parseCreditsEvents`, `parseTrainingEvents` | `(logs: Log[]) => Event[]` | Decode receipt logs into typed events. |\n| `grainsToSalt`, `grainsToSaltDisplay` | `(grains: bigint) => string` | Display formatters; SALT has 18 decimals, and \"grains\" are its wei. |\n\n`PostJobArgs` carries `modelHash`, `input` (bytes or a CID; keccak256-hashed if bytes), `maxPriceGrains`,\n`tier`, `bidWindowBlocks`, `execWindowBlocks`, and an optional `paymentMethod`.\n\n## Design rationale\n\nThe split between calldata builders and a signer is deliberate. The builders are pure: they take typed\narguments and return `data` plus, where it matters, a derived value such as `inputHash` or `requiredEscrow`,\nand they never touch a key or a network. Signing and sending stay with the account integration, so a key\nlives in exactly one place. `X402Client` is built to be hard to misuse: `maxPayWei` is required so there is\nno path to an uncapped auto-pay, every policy field is checked before a signature exists, and the client\nretries a paid request at most once. The trade is verbosity. You assemble calldata and send it yourself\nrather than calling a single do-everything method, and in return the dangerous step is small, explicit, and\nauditable.\n\n## Failure modes\n\n- `X402Client` refuses to sign and returns the unsigned `402` if the challenge fails any policy check: wrong\n chain, an unallowed token or recipient, an amount over `maxPayWei`, or an expired `valid_before`.\n- An uncapped `X402Client` is not constructible; a missing or malformed `maxPayWei` throws at construction.\n- A browser account that signs an x402 payment with `personal_sign` produces an unrecoverable signature; use\n `signEip712`. The SDK's `signChallenge` already prefers it.\n- `InjectedSigner` re-checks the chain before every sign and send and throws if the provider has switched\n underneath it.\n- `decryptKeystore` compares the MAC in constant time, so a wrong passphrase fails closed without leaking how\n many bytes matched.\n- `CitrateWallet.sendTransaction` throws if `connect(chain, rpcUrl?)` has not been called, and any signing\n call throws once the account is locked.\n\n## Access and canon\n\nCommercial. This is buyer-side integration depth, job, credit, and training calldata, the x402 codec, and\nprovider selection, the implementation work that benefits a contracted integrator and that we gate from\nanonymous scraping. It is not secret: every symbol resolves to public on-chain ABIs and an open package. No\nkeys or mnemonics appear here, and none are hardcoded in the source at the audited SHA. A `CitrateWallet`\nholds only a passphrase-encrypted v3 keystore in `localStorage`; an `InjectedSigner` delegates to the\nextension. Do not paste a key, passphrase, or mnemonic into any example.\n\n## Source and verification\n\n- Source repo: `citrate-sdk-marketplace`.\n- Paths: `src/index.ts` (public surface), `src/client.ts`, `src/x402.ts`, `src/wallet/`, `src/contracts.ts`,\n `src/jobs.ts`, `src/credits.ts`, `src/training.ts`, `src/types.ts`, `src/format.ts`, `AUDIT_TIER.md`.\n- Audited against SHA: `5cc1f39`.\n- Status: Implemented, pre-audit (Tier 1; a full external audit is required before any `v1.0.0` stable\n release).\n"},"/sdks/overview":{"slug":"/sdks/overview","title":"SDKs overview","tier":"public","orgId":null,"sourceKind":"authored","source":"npm @citratelabs + PyPI citrate-labs-sdk (published packages)","syncedSha":"2f8da46","toc":[{"depth":2,"text":"The packages","anchor":"the-packages"},{"depth":2,"text":"What each SDK gives you","anchor":"what-each-sdk-gives-you"},{"depth":2,"text":"Where KYC is required, and where it is not","anchor":"where-kyc-is-required-and-where-it-is-not"},{"depth":2,"text":"Status","anchor":"status"}],"body":"The Citrate SDKs are published and installable today. They run against testnet chain 40204 and are\npre-audit. This page is the honest map of what exists, what each one is for, and exactly where identity\nverification (KYC) is required and where it is not.\n\n## The packages\n\n| Package | Registry | What it is |\n|---|---|---|\n| **`@citratelabs/sdk`** | npm | The canonical TypeScript/JavaScript SDK: the client, the account-abstraction Keyring account, identity/OIDC, entitlements, a memory client, and the inference-gateway client. Start here. |\n| **`@citratelabs/marketplace-sdk`** | npm | The compute-marketplace SDK: contract bindings, ABI decoders, and the x402 payment client. |\n| **`citrate-labs-sdk`** | PyPI | The Python SDK. Non-canonical and opt-in; it may lag the TypeScript SDK. |\n\n`@citratelabs/citrate-js` is **deprecated**. It was renamed to `@citratelabs/sdk` and only re-exports it for\none migration cycle. Use `@citratelabs/sdk`.\n\n```bash\nnpm i @citratelabs/sdk # TypeScript / JavaScript\nnpm i @citratelabs/marketplace-sdk # marketplace + x402\npip install citrate-labs-sdk # Python\n```\n\n## What each SDK gives you\n\n- **`@citratelabs/sdk`**: a `CitrateClient` over the 40204 RPC; the account-abstraction surface (predict a\n counterfactual ERC-4337 Keyring account from an identity, verify it on-chain, request a factory deploy\n permit, no key custody); the OIDC/SIWE identity client; the entitlement capability map; a memory client;\n and an OpenAI-shaped inference-gateway client.\n- **`@citratelabs/marketplace-sdk`**: post and watch marketplace jobs, decode receipts, and pay metered\n endpoints over x402 with a spend-capped client.\n- **`citrate-labs-sdk`**: a Python surface over the same chain and gateway for teams that live in Python.\n\n## Where KYC is required, and where it is not\n\nWe are explicit about this because it is the first thing an integrator needs to know. Identity verification\non Citrate is **in-house (VERI)**, keyed to a real human or institutional account through the authorization\nspine; Citrate keeps a status and two dates, never the documents.\n\n**No KYC required**, build and read freely:\n\n- Installing any SDK and reading the chain (blocks, transactions, receipts, contract state).\n- Predicting and verifying an account-abstraction Keyring account address.\n- Signing in with OIDC or SIWE at the `public` tier.\n- Calling public inference-gateway routes and the public x402 sandbox.\n\n**KYC required** (the `commercial.kyc` entitlement tier), actions that touch money, regulated compute, or\ngated artifacts:\n\n- Selling compute on the marketplace (operator-side).\n- Paid membership grants and validator staking.\n- Downloading tier-gated artifacts from the Commissary, and reading `commercial.kyc` docs.\n- Requesting scoped access to the not-yet-public repositories.\n\nThe gate is an entitlement claim the authority mints from the account's verified status; the SDK reads it and\nfails closed. A call that needs `commercial.kyc` on an unverified account is refused with a clear reason, not\na silent partial success.\n\n## Status\n\nPublished and pre-audit, running against testnet 40204. External audits gate the stable and mainnet releases.\nSee [the JS SDK](/sdks/js), [the Python SDK](/sdks/python), [the marketplace SDK](/sdks/marketplace),\n[identity](/sdks/identity), and [entitlements](/sdks/entitlements) for the per-surface detail.\n"},"/sdks/python":{"slug":"/sdks/python","title":"Python SDK","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-sdk-python/citrate_sdk/","syncedSha":"869694b","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"CitrateClient","anchor":"citrateclient"},{"depth":3,"text":"Economic and education managers","anchor":"economic-and-education-managers"},{"depth":3,"text":"The citrate console script","anchor":"the-citrate-console-script"},{"depth":2,"text":"Identity, entitlements, and gateway","anchor":"identity-entitlements-and-gateway"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The Python client for Citrate, for data and ML teams who already live in Python. It reads account state,\ndeploys models, runs inference, and drives the economic and education managers against a Citrate node. It\nis a secondary client. The canonical, fullest SDK is the JavaScript one at [JavaScript SDK](/sdks/js); read\nthis page when Python is where your work already is, and expect it to lag.\n\n## What it is\n\n`citrate-labs-sdk` is a thin Python layer over a Citrate node's JSON-RPC. You create one `CitrateClient`,\nbound to an RPC endpoint and, for writes, a private key. The client speaks JSON-RPC to the node and exposes\nthe model, inference, and account methods directly. The economic and education surfaces, learning, staking,\nclassroom, compute, treasury, and farming, are separate manager classes you construct yourself, passing the\nclient's RPC callable and the relevant contract addresses.\n\nTwo facts about maturity belong up front, because the package states them about itself. The SDK is\nnon-canonical: its own `pyproject.toml` description and its `NON_CANONICAL.md` say the canonical SDK is the\nJavaScript `@citratelabs/sdk`, that features land there first, and that Python may lag by an unbounded amount. And\nit is early: the `pyproject.toml` classifier is `Development Status :: 2 - Pre-Alpha`. Treat every surface\nhere as pre-audit and subject to change. New work should start on the [JavaScript SDK](/sdks/js); reach for\nPython when a Python codebase is the reason you are here.\n\n## How to use it\n\n1. Install the package. The distribution is `citrate-labs-sdk`; the import name is `citrate_sdk`.\n\n ```bash\n pip install citrate-labs-sdk\n ```\n\n2. Point the client at a node. Without a private key the client is read-only, which is all you need for\n balances, nonces, and model listings.\n\n ```python\n from citrate_sdk import CitrateClient\n\n client = CitrateClient(rpc_url=\"https://rpc.example\")\n print(\"chain id:\", client.get_chain_id())\n ```\n\n3. Supply a key for writes. Pass it through the environment, never in source. A remote `http://` endpoint\n raises a cleartext-transport warning, because a signed transaction would cross the wire in the clear;\n `localhost` http is allowed silently. Pass `allow_insecure_http=True` only when you mean plaintext to a\n remote host.\n\n ```python\n import os\n from citrate_sdk import CitrateClient\n\n client = CitrateClient(\n rpc_url=os.environ[\"CITRATE_RPC_URL\"],\n private_key=os.environ[\"CITRATE_PRIVATE_KEY\"],\n )\n ```\n\n4. Use a manager when you need an economic or education surface. Managers are not attributes of the client;\n you construct them with the client's `_rpc_call` callable and the addresses they act on.\n\n ```python\n from citrate_sdk import FarmingManager\n\n farming = FarmingManager(\n client._rpc_call,\n contract_addresses={\"farming\": \"0xFarmingContract\"},\n )\n for row in farming.get_leaderboard(count=10):\n print(row)\n ```\n\nThe full install-to-inference walkthrough is in [Python quickstart](/sdks/python/tutorials/python-quickstart).\n\n## Reference\n\nThe surface below is verified against `citrate-sdk-python` at `869694b`. Distribution name `citrate-labs-sdk`,\nversion `0.6.1`, `requires-python >= 3.10`. Runtime dependencies, from `pyproject.toml`: `requests~=2.33`,\n`cryptography>=48.0.1,<51`, `eth-account~=0.9`, `web3~=7.15`, `numpy~=2.0`, `typing-extensions~=4.0`. Optional\nextras: `dev`, `docs`. Configuration reads `CITRATE_RPC_URL`, `CITRATE_CHAIN_ID`, and `CITRATE_PRIVATE_KEY`\nin the examples and tests.\n\n### CitrateClient\n\nSource: `citrate_sdk/client.py` (class `CitrateClient`), exported from `citrate_sdk/__init__.py`.\n\n| Method | Signature | Notes |\n|---|---|---|\n| `__init__` | `(rpc_url=\"http://localhost:8545\", private_key=None, allow_insecure_http=False)` | `client.py:31`. Read-only without a key. |\n| `get_chain_id()` | `-> int` | `eth_chainId`, `client.py:103`. |\n| `get_balance(address)` | `-> int` | wei, `eth_getBalance`, `client.py:107`. |\n| `get_nonce(address)` | `-> int` | pending nonce, `eth_getTransactionCount`, `client.py:112`. |\n| `deploy_model(model_path, config)` | `-> ModelDeployment` | needs a key; hashes, optionally encrypts, uploads to IPFS, deploys via precompile `0x...0100`, `client.py:117`. |\n| `inference(model_id, input_data, encrypted=False, max_gas=1000000, recipient_public_key=None)` | `-> InferenceResult` | precompile `0x...0101`; the encrypted path fails closed without `recipient_public_key`, `client.py:192`. |\n| `get_model_info(model_id)` | `-> Dict` | `citrate_getModel`, raises `ModelNotFoundError`, `client.py:267`. |\n| `list_models(owner=None, limit=100)` | `-> List[Dict]` | `citrate_listModels`, `client.py:277`. |\n| `purchase_model_access(model_id, payment_amount)` | `-> str` | needs a key; access-control precompile `0x...0104`, `client.py:282`. |\n\nSigning binds `chainId` under EIP-155 (`_eip155_chain_id`, `client.py:312`) so a signature cannot be replayed\non another network. IPFS upload fails closed rather than fabricating a fallback CID (`client.py:298`). A\nprivate key creates a `KeyManager` on `client.key_manager` (`citrate_sdk/crypto.py`), which exposes\n`get_address()`, `get_private_key()`, and the ECDH helpers used by encrypted inference.\n\n### Economic and education managers\n\nThese are separate classes, not attributes of `CitrateClient`. Each takes the `_rpc_call` callable, an\noptional `default_account` (required for writes), `gas_limit`, `gas_price`, and the addresses it acts on.\nMost take a `contract_addresses` dict; `StakingManager` and `ClassroomManager` instead take a single\n`staking_address` or `classroom_address`. Writes raise `ConfigurationError` when `default_account` is unset;\nread methods are `eth_call`-only and need no account.\n\n| Manager | Source | Selected methods |\n|---|---|---|\n| `LearningManager` | `learning.py:176` | `list_pools`, `join_pool`, `leave_pool`, `create_pool`, `get_cycle_status`, `register_for_cycle`, `claim_cycle_reward`, `get_contributions`, `claim_contribution_rewards` |\n| `StakingManager` | `learning.py:461` | `deposit`, `withdraw`, `claim_withdrawal`, `get_info`, `preview_deposit`, `preview_withdraw`, `get_withdrawal` |\n| `ClassroomManager` | `learning.py:629` | `create`, `enroll`, `unenroll`, `deploy_model`, `remove_model`, `rotate_invite_code`, `get_classroom`, `can_student_access_model`, `get_student_teacher` |\n| `ComputeManager` | `compute.py:72` | `post_job`, `bid_on_job`, `get_job`, `list_jobs`, `submit_result`, `register_provider`, `get_provider_info`, `heartbeat`, `create_pool`, `join_pool`, `leave_pool`, `get_pools`, `dispute_result`, `get_dispute` |\n| `TreasuryManager` | `treasury.py:60` | `deposit_stablecoin`, `purchase_compute_credits`, `get_credit_balance`, `estimate_calls_remaining`, `get_treasury_value`, `get_epoch_revenue`, `get_current_epoch`, `get_stablecoin_balance`, `get_total_distributed`, `get_credit_price_usd` |\n| `FarmingManager` | `farming.py:56` | `get_my_score`, `get_my_share`, `get_leaderboard`, `claim`, `has_claimed`, `get_distribution_info`, `is_in_snapshot`, `get_claimed_amount` |\n\nAll six classes are re-exported from `citrate_sdk/__init__.py`. Shared data types (`LearningPool`,\n`CycleStatus`, `ComputeJob`, `ProviderInfo`, `StakingInfo`, and the rest) live in `citrate_sdk/types.py`;\nmodel types (`ModelConfig`, `ModelDeployment`, `InferenceResult`, `ModelType`, `AccessType`) live in\n`citrate_sdk/models.py`.\n\n### The citrate console script\n\n`pyproject.toml` declares a console script under `[project.scripts]`, `citrate = \"citrate_sdk.cli:main\"`,\nand it now resolves. `citrate_sdk/cli.py` implements `main()` over the standard-library `argparse`, with four\ncommand groups, each reading the generated federation contract so addresses and endpoints are never hand-typed:\n\n- `citrate contract [--section all|chain|aa|identity|gateway|entitlements]`, print the canonical table.\n- `citrate wallet predict (--user-id 0x… | --uuid ) [--verify]`, the embedded smart-account address;\n `--verify` checks it against the on-chain factory.\n- `citrate entitlement capabilities|normalize --tier `, the canonical capability set for a tier.\n- `citrate gateway models|health|chat --model M --message TEXT [--api-key-file PATH]`, the inference gateway.\n The gateway key is read from `$CITRATE_GATEWAY_API_KEY` or `--api-key-file` (a path, or `-` for stdin); it\n is never accepted as a value on `argv`, so it cannot leak through `ps` or shell history (SPY-B-012).\n\nFor example, `citrate wallet predict --user-id 0x4242…4242` prints the same address the on-chain factory\ndeploys. Status for this surface: Implemented (`citrate_sdk/cli.py`).\n\n## Identity, entitlements, and gateway\n\nBeyond the on-chain client, the Python SDK ships the same identity spine, embedded Keyring account, entitlement\ncapabilities, and inference-gateway client as the JavaScript SDK, at full parity. `citrate_sdk.identity`\ncovers OIDC PKCE and SIWE sign-in, hardened ID-token verification, and smart-account address prediction that\nmatches the on-chain factory byte-for-byte; `citrate_sdk.entitlements` is the canonical `normalize_tier` and\ncapability map; `citrate_sdk.gateway` is an OpenAI-compatible client for `infer.citrate.ai`. These use only\nexisting dependencies (`cryptography`, `eth_utils`, `requests`). See [identity and the embedded Keyring account](/sdks/identity)\nand [entitlements](/sdks/entitlements) for the shared reference; the examples there include Python.\n\nThe SDK also ships a memory client for a `citrate-memories` gateway. `MemoryClient` (OIDC REST: recall,\nsearch, neighbors, verify, review, assert) and `ByomMemoryClient` (the bring-your-own-model MCP path), with\n`MemoryError`, live in `citrate_sdk/memory.py` and are re-exported from `citrate_sdk/__init__.py`.\n\n## Design rationale\n\nThe managers are constructed separately, rather than hung off the client, because each binds to a contract\naddress that varies by deployment and that the client has no business knowing by default. Passing\n`_rpc_call` keeps a single transport and a single signing path while letting a caller wire up only the\nsurfaces they use. The harder edges, EIP-155 chain binding on every signature and a fail-closed IPFS upload,\nare there so a transaction signed for Citrate cannot be replayed elsewhere and so a deploy never reports a\nfabricated content hash. The cost of being a secondary client is real: this SDK trails the JavaScript one,\nand we say so rather than paper over it.\n\n## Failure modes\n\n- Encrypted inference without `recipient_public_key` fails closed (`client.py:208`). The symmetric key is\n ECDH-wrapped to the recipient and is never shipped in cleartext on public calldata.\n- A signed transaction binds `chainId` via EIP-155, so it cannot be replayed on a different network.\n- IPFS upload failures propagate; `deploy_model` never invents a fallback CID (`client.py:298`).\n- A manager write without `default_account` raises `ConfigurationError`. Reads are `eth_call`-only and need\n no account.\n- A remote `http://` RPC endpoint raises a cleartext-transport warning. Use `https://`, or set\n `allow_insecure_http=True` only when you intend plaintext to a remote host.\n- The `citrate gateway` command refuses a key passed as a plain `argv` value. Supply it through\n `$CITRATE_GATEWAY_API_KEY` or `--api-key-file`, or the command reads no key at all (SPY-B-012).\n\n## Access and canon\n\nPublic. This is open SDK reference a developer needs to build on Citrate, so no tier gate applies. No keys,\nmnemonics, or private endpoints appear here; private keys are supplied at runtime through `private_key=` or\n`CITRATE_PRIVATE_KEY` and must never be committed. The SDK holds no identity data.\n\n## Source and verification\n\n- Source repo: `citrate-sdk-python`.\n- Paths: `citrate_sdk/client.py`, `citrate_sdk/learning.py`, `citrate_sdk/compute.py`,\n `citrate_sdk/treasury.py`, `citrate_sdk/farming.py`, `citrate_sdk/crypto.py`, `citrate_sdk/cli.py`,\n `citrate_sdk/memory.py`, `citrate_sdk/identity/`, `citrate_sdk/entitlements.py`, `citrate_sdk/gateway.py`,\n `citrate_sdk/types.py`, `citrate_sdk/models.py`, `citrate_sdk/__init__.py`, `pyproject.toml`, `examples/`,\n `NON_CANONICAL.md`.\n- Audited against SHA: `869694b`.\n- Status: Implemented, pre-audit, non-canonical (the canonical SDK is the [JavaScript SDK](/sdks/js)). The\n `citrate` console script is Implemented (`citrate_sdk/cli.py`).\n"},"/sdks/tutorials/first-app-with-sdk-js":{"slug":"/sdks/tutorials/first-app-with-sdk-js","title":"Build your first app with the JS SDK","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-sdk-js/src/index.ts","syncedSha":"2f8da46","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Step 1, set up the project","anchor":"step-1-set-up-the-project"},{"depth":3,"text":"Step 2, connect to testnet","anchor":"step-2-connect-to-testnet"},{"depth":3,"text":"Step 3, read chain state","anchor":"step-3-read-chain-state"},{"depth":3,"text":"Step 4, derive your Citrate Keyring account address","anchor":"step-4-derive-your-citrate-keyring-account-address"},{"depth":3,"text":"Step 5, assemble one sponsored write","anchor":"step-5-assemble-one-sponsored-write"},{"depth":3,"text":"Step 6, run it","anchor":"step-6-run-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"A short, end-to-end first project with `@citratelabs/sdk`. You will install the SDK, connect to Citrate Network on\ntestnet, derive a Citrate Keyring account address, read state off the chain, and assemble one sponsored write\nas a UserOperation. It takes about fifteen minutes, and every symbol used is verified against `citrate-sdk-js` at\nSHA `2f8da46`.\n\n## What it is\n\nA small Node or TypeScript script that talks to Citrate testnet, chain id `40204`. The reads need no key and\nno account. The write uses a Citrate Keyring account, a smart-contract account built on Kernel v3, and routes\nthrough the paymaster so the account does not need a funded balance to send its first operation. Where a step\nneeds a value that lives in the network's deployment config rather than the SDK, the page says so plainly\ninstead of inventing one.\n\n## How to use it\n\nYou will need Node 16 or newer (the SDK's declared `engines.node`) and npm.\n\n### Step 1, set up the project\n\n```bash\nmkdir citrate-first-app && cd citrate-first-app\nnpm init -y\nnpm pkg set type=module\nnpm install @citratelabs/sdk\nnpm install -D typescript tsx @types/node\n```\n\nKeep secrets out of source. The read steps need no key. If you later add a key for direct chain writes, put\nit in an environment variable such as `CITRATE_PRIVATE_KEY`, never inline.\n\n### Step 2, connect to testnet\n\nCreate `index.ts`:\n\n```typescript\nimport { CitrateClient, CHAIN_IDS, DEFAULT_RPC_URLS } from '@citratelabs/sdk';\n\nconst client = new CitrateClient({\n // DEFAULT_RPC_URLS[40204] resolves to ['https://rpc.citrate.ai'].\n rpcUrl: DEFAULT_RPC_URLS[CHAIN_IDS.TESTNET],\n});\n\nconsole.log('RPC endpoints:', client.getRpcUrls());\n```\n\nThe constructor validates each RPC URL as it builds, so a typo fails immediately with a `ValidationError`\n(`src/client/CitrateClient.ts`).\n\n### Step 3, read chain state\n\n```typescript\nconst chainId = await client.getChainId(); // -> 40204\nconsole.log('chainId:', chainId);\n\nconst addr = '0x0000000000000000000000000000000000000000'; // any address you want to inspect\nconsole.log('balance (wei):', (await client.getBalance(addr)).toString());\nconsole.log('nonce:', await client.getNonce(addr));\n```\n\nIf `getChainId` returns anything other than `40204`, you are pointed at a different network. These map to\n`getChainId`, `getBalance`, and `getNonce` in `src/client/CitrateClient.ts`. To confirm the node directly,\nsee [the JSON-RPC reference](/chain/rpc).\n\n### Step 4, derive your Citrate Keyring account address\n\nA Citrate Keyring account has the same address on every device, because the address is derived\ndeterministically from the user's id. You can compute it offline, before the account is ever deployed.\n\n```typescript\nimport { aa } from '@citratelabs/sdk';\n\n// The 32-byte AA userId is keccak256(utf8(lowercase uuid)).\nconst userId = aa.uuidToUserId('3f2504e0-4f89-41d3-9a0c-0305e82c3301');\n\n// factory and walletImpl are the deployed AA-stack addresses for chain 40204.\n// They live in the network's deployment config (served by auth.citrate.ai),\n// not in the SDK. Fetch them from your AA config; the shape is aa.CitrateAaConfig.\nconst account = aa.predictWalletAddress(factory, walletImpl, userId);\nconsole.log('account address:', account);\n```\n\n`uuidToUserId` and `predictWalletAddress` are pure functions in `src/aa/address.ts`. The address is the\nCREATE2 address the factory will deploy the Kernel proxy to. Reading state for this address works the same as\nany other: `await client.getBalance(account)`.\n\n### Step 5, assemble one sponsored write\n\nA write through a Citrate Keyring account is a UserOperation: encode the call, build the packed op, hash it,\nsign it, and submit it to the bundler. The paymaster sponsors the gas, so the account needs no balance for its\nfirst op. The pieces below are all real `aa` exports; the deployment addresses (`factory`, `walletImpl`,\n`paymaster`, `entryPoint`, `webauthnValidator`) come from your `aa.CitrateAaConfig`, and the gas figures come\nfrom a bundler estimate.\n\n```typescript\nimport { aa } from '@citratelabs/sdk';\n\n// 1. Encode the call this account should make (target, value, calldata).\nconst callData = aa.encodeExecuteSingle({\n to: '0xTargetContract',\n value: 0n,\n data: '0x', // your function calldata\n});\n\n// 2. Mark the op for paymaster sponsorship. FirstOp is the one-per-account\n// deploy category; Standard counts against the per-user daily cap.\nconst paymasterAndData = aa.packCitratePaymasterAndData({\n paymaster,\n paymasterVerificationGasLimit: 80_000n,\n paymasterPostOpGasLimit: 40_000n,\n category: aa.PaymasterCategory.FirstOp,\n});\n\n// 3. Build the packed UserOperation. The nonce comes from\n// EntryPoint.getNonce(account, key); gas limits come from a bundler estimate.\nconst op = aa.buildPackedUserOp({\n sender: account,\n nonce, // see aa.composeNonce / EntryPoint.getNonce\n initCode, // aa.packInitCode(factory, aa.encodeDeployFor(...)) on first op, else '0x'\n callData,\n callGasLimit: 200_000n,\n verificationGasLimit: 300_000n,\n preVerificationGas: 60_000n,\n maxFeePerGas,\n maxPriorityFeePerGas,\n paymasterAndData,\n});\n\n// 4. Hash and sign. Pick the signer the account is enrolled with.\nconst userOpHash = aa.getUserOpHash(op, entryPoint, BigInt(chainId));\nop.signature = await aa.signUserOpWithPasskey(userOpHash); // passkey path\n// or: op.signature = await aa.signUserOpWithEoa(signer, userOpHash);\n\n// 5. Submit through the bundler and wait for the receipt.\nconst bundler = new aa.BundlerClient(); // defaults to https://bundler.citrate.ai/rpc\nconst hash = await bundler.sendUserOperation(op, entryPoint);\nconst receipt = await bundler.waitForUserOperationReceipt(hash);\nconsole.log('mined:', receipt.success, receipt.receipt.transactionHash);\n```\n\nTwo steps depend on values outside the SDK and are described, not hard-coded. The `nonce` comes from\n`EntryPoint.getNonce(account, key)` composed with `aa.composeNonce`; build the key with\n`aa.validatorNonceKey` for an installed validator or `aa.rootValidatorNonce` for the root. The first op also\nneeds an `initCode` from `aa.packInitCode(factory, aa.encodeDeployFor(...))`, where `encodeDeployFor` carries\na permit signed by `auth.citrate.ai`. Passkey enrollment and signing are covered under\n[passkeys](/aa/passkeys); sponsorship categories and caps under [the paymaster](/aa/paymaster).\n\n### Step 6, run it\n\n```bash\nnpx tsx index.ts\n```\n\nExpect the RPC list, `chainId: 40204`, a balance and nonce for the address you inspected, and your derived\naccount address. The write step runs once you supply the deployment config and a signer.\n\n## Reference\n\nThe symbols this tutorial uses, with their source files in `citrate-sdk-js`.\n\n| Symbol | Source |\n|---|---|\n| `CitrateClient`, `getRpcUrls`, `getChainId`, `getBalance`, `getNonce` | `src/client/CitrateClient.ts` |\n| `CHAIN_IDS`, `DEFAULT_RPC_URLS` | `src/utils/constants.ts` |\n| `aa.uuidToUserId`, `aa.predictWalletAddress` | `src/aa/address.ts` |\n| `aa.encodeExecuteSingle` | `src/aa/kernel.ts` |\n| `aa.buildPackedUserOp`, `aa.getUserOpHash`, `aa.packCitratePaymasterAndData`, `aa.packInitCode`, `aa.encodeDeployFor` | `src/aa/userop.ts` |\n| `aa.signUserOpWithPasskey` | `src/aa/webauthn.ts` |\n| `aa.signUserOpWithEoa` | `src/aa/eoa.ts` |\n| `aa.BundlerClient`, `aa.PaymasterCategory` | `src/aa/bundler.ts`, `src/aa/types.ts` |\n\n## Design rationale\n\nThe reads come first because they need nothing: no key, no account, no funds. That lets a reader confirm they\nare on Citrate Network before they touch anything that costs. The write is shown as a UserOperation rather\nthan a raw signed transaction because that is how a Citrate Keyring account moves, and because the paymaster\ncan cover the first op so a new account is usable immediately. The deployment addresses are deliberately left\nas values you fetch, because they are per-network config and pinning a wrong literal in a tutorial is worse\nthan naming the source.\n\n## Failure modes\n\n- A `chainId` other than `40204` means the client is pointed at a different network. Check `rpcUrl`.\n- `predictWalletAddress` throws if the factory or implementation is the zero address, or if the userId is not\n a 32-byte hex string, so a bad config fails before any chain call.\n- A bundler rejection arrives as a `BundlerRpcError` carrying the `AAxx` code, for example `AA21` for an\n unfunded prefund or `AA31` for a paymaster deposit too low. Read the code; it names the real cause.\n- `signUserOpWithPasskey` throws outside a browser with WebAuthn available. In Node, use the EOA path with\n `signUserOpWithEoa` and an ethers `Signer`.\n\n## Access and canon\n\nPublic. The read steps need no key or credentials and write no state. The write step sources its key from a\npasskey or an environment-held signer, never inline, and the hostnames named (`rpc.citrate.ai`,\n`bundler.citrate.ai`, `auth.citrate.ai`) are public production defaults shipped in the SDK.\n\n## Source and verification\n\n- Source repo: `citrate-sdk-js`, package `@citratelabs/sdk@0.2.0` (`@citratelabs/citrate-js` retained as a deprecated alias).\n- Audited against SHA: `2f8da46`.\n- Symbols verified in `src/client/CitrateClient.ts`, `src/utils/constants.ts`, and `src/aa/{address, kernel,\n userop, webauthn, eoa, bundler, types}.ts`.\n- Status: Implemented (pre-audit). The client reads run against testnet 40204; the `aa` write path is\n Implemented but in development (EW-S1 WP-7) and depends on live bundler and auth infrastructure.\n"},"/sdks/tutorials/post-a-marketplace-job":{"slug":"/sdks/tutorials/post-a-marketplace-job","title":"Post a marketplace job","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-sdk-marketplace/src/index.ts","syncedSha":"5cc1f39","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Step 1, set up clients","anchor":"step-1-set-up-clients"},{"depth":3,"text":"Step 2, pick a model and inspect providers","anchor":"step-2-pick-a-model-and-inspect-providers"},{"depth":3,"text":"Step 3, estimate cost","anchor":"step-3-estimate-cost"},{"depth":3,"text":"Step 4, load an account","anchor":"step-4-load-an-account"},{"depth":3,"text":"Step 5, build job calldata and submit","anchor":"step-5-build-job-calldata-and-submit"},{"depth":3,"text":"Step 6, read back the result events","anchor":"step-6-read-back-the-result-events"},{"depth":3,"text":"Step 7, pay per inference over the gateway (optional)","anchor":"step-7-pay-per-inference-over-the-gateway-optional"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"A runnable, end-to-end walkthrough. You will pick a model, estimate its cost, build job calldata, and submit\nit to Citrate Market on chain id `40204` with the [Marketplace SDK](/sdks/marketplace), then optionally pay\nfor a single inference over the x402 path instead. For integrators building buyer-side. Every API used here\nexists in `citrate-sdk-marketplace` at `5cc1f39`.\n\n## What it is\n\nA short Node and TypeScript script that reads marketplace state with `MarketplaceClient`, loads an account\nwith `CitrateWallet`, builds `postJob` calldata with `postJobCalldata` and submits it, reads back the result\nevents, and, as an alternative, calls a paid gateway route over x402 with `X402Client`. The SDK is `0.1.0`\nand Tier 1, so it is pre-audit; run against testnet only, and use a throwaway account. Never paste a real\nkey, passphrase, or mnemonic. The compute marketplace and its contracts are described under\n[Citrate Market](/compute/pool) and [the compute contracts](/contracts/compute); the per-request payment path is\nthe [x402 contract path](/contracts/x402).\n\n## How to use it\n\nYou will need Node 20 or newer, a Citrate testnet RPC URL, and a small SALT balance on a test account. Then\ninstall the SDK and viem:\n\n```bash\nnpm install @citratelabs/marketplace-sdk viem\n```\n\n### Step 1, set up clients\n\n```ts\nimport { createPublicClient, http } from \"viem\";\nimport { MarketplaceClient, defaultAddresses, CITRATE_TESTNET_CHAIN_ID } from \"@citratelabs/marketplace-sdk\";\n\nconst RPC_URL = process.env.CITRATE_RPC_URL!; // never hardcode\nconst publicClient = createPublicClient({ transport: http(RPC_URL) });\nconst market = new MarketplaceClient({ publicClient, addresses: defaultAddresses() });\n\nconsole.log(\"chainId:\", CITRATE_TESTNET_CHAIN_ID); // 40204\n```\n\n### Step 2, pick a model and inspect providers\n\nSlice 1 requires a fully pinned model hash (`0x` plus 64 hex). Validate it, then list active providers:\n\n```ts\nconst modelHash = market.resolveModelHash(process.env.MODEL_HASH!); // throws if not a pinned hash\nconst providers = await market.listProviders(modelHash);\nconsole.log(`${providers.length} active providers`, providers.map(p => p.endpoint));\n```\n\n### Step 3, estimate cost\n\n```ts\nimport { VerificationTier, grainsToSaltDisplay } from \"@citratelabs/marketplace-sdk\";\n\nconst cost = await market.estimateCost({\n modelHash,\n inputTokens: 1200n,\n outputTokens: 800n,\n tier: VerificationTier.Commitment, // 0 is cheapest; ZKProof is 1.5x, TEE is 2.0x\n});\nconsole.log(\"estimated cost:\", grainsToSaltDisplay(cost)); // e.g. \"0.0123 SALT\"\n```\n\n### Step 4, load an account\n\nUse a passphrase-encrypted keystore that stays in the local key store. The passphrase comes from the\nenvironment, never from source. This is the Citrate Keyring integration; the code symbol is `CitrateWallet`.\n\n```ts\nimport { CitrateWallet } from \"@citratelabs/marketplace-sdk\";\nimport { defineChain } from \"viem\";\n\nconst citrate = defineChain({\n id: 40204,\n name: \"Citrate Testnet\",\n nativeCurrency: { name: \"SALT\", symbol: \"SALT\", decimals: 18 },\n rpcUrls: { default: { http: [RPC_URL] } },\n});\n\n// First run: CitrateWallet.createWallet(process.env.WALLET_PASSPHRASE!) to generate and persist a key.\nconst account = (await CitrateWallet.unlockWallet(process.env.WALLET_PASSPHRASE!))\n .connect(citrate, RPC_URL); // connect() is required before sendTransaction\n```\n\n### Step 5, build job calldata and submit\n\n```ts\nimport { postJobCalldata, PaymentMethod } from \"@citratelabs/marketplace-sdk\";\n\nconst { data, inputHash } = postJobCalldata({\n modelHash,\n input: new TextEncoder().encode(\"Summarize the Citrate whitepaper.\"),\n maxPriceGrains: cost * 2n, // headroom over the estimate\n tier: VerificationTier.Commitment,\n bidWindowBlocks: 20n,\n execWindowBlocks: 200n,\n paymentMethod: PaymentMethod.SALT, // SALT sends value = maxPriceGrains\n});\n\nconst txHash = await account.sendTransaction({\n to: market.addresses.computeMarketplace,\n data,\n value: cost * 2n, // BulkCredits would send value: 0n instead\n});\nconsole.log(\"posted job:\", txHash, \"inputHash:\", inputHash);\n```\n\n### Step 6, read back the result events\n\n```ts\nconst receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });\nconst { parseJobEvents } = await import(\"@citratelabs/marketplace-sdk\");\nconsole.log(parseJobEvents(receipt.logs));\n```\n\n`parseJobEvents` returns typed events: `JobPosted`, `JobAssigned`, and `JobCompleted`, each carrying the job\nid and the relevant addresses.\n\n### Step 7, pay per inference over the gateway (optional)\n\nInstead of posting an on-chain job, you can call a paid gateway route and settle a single request over x402:\n\n```ts\nimport { X402Client } from \"@citratelabs/marketplace-sdk\";\n\nconst x402 = new X402Client({\n signer: account,\n chainId: 40204,\n maxPayWei: cost * 2n, // hard per-request cap\n allowedTokens: [process.env.WSALT_ADDRESS as `0x${string}`],\n});\n\nconst res = await x402.send(`${process.env.GATEWAY_URL}/v1/chat/completions`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ model: process.env.MODEL_ID, messages: [{ role: \"user\", content: \"hi\" }] }),\n});\nconsole.log(await res.json()); // X402Client checks policy, signs the 402 challenge, retries once\n```\n\nThe server side of this handshake is the [x402 contract path](/contracts/x402).\n\n## Reference\n\nThe API used in each step, with its source in `citrate-sdk-marketplace`:\n\n| Step | API | Source |\n|---|---|---|\n| 1 | `MarketplaceClient`, `defaultAddresses`, `CITRATE_TESTNET_CHAIN_ID` | `src/client.ts`, `src/contracts.ts` |\n| 2 | `resolveModelHash`, `listProviders` | `src/client.ts` |\n| 3 | `estimateCost`, `VerificationTier`, `grainsToSaltDisplay` | `src/client.ts`, `src/types.ts`, `src/format.ts` |\n| 4 | `CitrateWallet.unlockWallet`, `connect` | `src/wallet/citrate.ts` |\n| 5 | `postJobCalldata`, `PaymentMethod` | `src/jobs.ts`, `src/types.ts` |\n| 6 | `parseJobEvents` | `src/jobs.ts` |\n| 7 | `X402Client` | `src/x402.ts` |\n\nThe full surface is on the [Marketplace SDK](/sdks/marketplace) reference.\n\n## Failure modes\n\n- `resolveModelHash` throws on anything that is not a pinned `0x`+64-hex hash; slice 1 has no name lookup.\n- `sendTransaction` throws if you have not called `connect(chain, rpcUrl?)` first, or if the account is\n locked.\n- For the `SALT` payment method, `value` must equal `maxPriceGrains`; for `BulkCredits`, `value` must be\n `0n`, or the marketplace rejects the mixed payment.\n- `X402Client` returns the unsigned `402` rather than paying if the challenge is for the wrong chain, an\n unallowed token or recipient, an amount over `maxPayWei`, or an expired window.\n\n## Access and canon\n\nCommercial. This walks buyer-side integration depth, which we gate from anonymous scraping. Every credential,\n`CITRATE_RPC_URL`, `WALLET_PASSPHRASE`, `MODEL_HASH`, `WSALT_ADDRESS`, and `GATEWAY_URL`, is read from the\nenvironment; never hardcode a key, passphrase, or mnemonic. The account key stays encrypted in the local\nWeb3 v3 keystore. Run against testnet `40204` only.\n\n## Source and verification\n\n- Source repo: `citrate-sdk-marketplace`.\n- Built against `src/index.ts`, `src/client.ts`, `src/jobs.ts`, `src/x402.ts`, `src/wallet/`,\n `src/contracts.ts`, `src/types.ts`, `src/format.ts`.\n- Audited against SHA: `5cc1f39`.\n- Status: Implemented, pre-audit (Tier 1). Run on testnet only with a throwaway account.\n"},"/sdks/python/tutorials/python-quickstart":{"slug":"/sdks/python/tutorials/python-quickstart","title":"Python quickstart","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-sdk-python/examples/basic_usage.py","syncedSha":"869694b","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Step 1, install","anchor":"step-1-install"},{"depth":3,"text":"Step 2, set the environment","anchor":"step-2-set-the-environment"},{"depth":3,"text":"Step 3, connect","anchor":"step-3-connect"},{"depth":3,"text":"Step 4, read account state","anchor":"step-4-read-account-state"},{"depth":3,"text":"Step 5, deploy a model","anchor":"step-5-deploy-a-model"},{"depth":3,"text":"Step 6, run inference","anchor":"step-6-run-inference"},{"depth":3,"text":"Step 7, discover models","anchor":"step-7-discover-models"},{"depth":3,"text":"Step 8, use a manager (optional)","anchor":"step-8-use-a-manager-optional"},{"depth":3,"text":"Step 9, clean up","anchor":"step-9-clean-up"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Install the Python SDK, connect to a Citrate node, read account state, then deploy a model and run inference,\nin a few minutes. For Python developers meeting Citrate for the first time. Every call below exists in\n`citrate_sdk/client.py` at `869694b`, and the flow mirrors `examples/basic_usage.py`, which you can run\nas-is from the repo.\n\n## What it is\n\nA copy-paste tour of the working Python surface. The read steps need no key. The write steps, deploy and\ninference, need a funded account. The Python SDK is non-canonical and Pre-Alpha; the canonical SDK is the\n[JavaScript SDK](/sdks/js). A `citrate` console script also ships (`citrate_sdk/cli.py`, with `contract`,\n`wallet`, `entitlement`, and `gateway` commands); this tutorial uses the `CitrateClient` API directly rather\nthan the CLI.\n\n## How to use it\n\nYou will need Python 3.10 or newer (`pyproject.toml` sets `requires-python = \">=3.10\"`), a reachable Citrate\nRPC endpoint, and, for the write steps, a funded account's private key supplied through the environment.\n\n### Step 1, install\n\n```bash\npython -m venv .venv && source .venv/bin/activate\npip install citrate-labs-sdk\n```\n\nThe distribution is `citrate-labs-sdk`; the import name is `citrate_sdk`.\n\n### Step 2, set the environment\n\n```bash\nexport CITRATE_RPC_URL=\"https://rpc.example\" # your node's RPC endpoint\nexport CITRATE_PRIVATE_KEY=\"0x...\" # only needed for writes\n```\n\nKeep any key out of version control. If you have none yet, Step 3 generates one for local experimentation.\n\n### Step 3, connect\n\n```python\nimport os\nfrom citrate_sdk import CitrateClient\nfrom citrate_sdk.crypto import KeyManager\n\nrpc_url = os.getenv(\"CITRATE_RPC_URL\", \"http://localhost:8545\")\nprivate_key = os.getenv(\"CITRATE_PRIVATE_KEY\")\n\n# No key yet? Generate one for local experimentation, then store it securely.\nif not private_key:\n km = KeyManager()\n private_key = km.get_private_key()\n print(\"Generated address:\", km.get_address())\n\nclient = CitrateClient(rpc_url=rpc_url, private_key=private_key)\nprint(\"Connected to chain id:\", client.get_chain_id())\n```\n\n`CitrateClient` warns if you point it at a remote `http://` endpoint, since a signed transaction would cross\nthe wire in the clear. Use `https://`, or pass `allow_insecure_http=True` only when you mean it.\n\n### Step 4, read account state\n\n```python\naddress = client.key_manager.get_address()\nbalance_wei = client.get_balance(address)\nnonce = client.get_nonce(address)\n\nprint(f\"Address: {address}\")\nprint(f\"Balance: {balance_wei / 10**18:.4f} (native units)\")\nprint(f\"Nonce: {nonce}\")\n```\n\nThese three calls, `get_balance`, `get_nonce`, and `get_chain_id`, are read-only and work without a key.\n\n### Step 5, deploy a model\n\n```python\nimport json\nfrom pathlib import Path\nfrom citrate_sdk import ModelConfig, ModelType, AccessType\n\n# A small stand-in model file for the demo.\nmodel_path = Path(\"demo_model.json\")\nmodel_path.write_text(json.dumps({\"type\": \"demo\", \"version\": \"1.0\"}))\n\nconfig = ModelConfig(\n name=\"Demo Classifier\",\n description=\"A simple demo classifier model\",\n model_type=ModelType.CUSTOM,\n access_type=AccessType.PUBLIC,\n encrypted=False,\n)\n\ndeployment = client.deploy_model(model_path, config)\nprint(\"Model ID:\", deployment.model_id)\nprint(\"Tx hash: \", deployment.tx_hash)\nprint(\"IPFS CID:\", deployment.ipfs_hash)\n```\n\n`deploy_model` hashes the file, uploads it to IPFS (failing closed if the upload fails, no fabricated CID),\nthen deploys through the model-deployment precompile. It requires a key.\n\n### Step 6, run inference\n\n```python\nresult = client.inference(\n model_id=deployment.model_id,\n input_data={\"data\": [0.5] * 10, \"format\": \"array\"},\n)\nprint(\"Output: \", result.output_data)\nprint(\"Gas used:\", result.gas_used)\n```\n\nFor encrypted inference, set `encrypted=True` and pass `recipient_public_key=...`. Without it the call fails\nclosed rather than shipping a symmetric key in cleartext on public calldata.\n\n### Step 7, discover models\n\n```python\nfor m in client.list_models(limit=5):\n print(m.get(\"name\", \"Unnamed\"), \"->\", m.get(\"model_id\"))\n```\n\n### Step 8, use a manager (optional)\n\nThe economic and education surfaces are separate classes. Construct one with the client's `_rpc_call`\ncallable, your account, and the addresses it acts on:\n\n```python\nfrom citrate_sdk import StakingManager\n\nstaking = StakingManager(\n client._rpc_call,\n default_account=address,\n staking_address=\"0xStakingContract\",\n)\nprint(\"Staking info:\", staking.get_info(address))\n```\n\nRead methods such as `get_info` and `preview_deposit` need no account; writes such as `deposit` and\n`withdraw` require `default_account`, or they raise `ConfigurationError`.\n\n### Step 9, clean up\n\n```python\nmodel_path.unlink(missing_ok=True)\n```\n\n## Reference\n\nThe calls used above, with their source in `citrate-sdk-python`:\n\n| Call | What it does | Source |\n|---|---|---|\n| `CitrateClient(...)` | bind to an RPC endpoint, optionally a key | `citrate_sdk/client.py:31` |\n| `get_chain_id()` | confirm the network | `citrate_sdk/client.py:103` |\n| `get_balance` / `get_nonce` | read account state | `citrate_sdk/client.py:107`, `:112` |\n| `deploy_model` | hash, IPFS-upload, deploy | `citrate_sdk/client.py:117` |\n| `inference` | run a model call | `citrate_sdk/client.py:192` |\n| `list_models` | list deployed models | `citrate_sdk/client.py:277` |\n| `KeyManager` | generate or load a key | `citrate_sdk/crypto.py` |\n| `StakingManager` | a representative manager | `citrate_sdk/learning.py:461` |\n\n## Failure modes\n\n- A remote `http://` endpoint warns about plaintext transport. Use `https://`, or set\n `allow_insecure_http=True` deliberately.\n- Encrypted inference without `recipient_public_key` fails closed.\n- An IPFS upload failure during `deploy_model` propagates; no fallback CID is invented.\n- A manager write without `default_account` raises `ConfigurationError`.\n- The `citrate gateway` command never takes its key as an `argv` value; supply it through\n `$CITRATE_GATEWAY_API_KEY` or `--api-key-file` (SPY-B-012).\n\n## Access and canon\n\nPublic. The write steps touch state and need a funded account; the read steps do not. No keys appear here:\nthey come from `CITRATE_PRIVATE_KEY` at runtime, or from a locally generated `KeyManager`. Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity.\n\n## Source and verification\n\n- Source repo: `citrate-sdk-python`.\n- Mirrors `examples/basic_usage.py`; APIs in `citrate_sdk/client.py`, `citrate_sdk/crypto.py`,\n `citrate_sdk/learning.py`.\n- Audited against SHA: `869694b`.\n- Status: Implemented, pre-audit, non-canonical (the canonical SDK is the [JavaScript SDK](/sdks/js)). See\n the full surface on the [Python SDK](/sdks/python) reference.\n"},"/security/posture":{"slug":"/security/posture","title":"Security Posture & Audit History","tier":"public","orgId":null,"sourceKind":"authored","source":"internal security control plane; GitHub Security Advisories","toc":[{"depth":2,"text":"Report a vulnerability","anchor":"report-a-vulnerability"},{"depth":2,"text":"How we audit","anchor":"how-we-audit"},{"depth":2,"text":"Prior known issues","anchor":"prior-known-issues"},{"depth":2,"text":"What this page does not contain","anchor":"what-this-page-does-not-contain"}],"body":"Citrate secures real value - a Layer-1, on-chain settlement, embedded-account key custody, and\nmetered compute. This page is the public, standing summary of how we keep it safe: how we\naudit, how to report a vulnerability, and where to read the record of issues we've already\nfixed.\n\n## Report a vulnerability\n\n**Never open a public issue for a security problem.** Report privately:\n\n- **GitHub Private Vulnerability Reporting** - on the affected repository's **Security** tab → *Report a vulnerability* (encrypted, no key exchange).\n- **Email** [security@citrate.ai](mailto:security@citrate.ai). We do not publish a PGP key yet, so send sensitive details through private vulnerability reporting.\n\nFull policy: [`SECURITY.md`](https://github.com/CitrateNetwork/.github/blob/main/SECURITY.md). Bounty scope,\nsafe-harbor rules and testing limits: coming soon.\nWe acknowledge within **72 hours**, triage within **5 business days**, and follow a\n**90-day coordinated disclosure** window.\n\n## How we audit\n\nSecurity is continuous, not a one-time gate. We run an internal **adversarial audit\nprogram** (the Agentile-Audit standard) across the federation on every meaningful change,\nwith a per-repository **tier** that sets the bar a change must clear. This is the same table as the\norg [`SECURITY.md`](https://github.com/CitrateNetwork/.github/blob/main/SECURITY.md):\n\n| Tier | Repositories | Audit policy | Vulnerability handling |\n|---|---|---|---|\n| **Tier 1**: consensus, value, keys, identity | `citrate-chain` (node, contracts, ZK), `citrate-core`, `citrate-identity`, `citrate-inference-gateway`, `citrate-compute-pool`, `citrate-coop`, `citrate-agent-runtime`, `citrate-sdk-js`, `citrate-sdk-python` | Full adversarial audit before every stable release | Coordinated disclosure; a GitHub Security Advisory (with a CVE request) for fixed High and Critical issues in released code |\n| **Tier 3**: docs and content | `citrate-docs`, `.github`, and other content-only repositories | Content review | Triage as documentation corrections, no CVE |\n\nA repository's own `AUDIT_TIER.md` is authoritative for that repository. A public repository without an\n`AUDIT_TIER.md` is handled as Tier 1 for reports.\n\nSupply-chain hardening is in progress: required review and CI checks on every public repository,\nthird-party GitHub Actions pinned to commit SHAs, and signed releases with SBOMs. Current prereleases are\nunsigned.\n\n> **Independent review.** We welcome external audits. No external-firm audit has been completed\n> yet; completed engagements (firm and scope) will be listed here.\n\n## Prior known issues\n\nResolved, disclosable vulnerabilities will be published as **[GitHub Security Advisories](https://github.com/CitrateNetwork/citrate-chain/security/advisories)**\non the affected repository (Security → Advisories), with affected and patched versions. None\nhave been published yet; the first will follow the fixes from the 2026-09 pre-bounty audit.\nSubscribe to a repo's advisories to be notified.\n\nAt a high level, the classes of issue we've found and fixed to date include node **sync\nrobustness** (deep-sync and restart edge cases), **consensus liveness** under adversarial\nload, and hardening from our recurring RM-Q audit passes. **Always run the latest release**\nof node software - older binaries can diverge from the current chain.\n\n## What this page does not contain\n\nTo keep the network safe, we don't publish: unfixed or embargoed vulnerabilities, exploit\ndetails ahead of coordinated disclosure, secrets or credentials, or operational details\n(host addresses, keys, internal topology). Our internal audit trail is kept private for that\nreason; this page and the advisories are its public, secret-free derivative.\n\n---\n\n*Questions about our security program: [security@citrate.ai](mailto:security@citrate.ai).*\n"},"/start/agentile":{"slug":"/start/agentile","title":"A primer on Agentile","tier":"public","orgId":null,"sourceKind":"linked","source":"AGENTILE.md","syncedSha":"a43a354","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Agentile is how the Citrate federation keeps its repositories coherent and auditable. This is a short\norientation; the canonical document is `AGENTILE.md` at the federation root, and where this page and that\nfile differ, the file wins.\n\n## What it is\n\nAgentile is the methodology we use to keep planning, governance, audit posture, and cross-repo state\ncoherent across the federation. It is three things working together:\n\n- a small set of **14 rules**, numbered 0 through 13, that constrain what can ship;\n- a **sprint-driven workflow** that constrains when and how things ship;\n- a **single-source-of-truth** convention: every document is dated and branch-stamped, each topic lives in\n exactly one place, and the agents that do the work follow the same rules and leave the same file-based\n trail a person would.\n\nIt is not Scrum, and it needs no tooling beyond git and Markdown. It exists because the codebase grew from\none repository into many, which opens two failure modes Agentile is built to close: drift between repos,\nand context that lives in someone's head instead of on disk.\n\n## How to use it\n\nIf you are building on Citrate, Agentile is the reason the docs you read are dated, traceable, and\nconsistent with the code. If you are contributing, it is the protocol you follow: read the entry point\nfirst, write the sprint file before the code, keep the test count climbing, and link rather than copy. If\nyou are auditing, it is why the evidence sits on disk rather than in memory. The two pages under\n[methodology](/methodology/rules) give the full statement and the sprint lifecycle.\n\n## Reference\n\nThe 14 rules, numbered 0 through 13, in brief. The full statement is on [the rules page](/methodology/rules).\n\n| # | Rule |\n|---|---|\n| 0 | Read before writing. Start with the entry point and the owners file. |\n| 1 | No mocks, stubs, or TODOs in production paths. |\n| 2 | Test count only goes up within a sprint. |\n| 3 | Audits are immutable; errata go in a follow-up, never an edit. |\n| 4 | The sprint file is the truth, not chat and not memory. |\n| 5 | Rule-12 frontmatter on every document (created, branch, author, status). |\n| 6 | Daily benchmark on the chain-core crates. |\n| 7 | Trace the data source before implementing any endpoint. |\n| 8 | Zero `.unwrap()` in production paths. |\n| 9 | One source of truth per topic. Link, do not copy. |\n| 10 | Authorization before destruction. Force-push, delete, or rotate needs a human's sign-off. |\n| 11 | The federation manifest is canonical. |\n| 12 | Cross-repo dependencies follow the drift map: the drift entry first, then the dependency. |\n| 13 | Visibility flips need sign-off (private to public on a Tier-1 repo). |\n\nThe sprint lifecycle, in brief: work lives in dated files that move from `active/` to `completed/`. A\nsprint opens with a goal, scope, and plan under Rule-12 frontmatter, takes daily updates, turns decisions\ninto ADRs, and bumps the manifest when a change crosses repos. Closing it means moving the file and writing\nthe close note. Completed sprints are immutable. The full choreography is on\n[the workflow page](/methodology/workflow).\n\n## Access and canon\n\nPublic. This is an overview that links to the canonical methodology documents in the federation. No\nsecrets. Internal-only procedures, such as incident response and access review, are gated; see\n[SOPs](/methodology/sops).\n\n## Source and verification\n\nLinked page. The canonical sources are `AGENTILE.md`, `docs/AGENTILE_RULES.md`, and\n`docs/AGENTILE_WORKFLOW.md` at the federation root, at SHA `a43a354`. Per Rule 9, this page summarizes and\nthe canonical files govern. Status: Implemented.\n"},"/start/local-workspace":{"slug":"/start/local-workspace","title":"Set up the federation locally","tier":"public","orgId":null,"sourceKind":"authored","source":".github/setup.sh, .github/AGENTS.md","toc":[{"depth":2,"text":"One command","anchor":"one-command"},{"depth":2,"text":"What you get","anchor":"what-you-get"},{"depth":2,"text":"Build and audit","anchor":"build-and-audit"},{"depth":2,"text":"For AI agents","anchor":"for-ai-agents"},{"depth":2,"text":"Licensing and contributing","anchor":"licensing-and-contributing"}],"body":"Get every public Citrate repository onto your machine in one command, in a single workspace\nyour IDE can open all at once. This is the same folder layout the maintainers use, so git\nintegration, cross-repo references, and the reusable CI all line up.\n\n## One command\n\nYou need the [GitHub CLI](https://cli.github.com) (`gh`), authenticated with `gh auth login`,\nand `git`. Then:\n\n```sh\nmkdir -p citrate-labs && cd citrate-labs\ngh repo clone CitrateNetwork/.github\nbash .github/setup.sh\n```\n\nThat clones and stars every public repository into `citrate-labs/`. To contribute (fork each\nrepo to your account, clone your fork, and set the `upstream` remote) run `bash .github/setup.sh fork`\ninstead. Set `NO_STAR=1` to skip starring, or `SHALLOW=1` for faster history-light clones.\n\nThe script reads the live list of public repositories, so it always matches what is published\nand never touches private ones.\n\n## What you get\n\n```\ncitrate-labs/\n .github/ org profile, reusable CI, AGENTS.md\n citrate-chain/ the L1: GhostDAG consensus, EVM/LVM, contracts, ZK (chain 40204)\n citrate-core/ the desktop node app\n citrate-sdk-js/ citrate-sdk-python/ citrate-sdk-marketplace/\n citrate-docs/ this handbook, plus LOCAL_STACK.md\n ... every other public repo\n```\n\nOpen the `citrate-labs/` folder in your IDE and each repository is its own git root.\n\n## Build and audit\n\n- Bring the stack up with `citrate-docs/LOCAL_STACK.md`; a local devnet is `citrate devnet`.\n- Per repo: read its `README.md` and `AUDIT_TIER.md`, then run its tests: `cargo test` (Rust),\n `npm test` (TypeScript), `forge test` (Solidity), `pytest` (Python).\n\n## For AI agents\n\n`.github/AGENTS.md` is the agent-facing brief: it carries this setup, the open-core licensing\nrules, the DCO sign-off requirement, and how to build and audit each repo. Point your agent at\nit, or at this page, and it can set the whole workspace up and start reviewing code.\n\n## Licensing and contributing\n\nCitrate is open-core: the chain, SDKs, docs, explorer, and agent runtime are Apache-2.0; the\ndesktop app and monetized services are source-available under BUSL-1.1. Sign every commit\n(`git commit -s`), and a merged, qualified contribution earns a free or refunded membership.\nSee [`CONTRIBUTING.md`](https://github.com/CitrateNetwork/.github/blob/main/CONTRIBUTING.md).\n"},"/start/open-source":{"slug":"/start/open-source","title":"Open source and access","tier":"public","orgId":null,"sourceKind":"authored","source":"Citrate open-source policy (owner decision, 2026-07-27)","syncedSha":"~","toc":[{"depth":2,"text":"How the licensing works","anchor":"how-the-licensing-works"},{"depth":2,"text":"What is public","anchor":"what-is-public"},{"depth":2,"text":"What stays private","anchor":"what-stays-private"}],"body":"Citrate is open-core, and the code is public today at\n[github.com/CitrateNetwork](https://github.com/CitrateNetwork). The chain and its application layer are\nalready open - you can read the source, build against it, and reproduce the results now, ahead of the Q2\n2027 mainnet. There is no waiting list and no gate on reading the code.\n\n## How the licensing works\n\nThe repositories ship under a two-tier open-core model, with **Citrate Inc.** as the licensor:\n\n- **Infrastructure is Apache-2.0** - permissively licensed, use it however you like. This is the chain, the\n federated-types crate, the node agent, the bundler, NAT, the cooperative contracts, the agent runtime,\n the JavaScript / Python / marketplace SDKs, the docs, and the explorer.\n- **The application layer is BUSL-1.1** - source-available today (you can read, build, and self-host it for\n non-production use), and it converts to Apache-2.0 on its Change Date. This is the inference gateway, the\n compute pool, the cluster, Citrate Core, Comms, Quorum, Identity, Memories, Citrate Native, the\n air-gapped agent, and Studio.\n\nPublishing the source in the open is the stronger position - for the network and for the people who build\non it - than holding it back. The design is public, the audits land against public code, and the\nBUSL Change Date puts the whole application layer on a path to fully permissive licensing.\n\n## What is public\n\nEverything that ships is public at [github.com/CitrateNetwork](https://github.com/CitrateNetwork). A few\nstarting points:\n\n- **NAT** is the model architecture. Memory-safe Rust, formally specified, Apache-2.0. Read the source and\n reproduce the results.\n- **American Learning Federation (ALF)** is the cooperative that trains NAT through federated learning.\n- **agentile-skills** is the engineering methodology, installable by anyone.\n- **The chain, SDKs, and explorer** are Apache-2.0; **Core, the gateway, and the rest of the app layer**\n are BUSL-1.1 and source-available.\n\n## What stays private\n\nA small set of repositories are deliberately closed, and none of them are the network itself:\n\n- **Client and enterprise repositories** - per-customer and on-premise (Citrate Ground / Homestead) work,\n closed for the customers' sake, not ours.\n- **Security and internal repositories** - the security program's private tracker (its history carries\n material that must not be public) and internal federation tooling.\n\nIf you are building on Citrate, start with the public repositories - you do not need to request access to\nread or build the code.\n\n- Contact: [citrate.ai/contact](https://citrate.ai/contact), or email `hello@citrate.ai`.\n- Already building: the chain, SDKs, NAT, and ALF are public now. Start there.\n"},"/start/primer":{"slug":"/start/primer","title":"A primer on the mental models","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain consensus + economics + keyring","syncedSha":"9d5959e","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":3,"text":"Blue score, not height","anchor":"blue-score-not-height"},{"depth":3,"text":"Confirmation by depth","anchor":"confirmation-by-depth"},{"depth":3,"text":"Merge parents","anchor":"merge-parents"},{"depth":3,"text":"Accounts without a seed phrase","anchor":"accounts-without-a-seed-phrase"},{"depth":3,"text":"SALT, the unit you count in","anchor":"salt-the-unit-you-count-in"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Five ideas make the rest of Almanac click. If your intuition comes from a single-chain world, these are the\nplaces it needs to bend. Read [what Citrate is](/start/what-is-citrate) first if you have not.\n\n## What it is\n\nA short tour of the concepts the deeper pages assume. Each one ends with a pointer to where it is treated\nin full.\n\n### Blue score, not height\n\nOn a single-parent chain, \"block N\" is unambiguous. On a BlockDAG, a block can name several parents, so\nheight alone cannot order the ledger.\n\n- **Height** is roughly how many layers deep a block sits. It is useful, but it is not the ordering key.\n- **Blue score** is the ordering key GhostDAG uses: the cumulative count of a block's blue ancestors, the\n honest-majority-consistent set picked out by the k-cluster rule (k = 18). The tip with the highest blue\n score wins.\n\nBlue score is the DAG's clock. When you read `citrate_getDagStats`, the field that tells you the head is\n`maxBlueScore`, not `height`. Full detail under [Citrate Network](/chain/consensus).\n\n### Confirmation by depth\n\nConfirmation on the testnet is probabilistic: a block gains weight as later blocks build on it, so the\ndeeper a block sits behind the selected tip, the more work a competing branch would need to displace it.\nThere is no protocol finality point today. A finality depth of 100 (`finality_depth`) and a BFT\ncheckpoint committee are specified in the code, but checkpoint finality is not running. The explorer's depth≥100 flag is a display heuristic, not a\nsettlement guarantee. See [consensus, current status](/chain/consensus#current-status).\n\n### Merge parents\n\nA block names one **selected parent**, the place on the chain it builds on, plus zero or more **merge\nparents**, other tips it folds into the order. Merging is how the DAG stays one ledger instead of forking:\na block absorbs its sibling tips rather than orphaning them. Selected parent is where you stand; merge\nparents are the siblings you are folding in. The block's mergeset is then interleaved into the canonical\norder. See [Citrate Network](/chain/consensus).\n\n### Accounts without a seed phrase\n\nCitrate accounts live in **Citrate Keyring**. An account is a smart contract, and you sign in with a\npasskey (WebAuthn over P-256) or an existing key, so there is no seed phrase to lose. Transactions are sent\nas user operations through a bundler, and a sponsor contract can pay the fee, so you can transact with no\nSALT in hand. Recovery is by guardians, two to seven of a set you choose, and Citrate is never one of your\nguardians. The account is a contract, the key is a passkey, and someone else can cover the fee. See\n[Citrate Keyring](/aa/passkeys), [sponsorship](/aa/paymaster), and [guardians](/aa/guardians).\n\n### SALT, the unit you count in\n\nSALT has 18 decimals and a one-trillion supply cap, and it settles fees, block rewards, and staking. Amounts\nin the API are integers in the smallest unit, where 10^18 is one SALT, and supply is minted minus burned,\nheld under the cap. SALT measures the work the network does; it is not a product to hold. See\n[economics](/chain/economics).\n\n## How to use it\n\nPut the five together and a transaction's life reads cleanly. You sign a user operation with a passkey, a\nbundler submits it, the execution layer runs it (often alongside others, in parallel), and it lands in a\nblock that names a selected parent and maybe some merge parents. GhostDAG assigns the block a blue score\nand places it in the total order. Once the block is 100 deep, it is final. The fee and any reward are\ndenominated in SALT. If you can hold that sentence in your head, the rest of Almanac will read easily.\n\n## Reference\n\n| Idea | The key fact | Where it is treated in full |\n|---|---|---|\n| Blue score | ordering key; `maxBlueScore` is the head | [consensus](/chain/consensus) |\n| Finality | probabilistic confirmation; checkpoint finality is specified, not running | [consensus, current status](/chain/consensus#current-status) |\n| Merge parents | one selected parent, many merge parents | [consensus](/chain/consensus) |\n| Citrate Keyring | smart-contract account, passkey, sponsored fees | [Citrate Keyring](/aa/passkeys) |\n| SALT | 18 decimals, 1T cap, settles work | [economics](/chain/economics) |\n\n## Access and canon\n\nPublic. These are conceptual explainers only, with no secrets, keys, or private endpoints. The proofs\nbehind GhostDAG and finality are academic-tier and live on the linked Citrate Network pages.\n\n## Source and verification\n\nThe numbers (k = 18, finality depth 100, SALT 18 decimals and 1T cap, chain id 40204) are verified against\n`citrate-chain` at `9d5959e`: `core/consensus/src/types.rs` and `core/api/src/economics_rpc.rs`, surfaced\nthrough the [consensus](/chain/consensus) and [economics](/chain/economics) pages. Status: Implemented\n(testnet).\n"},"/start/roadmap":{"slug":"/start/roadmap","title":"The roadmap","tier":"public","orgId":null,"sourceKind":"authored","source":"Citrate mission + program plan","syncedSha":"9d5959e","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Where Citrate is today and what comes next. The network is live on testnet now; mainnet is targeted for the\nsecond quarter of 2027. This page is the plan, so it carries a Specified status: the dates are commitments\nwe are working toward, not facts already recorded on a ledger.\n\n## What it is\n\nA plain account of the path from testnet to mainnet, in the order the work lands. We would rather state the\ntimeline and be held to it than imply everything already exists.\n\n## How to use it\n\nRead this to decide when to build, pilot, or wait. If you are writing code, the testnet is ready for you\nnow, and the [tutorials](/start/tutorials/your-first-10-minutes) work against it today. If you run a public\ninstitution, the school program below is the path in. If you are evaluating for production, the mainnet\ntarget is the date to plan around.\n\n## Reference\n\n| Phase | Status | What it means |\n|---|---|---|\n| Testnet, chain id 40204 | Implemented | The public network is live. RPC, the SDKs, contracts, and the model calls in the tutorials all run against it today. |\n| School pilots | Specified, summer 2026 | The first Citrate Schools deployments: US K-12 public schools running on Citrate Ground, free in perpetuity. |\n| Mainnet | Specified, Q2 2027 | The production network. Chain id 40204 carries forward from testnet; it is permanent. |\n\nThe chain id does not change between testnet and mainnet. 40204 is canonical and permanent, so addresses,\ntooling, and integrations you build against testnet carry over.\n\n## Design rationale\n\nWe sequence pilots ahead of mainnet on purpose. Citrate is built for institutions that cannot move their\ndata, and the only honest way to prove on-premise sovereignty works is to run it inside real schools before\nwe ask anyone to depend on the production network. The pilots are the evidence; mainnet is what they earn.\n\n## Access and canon\n\nPublic. This page states the mainnet target (Q2 2027) and the pilot window. Where a phase is still ahead of\nus it is labeled Specified, and where it is live it is labeled Implemented, so the status is never\noverstated.\n\n## Source and verification\n\nThe chain id (40204, permanent) is verified against `citrate-chain` at `9d5959e` (`cli/src/config.rs`). The\ntimeline reflects the program plan and is stated as a target, not a recorded fact. Status: Specified, with\nthe testnet phase Implemented.\n"},"/start/tutorials/your-first-10-minutes":{"slug":"/start/tutorials/your-first-10-minutes","title":"Your first 10 minutes","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain core/api","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Step 1, confirm you are on Citrate","anchor":"step-1-confirm-you-are-on-citrate"},{"depth":3,"text":"Step 2, read the BlockDAG","anchor":"step-2-read-the-blockdag"},{"depth":3,"text":"Step 3, read the SALT token","anchor":"step-3-read-the-salt-token"},{"depth":3,"text":"Step 4, run a model call on the chain","anchor":"step-4-run-a-model-call-on-the-chain"},{"depth":3,"text":"Step 5, pick your next five minutes","anchor":"step-5-pick-your-next-five-minutes"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"A short, copy-paste tour. You will confirm you are on Citrate, read the BlockDAG, look at the SALT token,\nand run a model call on the chain, then point yourself at the right next page. Every method here exists in\n`citrate-chain`. The read-only steps need no account, no SALT, and no signup.\n\n## What it is\n\nA ten-minute orientation against a live node. Nothing here writes state, so you can run it against any\nCitrate endpoint you can reach without risk.\n\n## How to use it\n\nYou will need a reachable Citrate JSON-RPC endpoint. A local node serves `http://127.0.0.1:8545`. If you do\nnot have one, use the [RPC sandbox](/sandboxes/rpc) instead: same methods, in the browser. You will also\nwant `curl`, and `jq` for readable output.\n\nSet up a small helper so the steps stay short:\n\n```bash\nexport RPC=http://127.0.0.1:8545\n\nrpc () {\n curl -s \"$RPC\" -H 'content-type: application/json' \\\n -d \"{\\\"jsonrpc\\\":\\\"2.0\\\",\\\"id\\\":1,\\\"method\\\":\\\"$1\\\",\\\"params\\\":${2:-[]}}\"\n}\n```\n\n### Step 1, confirm you are on Citrate\n\n```bash\nrpc eth_chainId\n# {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x9d0c\"}\nprintf '%d\\n' 0x9d0c # 40204\n```\n\n`0x9d0c` is 40204, and 40204 is Citrate. Anything else means you are pointed at a different network.\n\n### Step 2, read the BlockDAG\n\n```bash\nrpc citrate_getDagStats | jq\n```\n\n```json\n{\n \"tipsCount\": 3,\n \"maxBlueScore\": 11800,\n \"currentTips\": [\"0x...\", \"0x...\", \"0x...\"],\n \"height\": 12345,\n \"ghostdagParams\": { \"k\": 18, \"maxParents\": 10, \"finalityDepth\": 100 }\n}\n```\n\nTwo things to notice. `maxBlueScore` is the DAG's ordering clock, not `height`. And `tipsCount` above one\nis normal: several tips can exist at once on a BlockDAG, and GhostDAG merges them into a single order. If\nthose words are new, read the [primer](/start/primer).\n\n### Step 3, read the SALT token\n\n```bash\nrpc citrate_getToken | jq\n# { \"name\": \"Citrate\", \"symbol\": \"SALT\", \"decimals\": 18, \"totalSupply\": \"0x...\", \"totalMinted\": \"0x...\" }\n```\n\nSALT has 18 decimals and a one-trillion cap (`TOTAL_SUPPLY` in `core/economics/src/lib.rs`). It is the\nunit fees and rewards are counted in. Detail: [economics](/chain/economics).\n\n### Step 4, run a model call on the chain\n\nCitrate runs inference as a chain operation, not as an outside service you trust. Generate an embedding with\nthe genesis model:\n\n```bash\nrpc citrate_getTextEmbedding '[\"the quick brown fox\"]' | jq '.result | length'\n# 1024\n```\n\nOr rank a short corpus by meaning:\n\n```bash\nrpc citrate_semanticSearch \\\n '[\"best network for AI compute\", [\"a payments network\",\"a substrate for AI compute\",\"a meme coin\"], 1]' | jq\n# [{ \"index\": 1, \"score\": 0.82, \"text\": \"a substrate for AI compute\" }]\n```\n\nA single call accepts at most 256 inputs, which is a denial-of-service guard (`MAX_EMBEDDING_INPUTS`).\n\n### Step 5, pick your next five minutes\n\n| If you want to | Go to |\n|---|---|\n| Understand the words you just saw | [the primer](/start/primer) |\n| See every RPC method | [JSON-RPC reference](/chain/rpc) |\n| Go deeper on these same calls | [call the Citrate RPC](/chain/tutorials/call-citrate-rpc) |\n| Deploy a contract | [deploy with the CLI](/chain/tutorials/deploy-a-contract-with-the-cli) |\n| Get an account with no seed phrase | [sign in with a passkey](/aa/tutorials/sign-in-with-a-passkey) |\n| Learn how the project is run | [the Agentile primer](/start/agentile) |\n\n## Reference\n\nThe methods used above, with their source files in `citrate-chain`:\n\n| Method | What it returns | Source |\n|---|---|---|\n| `eth_chainId` | the chain id, `0x9d0c` | `core/api/src/eth_rpc.rs` |\n| `citrate_getDagStats` | tips, blue score, GhostDAG params | `core/api/src/eth_rpc.rs` |\n| `citrate_getToken` | SALT name, decimals, supply | `core/api/src/economics_rpc.rs` |\n| `citrate_getTextEmbedding` | an embedding vector | `core/api/src/ai_rpc.rs` |\n| `citrate_semanticSearch` | corpus entries ranked by meaning | `core/api/src/ai_rpc.rs` |\n\n## Failure modes\n\n- **`Connection refused`** means no node is listening on `$RPC` (the default is `127.0.0.1:8545`). Use the\n [RPC sandbox](/sandboxes/rpc) instead.\n- **`-32601 Method not found`** is a typo or a method the node does not serve. The chain, DAG, and model\n methods above do not require an economics manager to be configured.\n- **A chain id other than `0x9d0c`** means you are not on Citrate.\n\n## Access and canon\n\nPublic and read-only. No keys or credentials are needed, and nothing here writes state. The example outputs\nare illustrative; exact values depend on the node's current state.\n\n## Source and verification\n\nMethods verified against `citrate-chain` at `e68af83` (`core/api/src/ai_rpc.rs`, `economics_rpc.rs`,\n`eth_rpc.rs`), and surfaced in full on the [JSON-RPC reference](/chain/rpc). Status: Implemented\n(testnet).\n"},"/start/what-is-citrate":{"slug":"/start/what-is-citrate","title":"What Citrate is","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain + Citrate mission","syncedSha":"9d5959e","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Citrate is a substrate for AI compute. It is the ground that models run on, not a model itself. You bring\nthe data and the weights; Citrate gives them somewhere verifiable to run, on hardware you control, with a\npublic record of the work that anyone you authorize can check.\n\n## What it is\n\nThe network has two halves that work together. The **Citrate Network** is a public ledger: a BlockDAG\nwritten in Rust, live on chain id 40204 in testnet today. **Citrate Ground** is the private half: a school,\na hospital, or a contractor runs Citrate on their own machines, and their data and models stay there. The\npublic ledger only ever sees what an operator chooses to publish.\n\nThree properties hold across the whole network, and the rest of Almanac assumes them:\n\n- **On-premise by default.** Your data and your models stay on your hardware. Publishing anything to the\n public ledger is a deliberate step, taken inside the compliance envelope you set.\n- **Verified participation.** Membership includes identity verification through VERI, Citrate's in-house verification; node and consensus code do not check it.\n Citrate keeps the verification result, not the personal data behind it.\n- **Work, not speculation.** SALT settles the work the network performs. It pays for compute and rewards\n contribution. It is the unit you count in, not a product to hold, and Almanac does not treat it as one.\n\nUnderneath, the Citrate Network is EVM-compatible: existing Solidity, tooling, and signing libraries work\nagainst it. What makes it a substrate for AI rather than a general ledger is that inference, embeddings,\nand verifiable model calls are first-class operations on the chain, not an outside service you have to\ntrust. The consensus that orders all of it is GhostDAG, which is covered in the [primer](/start/primer)\nand in full under [Citrate Network](/chain/consensus).\n\n## How to use it\n\nPick the path that matches why you are here.\n\n1. **You write code.** Read the [primer](/start/primer), then [chain RPC](/chain/rpc) and the\n [JavaScript SDK](/sdks/js). Confirm you are pointed at Citrate, then read the DAG and make your first\n model call in [your first 10 minutes](/start/tutorials/your-first-10-minutes).\n2. **You operate hardware.** Read [run a node](/operators/run-a-node) and [sell compute](/operators/sell-compute).\n A node is how idle GPUs earn SALT on Citrate Market.\n3. **You run models on your own data.** Read [Citrate Ground](/enterprise/federal) and\n [federated learning](/research/learning), where models train across nodes without the data leaving them.\n4. **You are evaluating the network.** Read the [roadmap](/start/roadmap) for the path to mainnet, then the\n [Gradient Papers](/research/gradient-papers) for the research the design rests on.\n\n## Reference\n\nThe surfaces you will meet across Almanac, named once here so the names are familiar later.\n\n| Surface | What it is |\n|---|---|\n| Citrate Network | the public ledger, the BlockDAG and its contracts |\n| Citrate Ground | a private, on-premise instance behind your own firewall |\n| Citrate Market | where compute is bought and sold |\n| Citrate Orchard | the federated-learning surface, where models train across nodes |\n| Citrate Node | the daemon an operator runs to contribute compute |\n| Citrate Keyring | your account, your keys, and account recovery |\n| Citrate Schools | the program giving US K-12 public schools free access in perpetuity |\n\nTo confirm you are talking to Citrate and not another network, ask the node for its chain id:\n\n```bash\ncurl -s http://127.0.0.1:8545 -H 'content-type: application/json' \\\n -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_chainId\",\"params\":[]}'\n# {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x9d0c\"} # 0x9d0c is 40204\n```\n\n## Design rationale\n\nMost networks ask you to move your data to where the compute is. For a hospital or a school district, that\nis a non-starter, and for good reason. Citrate inverts it: the compute is verified and the work is\nrecorded, but the data stays put. That is why Ground is the default and the public ledger is opt-in, and it\nis why participation is identity-checked rather than anonymous. The cost is that joining takes a real-world\nverification step. We think that is the right trade for the institutions Citrate is built to serve.\n\n## Access and canon\n\nPublic. This is the front door, the concepts you need to decide whether to build on Citrate. No secrets,\nkeys, or private endpoints appear here. Deeper pages carry implementation detail and are tiered to the\naudience that needs them.\n\n## Source and verification\n\nChain facts verified against `citrate-chain` at `9d5959e`: chain id 40204 (`eth_chainId` returns `0x9d0c`,\nsee `cli/src/config.rs` and `cli/src/commands/advanced.rs`), GhostDAG parameters in\n`core/consensus/src/types.rs`, SALT supply in `core/api/src/economics_rpc.rs`. The network is live on\ntestnet; mainnet is targeted for Q2 2027, with school pilots the prior summer. Status: Implemented\n(testnet).\n"},"/sdks/api-reference":{"slug":"/sdks/api-reference","title":"SDK API reference (generated)","tier":"public","orgId":null,"sourceKind":"transcluded","source":"scripts/gen-api-refs.mjs","toc":[{"depth":2,"text":"JavaScript, @citratelabs/sdk","anchor":"javascript-citratelabssdk"},{"depth":3,"text":"identity","anchor":"identity"},{"depth":3,"text":"entitlements","anchor":"entitlements"},{"depth":2,"text":"Python, citrate-labs-sdk","anchor":"python-citrate-labs-sdk"},{"depth":3,"text":"citrate_sdk.identity","anchor":"citrate_sdkidentity"},{"depth":3,"text":"citrate_sdk.entitlements","anchor":"citrate_sdkentitlements"},{"depth":3,"text":"citrate_sdk.gateway","anchor":"citrate_sdkgateway"},{"depth":2,"text":"Command line","anchor":"command-line"},{"depth":3,"text":"citrate (Python)","anchor":"citrate-python"}],"body":"This page is generated by `scripts/gen-api-refs.mjs` from the SDK sources on each build, so it\nstays in sync with the code. It is the exported public surface; the narrative reference with\nexamples lives in [identity](/sdks/identity), [entitlements](/sdks/entitlements), and the\n[JavaScript](/sdks/js) and [Python](/sdks/python) pages.\n\nSources: `citrate-sdk-js@328bdea`, `citrate-sdk-python@850b3c1`.\n\n## JavaScript, @citratelabs/sdk\n\n### identity\n\n| Symbol | Kind | Summary |\n|---|---|---|\n| `UserId` | type | A 0x-prefixed 32-byte hex user id (the raw stable identifier the factory salts with) |\n| `uuidToUserId` | function | Derive the raw 32-byte AA userId from an OIDC subject UUID |\n| `addressToUserId` | function | Left-pad a 20-byte EOA address to a 32-byte AA userId (SIWE-keyed principals) |\n| `predictWalletAddress` | function | Predict the counterfactual smart-wallet address for a userId. Pure + offline |\n| `verifyWalletAddressOnChain` | function | Verify the locally-predicted address against the on-chain factory (ground truth) |\n| `generateVerifier` | function | A 43-char base64url verifier (256 bits of entropy) |\n| `challengeFromVerifier` | function | S256 challenge for a verifier |\n| `verifyIdToken` | function | Verify an OIDC ID token and return its (now-trusted) claims. Throws IdTokenError on any failure |\n| `DeployPermit` | interface | A factory deploy permit signed by the authority's identity-signer |\n\n### entitlements\n\n| Symbol | Kind | Summary |\n|---|---|---|\n| `TIERS` | const | The five tiers the authority mints (mirrors citrate-identity `TIERS`) |\n| `CapabilitySet` | interface | What a principal may do. Explicit set membership — never derived from an ordering |\n| `DEFAULT_CAPABILITIES` | const | The canonical default tier→capability map |\n| `normalizeTier` | function | Normalize an entitlement tier value at the trust boundary. Unknown/garbage collapses to |\n| `EntitlementClaimLike` | interface | The minimal shape of the entitlement claim this module reads |\n| `capabilities` | function | Capabilities for a raw tier value (normalized first) |\n| `can` | function | Whether a claim grants a capability. Applies the same fail-safe + role-bypass semantics as |\n\n## Python, citrate-labs-sdk\n\n### citrate_sdk.identity\n\n| Symbol | Kind | Summary |\n|---|---|---|\n| `WalletPredictionError` | class | |\n| `uuid_to_user_id` | def | keccak256(utf8(lowercase(uuid))) — matches the authority's wallet-claims.ts |\n| `address_to_user_id` | def | Left-pad a 20-byte EOA to a 32-byte AA userId (SIWE-keyed principals) |\n| `generate_verifier` | def | A 43-char base64url verifier (256 bits of entropy) |\n| `challenge_from_verifier` | def | |\n| `Pkce` | class | |\n| `create_pkce` | def | |\n| `IdTokenError` | class | |\n| `IdentityError` | class | |\n| `TokenSet` | class | |\n| `UserInfo` | class | |\n| `IdentityClient` | class | |\n\n### citrate_sdk.entitlements\n\n| Symbol | Kind | Summary |\n|---|---|---|\n| `CapabilitySet` | class | |\n| `normalize_tier` | def | Fail-safe: unknown/garbage/non-str collapses to ``public``. Never escalates |\n| `capabilities` | def | |\n\n### citrate_sdk.gateway\n\n| Symbol | Kind | Summary |\n|---|---|---|\n| `GatewayError` | class | |\n| `GatewayClient` | class | |\n\n## Command line\n\n### citrate (Python)\n\n```text\nusage: citrate [-h] {contract,wallet,entitlement,gateway} ...\n\nCitrate SDK command line\n\npositional arguments:\n {contract,wallet,entitlement,gateway}\n contract Print the federation contract table\n wallet Embedded smart-account wallet helpers\n entitlement Entitlement capabilities\n gateway Inference gateway\n\noptional arguments:\n -h, --help show this help message and exit\n```\n"},"/chain/addresses":{"slug":"/chain/addresses","title":"Contract addresses","tier":"public","orgId":null,"sourceKind":"transcluded","source":"citrate-chain/contracts/addresses/40204.json","syncedSha":"de518be8","toc":[{"depth":2,"text":"Core contracts","anchor":"core-contracts"},{"depth":2,"text":"Membership","anchor":"membership"},{"depth":2,"text":"Account abstraction","anchor":"account-abstraction"},{"depth":2,"text":"Precompiles","anchor":"precompiles"}],"body":"This is the canonical list of contract addresses on chain 40204 (Citrate Network). It is generated\nfrom the federation address book (`citrate-chain/contracts/addresses/40204.json`), the single source of truth\nevery application reads from, and is regenerated after each re-roll or address fan-out. As of the book at\ncommit `de518be8`, deployed 2026-09-12 14:16:34UTC.\n\nNot every entry in the book is deployed. At block 178426 (2026-09-25T05:28:37Z), 57 of the 76\napplication and account-abstraction entries have code on chain; rows marked **not deployed** have none. A call to a\nnot-deployed address returns empty data, and a value transfer to one succeeds and strands the value, so check the\nstatus column before you send anything. Re-check any address yourself with\n`cast code
--rpc-url https://rpc.citrate.ai`.\n\nThe core and account-abstraction addresses are deterministic (CREATE2 through the genesis factory), so a\nre-roll moves them together and this page moves with them. The membership contracts are the exception (see below). The RPC endpoint is `https://rpc.citrate.ai` and the deployer is `0x4fAB35c8c5033c80b3a0452A873B81e6ED4ED732`.\n\n## Core contracts\n\n| Contract | Address | Status |\n|---|---|---|\n| `ModelRegistry` | `0xba36fa0da9327030bd14351db968c8c43c5a67e4` | deployed |\n| `SkillRegistry` | `0x2B687899EF4aF05A18F4f36cE1fE9d51c017A97c` | deployed |\n| `WrappedSALT` | `0xaa918302b94a4b0e75e01e019cc6b819b4f7c906` | deployed |\n| `AgentDecisionRegistry` | `0xd4008e0b4f0bd00d630810d1f7f0f78db0ba837a` | deployed |\n| `SpecRegistry` | `0x8ce7000c83d0ef5276a70bdc34bf2fa2fe0159ff` | deployed |\n| `IPFSIncentives` | `0xb79e438bc8c68f7d94cf694eb0ec8eae40525680` | deployed |\n| `X402Facilitator` | `0xae0d2ddc74732df4424d2a89c0815cba84be37e7` | deployed |\n| `X402Paywall` | `0xca98b1678a3127a4d605ac3b37454646adbf5453` | deployed |\n| `LiquidStakingPool` | `0xead6a4a47c528ecea2a86cd9d9af7504d7a5e30e` | deployed |\n| `ContributionAccounting` | `0xd00d442c735c16d00f04ae31a180c78eec5ec32f` | deployed |\n| `NematocystSlashing` | `0xfeb23abd20084d36a1145da8a2dc04e8b48f65c7` | deployed |\n| `MarketMakerAllocation` | `0xfcc747d35d616c48bddef98a31b7e8ebc8786864` | deployed |\n| `ModelMarketplace` | `0xbd94012b113c81843dc66196d13fe0667651a7f0` | deployed |\n| `InferenceRouter` | `0x00463e63a5645de75083460f5f1ee108d0870815` | deployed |\n| `LoRAFactory` | `0xbb7eeb6286a756b0e23af2ead3e03acca72f9e39` | deployed |\n| `LearningPool` | `0xd973cc744f9fd8da55a8b08cde303d5b59a29771` | deployed |\n| `LearningCycleManager` | `0xcce506d1f270f954b726c552879ebc19f3719035` | deployed |\n| `ClassroomRegistry` | `0x124f5f69691e0963c3a7c4d9497d1e224568ffb3` | deployed |\n| `MentorMatcher` | `0x78ca904036cd144b55f6dd07bc3e36603c6c089b` | deployed |\n| `ComputeVerifier` | `0x067c16ea5c2b90045607d9b33c127606e67e61da` | deployed |\n| `ComputeMarketplace` | `0x527e636389a46784b9537690716db00d4ab987d4` | deployed |\n| `ComputePool` | `0xcd778fc9820ac8cada5cd95aa7cddf6e4ca4d375` | deployed |\n| `HeartbeatMonitor` | `0xe9eaac272844f342266862bbefc6d117a227ad9b` | deployed |\n| `DisputeResolution` | `0x4562d2a68063a61b83683aae301fe0f480e4f03a` | deployed |\n| `ComputePricingOracle` | `0x10b5c17d6f018631fc221594ed8b8bb003c9c975` | deployed |\n| `StablecoinTreasury` | `0x0e9c5953bd7c77252119e32f989ba94f735c8599` | deployed |\n| `BulkComputeGateway` | `0xf55f743e4a20557f03fdaa3cc43b0d2354c73c79` | deployed |\n| `TestnetFarmingAccounting` | `0x220cc378641607df8ff9cff6e985d67331704ca4` | deployed |\n| `TreasuryGovernor` | `0xab7c486db6377225453a04a0bf7161291f2611b1` | deployed |\n| `InstitutionalVault` | `0xb38a64922fad87e86e36254dc2fd65a971eb211e` | deployed |\n| `ClassroomClusterV1` | `0xdd6bad78e88147a46f502e02ff56808916c8c4b8` | deployed |\n| `BudgetAllocation` | `0x220a8dbb48ba3dfbe2c4f5ae162c5e5b6dc2351e` | deployed |\n| `CashoutRequest` | `0xaeb938bf9eedcffb14ab2db1e8787e591b539700` | deployed |\n| `AIModelRegistryPortable` | `0xda30a0408b1690afa739fb63901a6608547f4da6` | deployed |\n| `AIInferenceRouterPortable` | `0x85b04c554ee0137818a0e9acbe6d5f8f4b6ef1d7` | deployed |\n| `AILearningCycleCorePortable` | `0x615297a23f954681ef4b648eaaf722455eca925c` | deployed |\n| `ModelAccessControl` | `0xc68f19c4f3e1fae734ca0a053af8a5b34ed98c63` | deployed |\n| `TEEAttestationRegistry` | `0x0834a05a5607af5ff10dade01e1c96cd6e83bd8b` | deployed |\n| `ComputePoolTraining` | `0x0858b110dfa9c61df34b9d57576e751229b900b7` | deployed |\n| `KYCRegistry` | `0xf72248f5dfe5c8dab3047ae52958aa65b216be8f` | deployed |\n| `IPFSIncentivesV2` | `0x951ddc6316efbeda36dcb940e4d81747415b8500` | deployed |\n| `IPFSIncentivesV3` | `0xc27a867b8d076d77cf17981f235c64a0d0203a68` | deployed |\n| `AggregationChallenge` | `0xe7d7ebe1242feec29d514b00c9272fbffc9e69be` | deployed |\n| `ComputePoolPipeline` | `0xc05a38141bb095275f8dc24dfbbcf69722cd1a3b` | deployed |\n| `ValidatorRegistry` | `0x2655d9fbbe599e75ff6e53790f99ebc9a20c93bf` | deployed |\n| `EduForwarder` | `0xe4c6aa7afd77e24c838f8a490aae6f34b286faff` | deployed |\n| `AnchorRegistry` | `0xfeaacf58d9a38c60cbc473c3abea55dd629cf660` | **not deployed** (no code) |\n| `MeetingRegistry` | `0x8fffde6f66901b30adcd1544763c279ca1e1b30a` | **not deployed** (no code) |\n| `GovernanceTemplateRegistry` | `0x90a3d1ccc159501833d1160190a58470db1a0a88` | **not deployed** (no code) |\n| `GovernanceProtocolFactory` | `0xbba38be5c9a0ad00c7d24430b53a7049f46c9b3f` | **not deployed** (no code) |\n| `PolicyBinding` | `0x76c41259d1454d983a2def855d532bfd67e10ea0` | **not deployed** (no code) |\n| `CapabilityGrant` | `0x3139e17e23914e9442e126228f5b14658b50a449` | **not deployed** (no code) |\n| `VoteAllowance` | `0xee0f77fb2e6f5f31ac8b5df14932ba4715b558bd` | **not deployed** (no code) |\n| `Sortition` | `0x1eabce0dddb74f5c144c7f452a7d27292c62e900` | **not deployed** (no code) |\n| `PatronageLedger` | `0x726f2c8a0bfa4145dca7c154577705803c8dafa3` | **not deployed** (no code) |\n| `ModelCooperative` | `0x54b70368373b0b22ac8ad9882961228d790133bf` | **not deployed** (no code) |\n| `FacilitySBTImpl` | `0xa8ad418a0be3877a797f183bade8dc52b1608ad0` | **not deployed** (no code) |\n| `NetworkSBTImpl` | `0x3a6ff326f83cd77ed936dcb1620ece2f5b41d7af` | **not deployed** (no code) |\n| `FacilitySBT` | `0x2520b5307752318b03047cf547b38b99311f65fb` | **not deployed** (no code) |\n| `NetworkSBT` | `0x062b355f67b8252054ad59c220b3aac1cd0a0ff6` | **not deployed** (no code) |\n| `CitrateMemberSBT` | `0xf0badd9eed5a81871a2f0d309b1f0a225646448a` | deployed |\n| `MemberBond` | `0x7d6b92757e928ab4207be3b54166ecd2c491aa92` | deployed |\n| `MembershipStakeVaultImpl` | `0x72035977f3ec295c70e2a734acbdffb0c98e6f0b` | deployed |\n| `MembershipStakeVault` | `0x53fb4badffaceedd575d47d0e74bb721504f786e` | deployed |\n| `CitrateCooperativeFactory` | `0xd4750aa00f0634cb2d5154dfc19eb8dcbc885e9f` | **not deployed** (no code) |\n| `CoopDeployer` | `0xccdfcb866f42dcde3aa19d2aa1434e4c3c6a3b48` | **not deployed** (no code) |\n| `CoopMembershipSBT` | `0xb455c14880aca8eeddf95f6e1dcfddb13d8b6a83` | **not deployed** (no code) |\n| `ContributionRewardPool` | `0x2aee5a81e0fa056d2e6949d71aaf96456218b6c8` | **not deployed** (no code) |\n| `CoopGovernor` | `0x8046c10f1bb4bf58cedb4a7a55ebfa7f8b09e64b` | **not deployed** (no code) |\n\n## Membership\n\nThe membership soulbound token and stake vault are top-level entries in the book. They are deployed by\nnonce rather than through the CREATE2 factory, so their addresses change at every re-roll; always read them\nfrom the book.\n\n| Contract | Address | Status |\n|---|---|---|\n| `CitrateMemberSBT` | `0xf0bADD9Eed5A81871a2F0D309b1f0a225646448a` | deployed |\n| `MembershipStakeVault` | `0x53fB4baDfFacEEDD575D47D0E74Bb721504F786e` | deployed |\n\n## Account abstraction\n\nThe Citrate Keyring account stack (ERC-4337). See [the Keyring section](/aa/identity) for how these fit\ntogether.\n\n| Contract | Address | Status |\n|---|---|---|\n| `EntryPoint` | `0x97d5391a647429233e202f99231743c53a648f3c` | deployed |\n| `CitrateWallet` | `0x2D742B98D867Fc7363F530DD6d756622e4Eb768D` | deployed |\n| `CitrateWalletFactory` | `0x86486d1de9f256e2cba327c46ac11120df0aa51a` | deployed |\n| `CitratePaymaster` | `0xfdc9f7a72163b5d45becdb8a9d8d44b970f77318` | deployed |\n| `WebAuthnP256Validator` | `0x0f421a99a0b8f6138dea12f45a523cb896d09fc7` | deployed |\n| `CitrateECDSAValidator` | `0xd2d35421379ae5b461e216bfcdd1b7e6a64bbc40` | deployed |\n| `GuardianRecoveryModule` | `0x0a909769160c1945401b8f37a9310d37dbb6a891` | deployed |\n\n## Precompiles\n\nPrecompiles are fixed genesis addresses and do not move across re-rolls.\n\n| Contract | Address | Status |\n|---|---|---|\n| `ModelDeploy` | `0x0000000000000000000000000000000000000100` | precompile (no code by design) |\n| `ModelInference` | `0x0000000000000000000000000000000000000101` | precompile (no code by design) |\n| `BatchInference` | `0x0000000000000000000000000000000000000102` | precompile (no code by design) |\n| `ModelMetadata` | `0x0000000000000000000000000000000000000103` | precompile (no code by design) |\n| `ModelBenchmark` | `0x0000000000000000000000000000000000000105` | precompile (no code by design) |\n| `ModelEncryption` | `0x0000000000000000000000000000000000000106` | precompile (no code by design) |\n| `TensorCommit` | `0x0000000000000000000000000000000000000107` | precompile (no code by design) |\n| `InferenceProofVerify` | `0x0000000000000000000000000000000000000108` | precompile (no code by design) |\n| `MerkleVerifyTensor` | `0x0000000000000000000000000000000000000109` | precompile (no code by design) |\n| `TensorMatmulQ16` | `0x000000000000000000000000000000000000010a` | precompile (no code by design) |\n| `TensorDotQ16` | `0x000000000000000000000000000000000000010b` | precompile (no code by design) |\n| `TensorSoftmaxQ16` | `0x000000000000000000000000000000000000010c` | precompile (no code by design) |\n| `TensorReluQ16` | `0x000000000000000000000000000000000000010d` | precompile (no code by design) |\n| `TensorLinearQ16` | `0x000000000000000000000000000000000000010e` | precompile (no code by design) |\n| `TensorTransposeQ16` | `0x000000000000000000000000000000000000010f` | precompile (no code by design) |\n| `BelnapAggregate` | `0x0000000000000000000000000000000000000110` | precompile (no code by design) |\n| `RoutingInference` | `0x0000000000000000000000000000000000000111` | precompile (no code by design) |\n| `Ed25519Verify` | `0x0000000000000000000000000000000000000120` | precompile (no code by design) |\n| `X402Eip712Verify` | `0x0000000000000000000000000000000000000200` | precompile (no code by design) |\n| `X402TransferAuthVerify` | `0x0000000000000000000000000000000000000201` | precompile (no code by design) |\n| `X402BatchPaymentVerify` | `0x0000000000000000000000000000000000000202` | precompile (no code by design) |\n\n"},"/start/changelog":{"slug":"/start/changelog","title":"Changelog","tier":"public","orgId":null,"sourceKind":"transcluded","source":"mem-gateway memory.recall over the federation memory graph","syncedSha":"live","toc":[],"body":"The changelog draws recent activity from the Citrate memory graph, the same\nsigned, code-anchored knowledge store that powers Ask Almanac. It is regenerated\non every deploy from `memory.recall` across the federation repositories.\n\nLive entries appear here once the docs build can reach the memory gateway. To\nsee current activity in the meantime, ask Ask Almanac what changed recently in a\ngiven area, or browse the source repositories directly.\n"}}; +export const CONTENT_DOCS: Record = {"/aa/contracts":{"slug":"/aa/contracts","title":"Account-abstraction contracts","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/contracts/src/aa/* (+ contracts/src/edu/Forwarder.sol)","syncedSha":"9d5959e","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"CitrateWallet","anchor":"citratewallet"},{"depth":3,"text":"CitrateWalletFactory","anchor":"citratewalletfactory"},{"depth":3,"text":"CitratePaymaster","anchor":"citratepaymaster"},{"depth":3,"text":"CitrateECDSAValidator","anchor":"citrateecdsavalidator"},{"depth":3,"text":"WebAuthnP256Validator","anchor":"webauthnp256validator"},{"depth":3,"text":"GuardianRecoveryModule","anchor":"guardianrecoverymodule"},{"depth":3,"text":"Forwarder, EIP-2771, cross-reference","anchor":"forwarder-eip-2771-cross-reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The contract reference for Citrate Keyring, the account-abstraction stack that gives a Citrate user a\nsmart-contract account with no seed phrase. This is the on-chain half: the account implementation, the\naccount factory, the gas paymaster, two signer validators, and the guardian recovery module. It pairs with\nthe concept pages at [passkeys](/aa/passkeys), [guardians](/aa/guardians), [paymaster](/aa/paymaster), and\n[identity](/aa/identity), and with the [JavaScript SDK](/sdks/js) that builds calldata against these\ncontracts.\n\n## What it is\n\nCitrate Keyring is an ERC-4337 v0.7 account-abstraction stack built on the ZeroDev Kernel v3.3 account\n(ERC-7579 modules). A user does not hold a private key for an externally owned account. Instead they hold a\nsigner, a passkey or an EOA, that authorizes operations against a smart-contract account deployed for them.\n\nThe mental model has three pieces. The account contract (`CitrateWallet`) is one deployed implementation;\nevery user account is a minimal proxy that delegate-calls into it. The account factory\n(`CitrateWalletFactory`) deploys those proxies at an address derived from the user's Citrate identity, so\nthe address is known before any deployment and is the same on every device. Validator modules decide which\nsignatures authorize an operation: an ECDSA validator for EOA signers and a WebAuthn-P256 validator for\npasskeys. A paymaster can pay gas under per-account budgets, and a guardian module lets a quorum of trusted\naddresses rotate the signer if the user loses it.\n\n| Contract | Tier | Role |\n|---|---|---|\n| `CitrateWallet` | commercial | The Kernel v3.3 account implementation behind every proxy |\n| `CitrateWalletFactory` | commercial | Identity-keyed CREATE2 deploy of account proxies |\n| `CitratePaymaster` | commercial | Per-account, per-day budgeted gas sponsorship |\n| `CitrateECDSAValidator` | commercial | secp256k1 EOA-signer validator module |\n| `WebAuthnP256Validator` | commercial | Passkey (P-256, WebAuthn) validator module |\n| `GuardianRecoveryModule` | commercial | M-of-N guardian recovery validator module |\n| `Forwarder` (EIP-2771) | commercial | Sponsored meta-transaction forwarder, education surface |\n\nThis whole surface is pre-audit. The contracts carry inline ADR and remediation references and an\nend-to-end Forge test, but they have not completed a final third-party audit, and addresses are not yet\nlisted. The status is honest per contract in [source and verification](#source-and-verification) below.\n\n## How to use it\n\nThe lifecycle a surface drives, from signup to a first sponsored operation:\n\n1. Derive the account address offline from the user's Citrate identity, before anything is deployed, with\n `CitrateWalletFactory.predictAddress(userId)`.\n2. Obtain a deploy permit from the identity authority, then call `deployFor(...)` on the factory. The first\n operation carries this as its ERC-4337 `initCode`, so the account deploys itself on first use.\n3. Register the new account with the paymaster (`registerWallet`), called by the registrar, so the\n paymaster will sponsor it.\n4. Submit operations through the bundler. The account's installed validator checks the signature; the\n paymaster pays gas under the budget for the category the operation is tagged with.\n\nThe [JavaScript SDK](/sdks/js) builds the calldata for every step. The\n[sign in with a passkey](/aa/tutorials/sign-in-with-a-passkey) tutorial walks the full path.\n\n## Reference\n\n### CitrateWallet\n\n`contracts/src/aa/wallet/CitrateWallet.sol`. A Citrate-named adapter over ZeroDev's Kernel v3.3 account\n(MIT). The factory deploys ERC-1967 minimal proxies that delegate-call into one deployment of this\ncontract; each user account is one such proxy. The Kernel v3 surface is preserved unchanged, so the\nvalidators and the recovery module install and execute through the standard ERC-7579 module ABI. The\nconstructor takes the EntryPoint v0.7 address as an immutable and passes it to Kernel. The adapter does not\noverride the EIP-712 domain: signatures are already separated by the proxy address and chain id. It exists\nto carry a Citrate-named symbol in artifacts and logs, and to hold any future Citrate-specific account\nstate in its own assembly storage slot rather than touching the vendored submodule.\n\n### CitrateWalletFactory\n\n`contracts/src/aa/factory/CitrateWalletFactory.sol`. Per `ADR-2026-06-05-ew-surface-interop`, the account\naddress must be stable across signer changes, so the CREATE2 salt is derived from the Citrate `userId`\nalone, `keccak256(abi.encodePacked(userId))`, and init data is deliberately not mixed into the salt. The\ntrade-off, that a third party could otherwise deploy someone's `userId` with hostile init data, is closed\nby requiring every deploy to carry an EIP-191 signature from a configured `identitySigner` operated by the\nidentity authority. The signature commits to `(this contract, chainId, userId, keccak256(initData),\nexpiresAt)`, so a leaked permit cannot be reused across users, deploys, or networks.\n\n| Function | Visibility | Purpose |\n|---|---|---|\n| `predictAddress(userId)` | view | The deterministic account address; offline-computable |\n| `deployFor(userId, initialValidator, initData, expiresAt, signature)` | payable | Permit-gated deploy; idempotent, returns the existing account if already deployed |\n| `permitDigest(userId, initData, expiresAt)` | view | The digest the identity signer signs |\n| `setIdentitySigner(newSigner)` | owner | Rotate the permit signer |\n| `transferOwnership(newOwner)` | owner | Transfer ownership |\n\n`initialValidator` is informational, recorded in the event for dashboards; `initData` is the source of\ntruth for which validator the account installs. The deploy uses Solady `LibClone.createDeterministicERC1967`\nover a 95-byte minimal proxy. Immutable: `implementation`. State: `identitySigner`, `owner`. Events:\n`AccountDeployed`, `IdentitySignerRotated`, `OwnerTransferred`. Errors: `ImplementationNotDeployed`,\n`PermitExpired`, `InvalidSigner`, `InitializeFailed`, `ZeroAddress`, `NotOwner`.\n\n### CitratePaymaster\n\n`contracts/src/aa/paymaster/CitratePaymaster.sol`, extending `@account-abstraction` `BasePaymaster`. Per\n`ADR-2026-06-05-ew-paymaster-policy` it sponsors gas in three categories, and every sponsored operation\ncarries a signed suffix on `paymasterAndData` after the ERC-4337 v0.7 prefix of paymaster address and two\npacked gas limits: a one-byte category at offset 52 (`0x00` standard, `0x01` recovery, `0x02` first-op), a\n`[validAfter, validUntil]` window, and a 65-byte ECDSA signature from the paymaster's `sponsorSigner`. The\nsignature binds the chain id, this paymaster, the sender, the category, the window, and the operation nonce,\nso it is single-use for one operation. Standard and recovery operations additionally require a registered\naccount; first-op is authorized by the signature alone, so a counterfactual account's first operation is\nsponsorable before registration. All budgets are denominated in WEI.\n\n| Surface | Members |\n|---|---|\n| Hooks (override) | `_validatePaymasterUserOp`, `_postOp` |\n| Admin (owner) | `setRegistrar`, `setSponsorSigner`, `setPaused`, `setDailyCap`, `setRecoveryEventCap`, `setFirstOpCap`, `setRecoveryDailyCountCap`, `setMaxFeePerGasCeiling`, `setGlobalDailyCap` |\n| Registrar only | `registerWallet(account)`, `unregisterWallet(account)` |\n| Views | `todayKey()`, `remainingStandard(account)`, `sponsorDigest(...)`, plus `dailyUsage`, `isRegistered`, `hasUsedFirstOp`, `registrar`, `sponsorSigner`, `paused`, `dailyCap`, `recoveryEventCap`, `firstOpCap`, `recoveryDailyCountCap`, `maxFeePerGasCeiling`, `globalDailyCap` |\n\n`_validatePaymasterUserOp` fails closed: it reverts when paused, when the sponsor signature is missing or\ndoes not recover to `sponsorSigner`, when the current time is outside the signed window, when the operation's\n`maxFeePerGas` exceeds `maxFeePerGasCeiling`, when the day's aggregate spend would exceed `globalDailyCap`,\nwhen a standard or recovery account is not registered, when the category tag is missing or unknown, or when\nthe relevant per-account budget cannot cover the EntryPoint-reported `maxCost`. Every budget is reserved\nduring validation so that same-bundle sibling operations cannot each pass against a stale counter. A standard\noperation draws from a per-account daily WEI allowance that resets at the next UTC day; recovery draws a\nper-event budget, bounded by a per-account daily recovery-op count, that never touches the daily counter; the\nfirst operation is sponsored once per account under a per-call cap. `_postOp` trues the reserved cost up to\nthe actual gas cost and flips the first-op flag. Events: `WalletRegistered`, `WalletUnregistered`,\n`RegistrarSet`, `SponsorSignerSet`, `SponsorshipUsed`, `PausedSet`, `DailyCapSet`, `RecoveryEventCapSet`,\n`FirstOpCapSet`, `RecoveryDailyCountCapSet`, `MaxFeePerGasCeilingSet`, `GlobalDailyCapSet`. The full policy\nand the bundler topology are on [paymaster](/aa/paymaster).\n\n### CitrateECDSAValidator\n\n`contracts/src/aa/validators/CitrateECDSAValidator.sol`, an `IValidator` and `IHook` module that binds one\nowner EOA per install. This is the path a surface uses to enroll an existing local EOA as an authorized\nsigner on the account without importing the EOA's private key; the EOA simply signs operation hashes.\nInstall data is 21 bytes, `address owner | uint8 source`, where `source` is metadata only (`Unknown`,\n`GuiNative`, `WalletExtension`, `Other`) for dashboard display. `validateUserOp` accepts a raw 65-byte\nECDSA signature over the operation hash or the EIP-191 prefixed variant. EIP-1271 is served by\n`isValidSignatureWithSender`. Lifecycle: `onInstall`, `onUninstall`, `isModuleType`, `isInitialized`; the\nhooks `preCheck` and `postCheck` are no-ops. View: `ownerOf(smartAccount)`. Events: `OwnerRegistered`,\n`OwnerUninstalled`. Errors: `AlreadyInstalled`, `InvalidInstallData`, `InvalidOwner`.\n\n### WebAuthnP256Validator\n\n`contracts/src/aa/validators/WebAuthnP256Validator.sol`, an `IValidator` and `IHook` passkey module that\nstores one P-256 passkey per install: a public key `(x, y)`, a `credentialIdHash`, and a\n`requireUserVerification` flag. Install data is 97 bytes, `bytes32 credentialIdHash | uint256 x | uint256 y\n| uint8 requireUserVerification`; the contract reverts on any other length and on a zero key.\n`validateUserOp` ABI-decodes `(authenticatorData, clientDataJSON, challengeLocation, responseTypeLocation,\nr, s)` and delegates to the vendored Daimo WebAuthn library, which checks the authenticator flags (user\npresence, and user verification if required), that the client-data type is `webauthn.get`, that the\nchallenge equals the operation hash, and the P-256 signature itself. EIP-1271 is served by\n`isValidSignatureWithSender`. View: `passkeyOf(smartAccount)`. Events: `PasskeyRegistered`,\n`PasskeyUninstalled`. Errors: `AlreadyInstalled`, `InvalidInstallData`, `PreCheckSenderMismatch`. The\nverification helpers live under `contracts/src/aa/lib/webauthn/` (`WebAuthn.sol`, `P256.sol`,\n`Base64URL.sol`). See [passkeys](/aa/passkeys).\n\n### GuardianRecoveryModule\n\n`contracts/src/aa/recovery/GuardianRecoveryModule.sol`, an `IValidator` and `IHook` module for M-of-N\nrecovery. Per `ADR-2026-06-05-ew-recovery` the user nominates N guardians at install (minimum 2, maximum 7)\nand a threshold M; install data is `uint8 threshold | uint8 count | address[count]`, and duplicate or zero\nguardians revert. Citrate is never a guardian. `validateUserOp` expects a signature blob of exactly\n`threshold × 65` concatenated ECDSA signatures over the digest `keccak256(userOpHash || account)`, bound to\nboth the operation and the account so a recovery signature cannot be replayed on another account that\nshares a guardian. Each guardian counts once, deduplicated by a bitmap, and both raw and EIP-191 signature\nshapes are tried. EIP-1271 returns `ERC1271_INVALID`, because recovery is an operation-only path. Lifecycle:\n`onInstall`, `onUninstall`. View: `configOf(smartAccount)` returns `(threshold, guardians[])`. Events:\n`GuardiansRegistered`, `GuardiansUninstalled`. Errors: `AlreadyInstalled`, `InvalidInstallData`,\n`InvalidGuardianCount`, `InvalidThreshold`, `DuplicateGuardian`, `ZeroGuardian`, `MalformedSignatureBlob`.\nThe chain enforces only M-of-N; the SDK constrains the action to a signer rotation. See\n[guardians](/aa/guardians).\n\n### Forwarder, EIP-2771, cross-reference\n\n`contracts/src/edu/Forwarder.sol`. The EIP-2771 meta-transaction forwarder for sponsored student actions\nlives in the education stack, not under `aa/`. It is the relayer path for the classroom surface, distinct\nfrom the ERC-4337 stack above, and is documented here only as a cross-reference; it is not part of Citrate\nKeyring and is not relocated.\n\n```solidity\n// 1. Deploy the account for a Citrate user (permit signed off-chain by the identity authority).\naddress account = factory.deployFor(userId, ecdsaValidator, initData, expiresAt, sig);\n\n// 2. Register it with the paymaster (called by the registrar / factory).\npaymaster.registerWallet(account);\n\n// 3. The account then submits sponsored operations through the bundler with the\n// category tag: 0x00 standard, 0x01 recovery, 0x02 first-op.\n```\n\n## Design rationale\n\nThe address is derived from the Citrate identity alone, not from the initial signer, because a user should\nsee one account address on every device and keep it when they rotate a signer or add a passkey. That choice\nopens a deploy-squatting risk, which the required identity-signer permit closes: a deploy is only valid if\nthe identity authority signed off on the exact init data and an expiry. Sponsorship is budgeted and fails\nclosed rather than open, so a misconfigured or exhausted budget refuses an operation at validation rather\nthan silently draining the paymaster. Recovery binds each guardian signature to both the operation and the\naccount, so guardians shared across accounts cannot be turned into a cross-account replay. The account\nitself is a thin adapter over an audited upstream account, which keeps Citrate-specific code in one file and\nlets upstream patches arrive through the submodule.\n\n## Failure modes\n\n- A deploy with an expired or wrong-signer permit reverts (`PermitExpired`, `InvalidSigner`); the account\n is never created with hostile init data.\n- An unregistered account, a missing or unknown category tag, or a budget too small for `maxCost` reverts\n at `_validatePaymasterUserOp`; the operation is refused, not sponsored on credit.\n- The first-op category is single-use per account (`FirstOpAlreadyUsed`), so it cannot be replayed to dodge\n the daily cap.\n- A WebAuthn install with the wrong length or a zero key reverts (`InvalidInstallData`); a high-`s`\n signature is rejected by the P-256 verifier, which the SDK pre-empts by normalizing `s`.\n- A recovery blob of the wrong length, a non-guardian signer, or a repeated guardian fails validation; the\n signer rotation does not execute.\n\n## Access and canon\n\nCommercial tier. This is the implementation depth a competitor would want to clone, identity-keyed deploy,\nfail-closed sponsorship, per-surface validators, and recovery, so it is gated to contracted builders. No\nsecrets appear here: `identitySigner`, `registrar`, and `owner` are roles, not keys, and no private keys,\nmnemonics, or internal endpoints are present. The identity authority is named as an operator role. Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity.\n\n## Source and verification\n\nSource repo `citrate-chain`, files under `contracts/src/aa/`: `wallet/CitrateWallet.sol`,\n`factory/CitrateWalletFactory.sol`, `paymaster/CitratePaymaster.sol`,\n`validators/CitrateECDSAValidator.sol`, `validators/WebAuthnP256Validator.sol`,\n`recovery/GuardianRecoveryModule.sol`, and `lib/webauthn/{WebAuthn,P256,Base64URL}.sol`. The EIP-2771\nforwarder is `contracts/src/edu/Forwarder.sol`. Audited against SHA `9d5959e`.\n\nStatus: Implemented, pre-audit. The contracts exist and pass an end-to-end Forge test under\n`contracts/test/aa/`, but have not had a final external audit and are not yet deployed at listed addresses.\nDo not custody material value on this surface until the audit closes.\n"},"/aa/guardians":{"slug":"/aa/guardians","title":"Guardians and social recovery","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-identity/src/aa (guardians.ts, guardian-routes.ts, install-data.ts) + contracts/src/aa/recovery/GuardianRecoveryModule.sol","syncedSha":"9664fa8","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"Nomination rules","anchor":"nomination-rules"},{"depth":3,"text":"HTTP routes","anchor":"http-routes"},{"depth":3,"text":"GuardianRecoveryModule","anchor":"guardianrecoverymodule"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Social recovery is how a person gets back into their Citrate Keyring after losing their signing device,\nwithout a seed phrase and without trusting Citrate to hold a spare key. The person names a small set of\npeople they trust, their guardians, and a threshold of those guardians can together approve a recovery. If\nyou build the recovery experience, this is the page you build against.\n\n## What it is\n\nA person nominates between two and seven guardians, each an address they control or trust, and an M-of-N\nthreshold. If they lose their device, M of their N guardians sign a recovery operation that rotates the\naccount's signing key to a new one. The on-chain `GuardianRecoveryModule` enforces the threshold; the\nidentity service holds the nomination until it can ride on-chain with the account's first deploy.\n\nOne rule sits above all of this, and it is enforced in code, not just stated as policy: **Citrate is never\na guardian.** A person chooses their own guardians. The nomination service stores only the addresses the\nperson chose, and it defensively refuses the authority's own signer address if it is ever submitted, with a\nclear error citing `ADR-2026-06-05-ew-recovery`. The recovery contract grants no role to the deployer or to\nanyone other than the account. Recovery is non-custodial; there is no key Citrate could hand over or be\ncompelled to hand over.\n\n## How to use it\n\nThe flow has three steps: nominate, install, recover.\n\n1. **Nominate.** From the page shown just after sign-in, the person posts their chosen guardians and\n threshold to `POST /auth/guardians`. The request is gated by the live sign-in interaction cookie, the\n same gate the password and passkey routes use, so the nomination binds to the authenticated account.\n\n ```ts\n await fetch('https://auth.citrate.ai/auth/guardians', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n guardians: ['0xGuardianA', '0xGuardianB', '0xGuardianC'],\n threshold: 2, // two of three\n }),\n });\n ```\n\n2. **Install.** The SDK reads the nomination back from `GET /aa/guardians`, gated by the caller's own\n access token. When the recovery-module address is configured, the response carries a ready-to-use\n `initConfig` entry: a Kernel `installModule` call for the `GuardianRecoveryModule`. The SDK appends that\n entry to the account's `initialize` calldata, so guardians are installed at the account's first deploy\n with no extra transaction.\n\n ```ts\n const res = await fetch('https://auth.citrate.ai/aa/guardians', {\n headers: { authorization: `Bearer ${accessToken}` },\n });\n const { nominated, guardians, threshold, initConfig } = await res.json();\n // append initConfig (when present) to the account's initialize() calldata\n ```\n\n3. **Recover.** If the device is lost, the person builds a recovery operation whose call data rotates the\n account to a fresh signing key. M guardians sign the recovery digest, and their signatures are\n concatenated into the operation's signature field. The module verifies them on-chain and, if at least M\n distinct guardians signed, the rotation succeeds. Recovery operations are sponsored from a separate\n budget so they work even when a person's daily sponsorship is spent; see [Paymaster](/aa/paymaster).\n\n## Reference\n\n### Nomination rules\n\nOff-chain, `normalizeNomination` in `citrate-identity/src/aa/guardians.ts` validates a nomination before it\nis stored. The same bounds are enforced again on-chain so a person sees a failure at nomination time, not as\na revert at deploy.\n\n| Rule | Value |\n|---|---|\n| Guardian count | between 2 and 7 |\n| Threshold M | an integer in `[1, N]`, where N is the guardian count |\n| Duplicates | rejected |\n| Address form | normalized to lowercase, each a valid address |\n| Forbidden | the authority's own signer address is refused, \"the Citrate authority cannot be a guardian\" |\n\nThe install payload is packed by `guardianInstallData` in `src/aa/install-data.ts` as `uint8 threshold |\nuint8 count | address[count] guardians`, two bytes followed by twenty bytes per guardian. The same 2-to-7\nand `[1, N]` bounds are enforced there too.\n\n### HTTP routes\n\n| Route | Method | Gate | Source |\n|---|---|---|---|\n| `/auth/guardians` | POST | sign-in interaction cookie | `src/aa/guardian-routes.ts` |\n| `/aa/guardians` | GET | Bearer access token, own subject | `src/aa/guardian-routes.ts` |\n\n### GuardianRecoveryModule\n\nThe contract is at `contracts/src/aa/recovery/GuardianRecoveryModule.sol`. It is a Kernel module that acts\nas both a validator and a hook.\n\n| Function | Purpose |\n|---|---|\n| `onInstall(bytes data)` | reads `threshold \\| count \\| guardians`, checks count in `[2, 7]` and threshold in `[1, N]`, stores the config, emits `GuardiansRegistered` |\n| `onUninstall(bytes)` | clears the config, emits `GuardiansUninstalled` |\n| `configOf(address account)` | returns the threshold and guardian list in install order |\n| `isInitialized(address account)` | true when a config is stored |\n| `isModuleType(uint256 typeID)` | true for the validator and hook module types |\n| `validateUserOp(PackedUserOperation op, bytes32 opHash)` | the recovery check: succeeds when at least M distinct guardians signed |\n| `isValidSignatureWithSender(...)` | always rejects; recovery is an operation-only path, not a sign-anything path |\n\nThe threshold model is stored as `uint8 threshold` (M) and `uint8 count` (N) with a fixed `address[7]`\nguardian slot. In `validateUserOp` the module computes a recovery digest over the operation hash bound to\nthe account, expects M concatenated 65-byte signatures, recovers each one, and matches it against the\nguardian set. Both plain key signatures and EIP-1271 contract signatures are honored, so a guardian can be\na person's key or another smart-contract account. A bitmap tracks which guardians have signed, so a repeated\nsignature from the same guardian does not count twice. Validation succeeds only when the count of distinct\nconfirming guardians reaches M. There is no timelock; the check is synchronous within the operation.\n\n## Design rationale\n\nA seed phrase is a single point of failure that a person carries alone. Social recovery spreads that trust\nacross people the person already knows, with a threshold so that no single guardian can move the account and\nlosing one guardian does not lock the person out. We cap guardians between two and seven because the lower\nbound rules out a one-guardian setup that is no better than a single key, and the upper bound keeps the\non-chain signature check cheap, M signatures of 65 bytes each, with a one-byte bitmap big enough for seven.\nWe forbid Citrate from being a guardian, in code, because the moment the authority could approve a recovery\nit would become a custodian and a target; keeping that impossible is the point of the design. Installing the\nmodule at first deploy means guardians cost the person no extra transaction.\n\n## Failure modes\n\n- **Below threshold.** If fewer than M guardians sign, `validateUserOp` returns failure and the rotation\n does not happen. Recovery fails closed.\n- **A guardian signs twice.** The signing bitmap counts each guardian once, so duplicate signatures cannot\n reach the threshold on their own.\n- **A submitted guardian is the authority.** The nomination is rejected with a clear error before it is\n stored, and again at install if it somehow reached the chain.\n- **Bounds at the edge.** Counts outside 2 to 7, or a threshold outside `[1, N]`, are rejected both\n off-chain and on-chain, so a person sees the error at nomination rather than at deploy.\n- **Secrets.** The nomination service stores only the addresses the person chose. No key or credential\n appears in Citrate Almanac.\n\n## Access and canon\n\nCommercial. The recovery experience and the install seam are the depth a contracted builder needs. The\npublic, conceptual account of recovery lives alongside [Passkeys](/aa/passkeys), and the on-chain account\nmodel is in [contracts](/aa/contracts). Recovery is non-custodial by construction: Citrate holds no\nguardian role and no spare key.\n\n## Source and verification\n\n| Surface | Source | Status |\n|---|---|---|\n| Nomination rules and store | `citrate-identity/src/aa/guardians.ts` | Implemented (pre-audit) |\n| HTTP routes | `citrate-identity/src/aa/guardian-routes.ts` | Implemented (pre-audit) |\n| Install payload encoder | `citrate-identity/src/aa/install-data.ts` | Implemented (pre-audit) |\n| Recovery module | `contracts/src/aa/recovery/GuardianRecoveryModule.sol` | Implemented (pre-audit) |\n| End-to-end recovery | `test/aa/GuardianRecoveryE2E.t.sol` | Verified (testnet 40204) |\n\nOff-chain surfaces verified against `citrate-identity` at `9664fa8`; the recovery contract and its\nend-to-end test verified against the `citrate-chain` contracts repo at `9d5959e`. The \"Citrate is never a guardian\"\ninvariant is enforced in `guardians.ts` and in the contract install. The stack has shipped and is exercised\nend to end, deploy through M-of-N recovery through a fresh-key operation, but has not had an external audit.\nRe-verify against the SHAs before relying on this page.\n"},"/aa/identity":{"slug":"/aa/identity","title":"Citrate Identity, the OIDC issuer","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-identity/src (server.ts, config.ts, siwe.ts, kyc.ts, kyc-engine.ts, entitlements.ts, identity-registry.ts, aa/, auth/)","syncedSha":"9664fa8","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"Verification status, Implemented","anchor":"verification-status-implemented"},{"depth":3,"text":"Entitlement claim, Implemented","anchor":"entitlement-claim-implemented"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Citrate Identity is the sign-in authority for the network. It is an ordinary OpenID Connect provider: a\nperson signs in once, with a passkey, an email and password, a Google account, or by signing a message\nwith their own key, and the service issues standard ID and access tokens that carry their canonical\nCitrate Keyring address. If you build a relying party that needs Citrate sign-in, this is the page you\nintegrate against.\n\n## What it is\n\nCitrate Identity is a generic OpenID Connect issuer built on the panva `oidc-provider` library\n(`src/server.ts`, `src/config.ts`). You talk to it the way you talk to any OIDC provider: read the\ndiscovery document at `/.well-known/openid-configuration`, fetch the signing keys at `/jwks`, and run the\nAuthorization Code flow with PKCE. The reference relying party is CitrateScan, the network explorer, which\nruns as a public client with refresh-token rotation enabled.\n\nThe service knows a person by one of two subject shapes, resolved in `findAccount` (`src/config.ts`):\n\n- A **UUID**, for accounts created by passkey, email and password, or Google sign-in. The Citrate Keyring\n for that person exists as a prediction, a CREATE2 address derived from the user id, until they first\n transact. See [Passkeys](/aa/passkeys).\n- An **EIP-55 address**, for accounts that sign in by proving control of a key, the EIP-4361 flow we call\n SIWE. Here the key is the identity, and a VERI verification result is keyed on the address.\n\nThe service stores almost nothing about a person. It holds sign-in records, the set of addresses a person\nhas linked, and a VERI verification result that is a status and two dates, never the documents behind it.\nVerification is done in-house by VERI, Citrate's own check (`src/kyc-engine.ts`). It is server-side\nprocessing: the server decrypts the identity, document and face evidence to decide the case. Evidence is\nsealed per case at rest, the biometric is destroyed once the decision is reached, and no outside vendor\nholds the personal data. The OIDC service persists only the closed\n`{status, verified_at, expires_at}` record and an opaque case reference. This follows the on-premise default\nthat holds across the network: the public ledger, and the authority in front of it, see only what they must.\n\n## How to use it\n\nYou integrate Citrate Identity as a relying party.\n\n1. Register your client and a redirect URI with the authority.\n2. Read the discovery document and cache the JWKS:\n\n ```bash\n curl -s https://auth.citrate.ai/.well-known/openid-configuration\n curl -s https://auth.citrate.ai/jwks\n ```\n\n3. Send the person to the authorization endpoint with PKCE and the scopes you need. Ask for `wallet` when\n you need the person's Citrate Keyring address, and `kyc` when you need their verification status. The\n access-tier claim rides under `openid`, so you do not request a scope for it:\n\n ```\n GET https://auth.citrate.ai/auth\n ?response_type=code\n &client_id=\n &redirect_uri=\n &scope=openid%20wallet%20kyc\n &code_challenge=&code_challenge_method=S256\n &state=&nonce=\n ```\n\n4. Exchange the returned code at `/token` for an ID token and an access token.\n5. Read claims from the ID token, or call `/userinfo` with the access token. Claims are recomputed on every\n `/userinfo` call, so a verification that was revoked or that expired after the token was minted shows up\n on the next read, not stale at mint time.\n\n## Reference\n\nScopes and the claims they release, defined in `citrate-identity/src/config.ts`:\n\n| Scope | Claims |\n|---|---|\n| `openid` | `sub`, `https://citrate.ai/entitlement` |\n| `profile` | `name`, `email`, `email_verified` |\n| `wallet` | `wallet_address`, `wallet_bound`, `wallets`, `signing_method` |\n| `kyc` | `kyc_status`, `kyc_verified_at`, `kyc_expires_at` |\n| `offline_access` | (enables refresh tokens) |\n\nThe `https://citrate.ai/entitlement` claim rides under `openid`, always granted, rather than behind its own\nscope, so every relying party receives the access tier without asking for it (`src/config.ts`, `claims`\nblock). It is minted only when the principal is on the entitlements roster; when absent the relying party\nfalls back to the public tier.\n\nClaim shapes, derived in `findAccount` (`src/config.ts`) and `src/aa/wallet-claims.ts`:\n\n| Claim | Meaning |\n|---|---|\n| `sub` | the subject: a lowercase UUID, or an EIP-55 address for a SIWE sign-in |\n| `email` | present for accounts that carry an email (email and password, Google) |\n| `email_verified` | whether that email has been proven, gating any entitlement keyed on it |\n| `wallet_address` | the person's one canonical Citrate Keyring address; for a UUID account this is a bound primary address if set, otherwise the CREATE2 prediction; omitted when the account-abstraction environment is unconfigured |\n| `wallet_bound` | `true` when `wallet_address` is a bound primary the person committed to, `false` when it is only the CREATE2 prediction; a relying party that pays this address must require `true` |\n| `wallets` | every address the person has linked, primary first, capped at ten per identity |\n| `signing_method` | the most recent successful sign-in method (`siwe`, `passkey`, `email-pw`, `google`) |\n| `https://citrate.ai/entitlement` | the access-tier grant, `{ tier, orgId, citrateRole?, milestone?, expiresAt? }`, resolved by `resolveEntitlementClaim` (`src/entitlements.ts`); present only for a principal on the roster |\n\nSign-in routes mounted in `src/server.ts`:\n\n| Route | Method | What it does |\n|---|---|---|\n| `/siwe/challenge` | GET | issues a fresh nonce for a message-signing sign-in (`src/siwe-routes.ts`) |\n| `/siwe/verify` | POST | verifies an EIP-4361 message and signature |\n| `/auth/password/register`, `/auth/password/login` | POST | email and password, Argon2id hashing (`src/auth/password-routes.ts`) |\n| `/auth/webauthn/*` | POST | passkey enrollment and sign-in (`src/auth/webauthn-routes.ts`) |\n| `/auth/google/start`, `/auth/google/callback` | GET | Google sign-in, mounted only when both `CITRATE_AA_GOOGLE_CLIENT_ID` and the matching secret are set (`src/auth/google-routes.ts`) |\n| `/identity/:sub/wallets*` | GET, POST, DELETE | link, list, and unlink addresses for an identity, gated to the caller's own subject (`src/identity-registry.ts`) |\n| `/aa/address`, `/aa/enroll-validator`, `/aa/validators` | GET, POST | Citrate Keyring address prediction and validator enrollment (`src/aa/aa-routes.ts`) |\n| `/kyc/_set`, `/kyc/_revoke` | POST | the VERI decision webhook that records or revokes a verification, guarded by a shared secret (`src/kyc-routes.ts`) |\n| `/logout`, `/sessions/events` | POST, GET | revoke a session and stream logout events |\n\n### Verification status, Implemented\n\nThe `kyc` scope releases `kyc_status`, with `kyc_verified_at` and `kyc_expires_at`. The stored status is one\nof `verified`, `pending`, or `revoked` (`KycStatus` in `src/kyc.ts`); `/userinfo` computes two more from the\nrecord, so a relying party can also read `expired` (a verified record whose `expires_at` has passed) or\n`none` (no record at all). A person reaches `verified` after a VERI check; once `expires_at` passes the same\nrecord reads as `expired` and prompts a re-check. The stored record is a closed type: a status, two dates,\nand an opaque case reference, and nothing else. There is no field where a name, a document, or an identifier\ncould be added.\n\nVERI is Citrate's in-house verification. It processes evidence server-side and keeps it sealed at\nrest. The engine (`src/kyc-engine.ts`) decides a captured case with\nno outside call: it unseals the per-case evidence, runs the liveness and 1:1 face-match\nanalyzers, the document OCR and MRZ check, and the in-house sanctions screener, then destroys the biometric\nimmediately and records only the decision. It fails closed: with no model backend a case routes to human\nreview, never to an auto-`verified`. The engine wiring and operator runbook are gated to operators and are\nnot on this page. A relying party consumes only the claim shapes above. See\n[compliance](/enterprise/compliance) for the verification posture.\n\n### Entitlement claim, Implemented\n\nCitrate Almanac decides which gated pages a request may read from the `https://citrate.ai/entitlement` claim,\nwhich names a caller's access tier. The identity service mints it: `resolveEntitlementClaim`\n(`src/entitlements.ts`) looks the principal up in the entitlements roster (a Postgres table keyed on `sub`,\n`wallet`, or a verified `email`) and returns `{ tier, orgId, citrateRole?, milestone?, expiresAt? }`, which\nrides in the token under `openid`. The tiers are `public`, `commercial`, `commercial.kyc`, `academic`, and\n`confidential`. Resolution is fail-safe and KYC-gated: an absent or expired grant mints no claim and the\nrelying party falls back to public; a role-bearing principal (admin, auditor, exec) is authorized by the\nroster without a KYC check; an unverified consumer keeps `public` and `commercial`, a `commercial.kyc` grant\ncollapses to `commercial`, and the higher `academic` and `confidential` tiers require a verified VERI check.\nPassing VERI auto-grants the `commercial.kyc` baseline if the principal has no grant yet\n(`grantKycBaseline`). When no database is configured the service mints no entitlement claim at all. No secret\never rides in any tier.\n\n## Design rationale\n\nWe made the identity authority a plain OIDC provider so that any team that has integrated OIDC before can\nintegrate Citrate sign-in without learning a Citrate-specific protocol. The two subject shapes exist\nbecause two kinds of people arrive: one brings a key and wants the key to be the identity, the other brings\nan email and wants a Citrate Keyring created for them. Holding only a verification status and never the\npersonal data behind it keeps the authority outside the scope of the heaviest data-protection duties, and\nit is the same discipline the rest of the network follows. Recomputing claims on every read, rather than\nfreezing them at mint time, means a revoked verification takes effect promptly instead of lingering for the\nlife of a token.\n\n## Failure modes\n\n- **A stale token after revocation.** Claims are recomputed on each `/userinfo` call, so a relying party\n that re-reads `/userinfo` sees a revocation or expiry promptly. A relying party that trusts only the\n original ID token for the token's full lifetime will lag; re-read for anything verification-sensitive.\n- **Unsafe production configuration.** At boot the service runs `assertProductionConfig` (`src/config.ts`).\n In production it refuses to start if the cookie keys are the development default or shorter than 32\n characters, if the issuer or origins point at localhost, or if the database or session store is unset. It\n fails closed rather than starting in a weak state.\n- **Account-abstraction environment unset.** When the account-abstraction environment is not configured,\n `wallet_address` is simply omitted rather than guessed. A relying party should treat the claim as\n optional.\n- **Secrets.** No client secret, cookie key, signing key, webhook secret, or vendor credential appears in\n Citrate Almanac. They live in operator environment only.\n\n## Access and canon\n\nPublic. The OIDC issuer and the claim shapes are what a relying party needs to integrate, and they are\nstandard. The verification internals are gated to operators. The `https://citrate.ai/entitlement` claim is\nminted by the authority and the tier meanings are public, while the roster of who holds which tier is not.\nVERI is Citrate's in-house, server-side verification. Node and consensus code do not check identity.\nAfter a decision Citrate keeps the verification result, and any retained evidence stays sealed at rest.\n\n## Source and verification\n\n| Surface | Source | Status |\n|---|---|---|\n| OIDC issuer, routes, boot checks | `citrate-identity/src/server.ts`, `src/config.ts` | Implemented (pre-audit) |\n| SIWE sign-in | `citrate-identity/src/siwe.ts`, `src/siwe-routes.ts` | Implemented (pre-audit) |\n| Keyring address-claim derivation | `citrate-identity/src/aa/wallet-claims.ts` | Implemented (pre-audit) |\n| Verification status and record | `citrate-identity/src/kyc.ts`, `src/kyc-pg.ts`, `src/kyc-routes.ts` | Implemented (pre-audit) |\n| VERI verification engine | `citrate-identity/src/kyc-engine.ts` | Implemented (pre-audit) |\n| Identity to address registry | `citrate-identity/src/identity-registry.ts` | Implemented (pre-audit) |\n| `https://citrate.ai/entitlement` claim | `citrate-identity/src/entitlements.ts`, `src/config.ts` | Implemented (pre-audit) |\n\nVerified against `citrate-identity` at `9664fa8`, package version 0.1.0. The service has shipped and runs;\nit has not had an external audit, so the implemented surfaces are pre-audit. The entitlement claim is now\nminted by the authority under `openid` and is KYC-gated. Re-verify against the SHA before relying on this\npage.\n"},"/aa/passkeys":{"slug":"/aa/passkeys","title":"Passkeys and Kernel operations","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-sdk-js/src/aa/ + citrate-chain/contracts/src/aa/","syncedSha":"9d5959e","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"Counterfactual address","anchor":"counterfactual-address"},{"depth":3,"text":"WebAuthnP256Validator, how a passkey authorizes an operation","anchor":"webauthnp256validator-how-a-passkey-authorizes-an-operation"},{"depth":3,"text":"CitrateECDSAValidator, the EOA path","anchor":"citrateecdsavalidator-the-eoa-path"},{"depth":3,"text":"Kernel v3 account and the first operation","anchor":"kernel-v3-account-and-the-first-operation"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"> **Status: passkey-only accounts are not yet available on chain 40204.** Use these pages to build and test against a local chain.\n\nHow a Citrate user signs in with a passkey and sends transactions with no seed phrase, end to end, plus the\nEOA path for users who already hold a signer. This is the builder reference for Citrate Keyring: the\nWebAuthn-P256 validator on chain, the Kernel v3 account, the address that is known before deployment, and\nthe first operation that deploys the account. For the on-chain contract reference see\n[account-abstraction contracts](/aa/contracts); for the runnable walkthrough see\n[sign in with a passkey](/aa/tutorials/sign-in-with-a-passkey).\n\n## What it is\n\nCitrate Keyring is an ERC-4337 v0.7 smart-contract account built on the Kernel v3 account (ERC-7579\nmodules). The model has three parts.\n\n- One user, one account address, derivable offline. A Citrate user id, a UUID or an EOA address for SIWE\n sign-ins, maps deterministically to a single CREATE2 account address. The account is counterfactual: it\n exists at a known address from signup and deploys itself lazily on the first operation.\n- The sign-in is the key. Instead of a seed phrase the user holds a passkey, a WebAuthn P-256 credential\n bound to their device authenticator, or an EOA, a browser, extension, or hardware secp256k1 key. Either\n one installs as a Kernel validator module that authorizes operations.\n- Gas can be sponsored. The [paymaster](/aa/paymaster) can pay gas under per-account daily budgets, so a\n user with a zero balance can still transact.\n\nThe on-chain object is a smart-contract account, never a key the user has to back up. Losing a device is a\nrecovery event, handled by [guardians](/aa/guardians), not a lost-funds event.\n\nThe pieces, and where the truth for each lives:\n\n| Concern | Code |\n|---|---|\n| Address derivation, UUID to userId to CREATE2 | `citrate-sdk-js/src/aa/address.ts` |\n| Passkey (WebAuthn P-256) signing | `citrate-sdk-js/src/aa/webauthn.ts` |\n| EOA (secp256k1) signing | `citrate-sdk-js/src/aa/eoa.ts` |\n| Kernel v3 nonce, execute, install encoders | `citrate-sdk-js/src/aa/kernel.ts` |\n| Operation build, hash, wire conversion | `citrate-sdk-js/src/aa/userop.ts` |\n| Bundler JSON-RPC client | `citrate-sdk-js/src/aa/bundler.ts` |\n| On-chain validators | `contracts/src/aa/validators/{WebAuthnP256Validator,CitrateECDSAValidator}.sol` |\n| Account factory | `contracts/src/aa/factory/CitrateWalletFactory.sol` |\n\n## How to use it\n\nInstall the SDK. The account-abstraction helpers are re-exported from the package root as the `aa`\nnamespace.\n\n```bash\nnpm install @citratelabs/sdk\n```\n\n```typescript\nimport { aa } from '@citratelabs/sdk';\nconst {\n uuidToUserId,\n predictWalletAddress,\n encodeDeployFor,\n packInitCode,\n encodeExecuteSingle,\n buildPackedUserOp,\n getUserOpHash,\n signUserOpWithPasskey,\n signUserOpWithEoa,\n packCitratePaymasterAndData,\n PaymasterCategory,\n BundlerClient,\n} = aa;\n```\n\nYou will need chain 40204 (Citrate), the Citrate identity authority that issues the deploy permit (see\n[identity](/aa/identity)), the Citrate bundler, and, for passkeys, a secure context (HTTPS or `localhost`)\nwhere `navigator.credentials` is available. The end-to-end flow, with the symbol that implements each step,\nall in `citrate-sdk-js/src/aa/`:\n\n1. Derive the userId. `uuidToUserId(citrateUserId)` returns the 32-byte userId,\n `keccak256(utf8(lowercase uuid))` (`address.ts`). For other identity shapes, `accountIdToAaUserId`\n resolves a 32-byte hex, a 20-byte EOA, or a UUID to the userId.\n2. Predict the address. `predictWalletAddress(factory, implementation, userId)` returns the one CREATE2\n address this user has on every surface (`address.ts`); it is the same value the factory's\n `predictAddress` returns on chain.\n3. Build the first operation, which deploys the account. Fetch the permit from the identity authority, then\n `encodeDeployFor({ userId, initialValidator, initData, expiresAt, signature })` and\n `packInitCode(factory, factoryData)` build the `initCode` (`userop.ts`). Later operations use\n `initCode = '0x'`.\n4. Build the call. `encodeExecuteSingle({ to, value, data })` or `encodeExecuteBatch(calls)` (`kernel.ts`).\n5. Set the nonce. Read it from `EntryPoint.getNonce(sender, key)`. For the account's root validator use the\n sequence directly (`rootValidatorNonce`); for an installed validator build the key with\n `validatorNonceKey(validator)` (`kernel.ts`).\n6. Build and hash. `buildPackedUserOp(args)` then `getUserOpHash(op, entryPoint, 40204n)` (`userop.ts`).\n7. Sign. `signUserOpWithPasskey(userOpHash, opts)` in the browser, which drives\n `navigator.credentials.get()`, or `signUserOpWithEoa(signer, userOpHash)` with any ethers signer.\n8. Submit. `new BundlerClient().sendUserOperation(op, entryPoint)`, then\n `waitForUserOperationReceipt(hash)` (`bundler.ts`).\n\n## Reference\n\n### Counterfactual address\n\n`predictWalletAddress` computes the CREATE2 address the factory deploys to, without any chain read. The\nsalt is `keccak256(userId)`; the init code is Solady's 95-byte minimal ERC-1967 proxy with the account\nimplementation embedded, and the address is `keccak256(0xff ++ factory ++ salt ++ initCodeHash)[12..]`\n(`address.ts`, `predictWalletAddress` and `erc1967MinimalInitCodeHash`). The SDK pins this against the live\nfactory on chain 40204 in its unit tests, and it matches the on-chain `predictAddress` byte for byte. The\npractical effect: a user has a stable address from the moment they sign up, before any transaction exists.\n\n### WebAuthnP256Validator, how a passkey authorizes an operation\n\nThe validator verifies a passkey assertion on chain through the vendored Daimo WebAuthn library. The SDK\nencodes `userOp.signature` as `abi.encode(authenticatorData, clientDataJSON, challengeLocation,\nresponseTypeLocation, r, s)` and normalizes `s` into the lower half of the P-256 group order, because the\nverifier rejects malleable high-`s` signatures (`webauthn.ts`, `normalizeP256S` and\n`encodeWebauthnValidatorSignature`). `challengeLocation` is the byte index in `clientDataJSON` where the\nexact substring `\"challenge\":\"\"` begins, and `responseTypeLocation` likewise for\n`\"type\":\"webauthn.get\"`. On chain the operation hash is the WebAuthn challenge, so a passkey assertion is\nonly valid for the one operation it signed. The install payload is the 97-byte\n`credentialIdHash | x | y | requireUserVerification` (`kernel.ts`, `webauthnInstallData`).\n\n`signUserOpWithPasskey` is the browser path: it asks the platform authenticator (Face ID, Touch ID, Windows\nHello) to sign the operation hash, parses the DER ECDSA signature the assertion carries\n(`parseDerEcdsaSignature`), and returns the encoded blob. The pure encoding and parsing helpers are\nexported separately so they can be unit-tested without a browser.\n\n### CitrateECDSAValidator, the EOA path\n\nThe ECDSA validator verifies a 65-byte secp256k1 signature over the operation hash, accepting both the raw\nshape and the EIP-191 personal-sign shape. The SDK's `signUserOpWithEoa` emits the EIP-191 shape via an\nethers `signMessage`, which is what browser signers produce by default (`eoa.ts`). The install payload is\nthe 21-byte `owner | source` (`kernel.ts`, `ecdsaInstallData`, where `source` is the `EcdsaValidatorSource`\nlabel `GuiNative`, `WalletExtension`, or `Other`). This path enrolls an existing local EOA as an authorized\nsigner on the account without importing its private key. One account can hold both a passkey and an EOA\nvalidator; adding guardians is covered in [guardians](/aa/guardians).\n\n### Kernel v3 account and the first operation\n\nThe account is a Kernel v3 modular account (ERC-7579). The factory's `deployFor` runs the account's\n`initialize(bytes21 rootValidator, address hook, bytes validatorData, bytes hookData, bytes[] initConfig)`\non the fresh proxy, installing the chosen validator as the root (`kernel.ts`, `kernelInitializeCalldata`,\n`packValidationId`). Calls are made through Kernel's `execute(bytes32 execMode, bytes executionCalldata)`,\nsingle or batch. The Kernel nonce layout is `1B mode | 1B validator-type | 20B validator | 2B nonceKey | 8B\nsequence`; `validatorNonceKey` builds the 192-bit key that routes an operation to an installed validator,\nand the EntryPoint appends the 8-byte sequence. The first operation carries the factory `initCode`, so the\naccount deploys itself, installs its root validator, and runs its first call in one operation; every later\noperation sets `initCode = '0x'`.\n\n## Design rationale\n\nA passkey is the right default because the private material never leaves the device's secure element, the\nuser authenticates with a fingerprint or face rather than a phrase to copy down, and the same credential\nworks across that user's platform. Verifying P-256 on chain is more work than secp256k1, which is why the\naccount delegates to an audited WebAuthn library and the SDK normalizes `s` rather than asking the verifier\nto accept malleable signatures. The address is derived from the identity rather than the first signer so it\nsurvives signer changes, and the account deploys on first use rather than at signup so an account that is\nnever used costs nothing to create.\n\n## Failure modes\n\n- Outside a secure context, or with no authenticator, `signUserOpWithPasskey` throws\n `WebAuthnSigningError`; run it in a browser served over HTTPS or `localhost`.\n- A passkey assertion whose `clientDataJSON` lacks the expected challenge or is not a `webauthn.get` type is\n rejected by the encoder before it ever reaches the chain.\n- A high-`s` signature would be rejected on chain; the SDK normalizes `s` so a correctly built operation\n does not hit that path.\n- A first operation without the factory `initCode` will fail at the EntryPoint for an undeployed account;\n the deploy and the first call must travel together.\n- The bundler enforces chain 40204; an operation built for another chain id will not verify, because the\n operation hash commits to the chain id.\n\n## Access and canon\n\nPublic tier. This is the open builder reference for Citrate Keyring; a developer needs it to build, and\nnothing here is a secret or a competitive moat. No private keys, mnemonics, credentials, or private\nendpoints appear on this page. The deploy permit is fetched at runtime from the identity authority, whose\nsigning key never leaves it, and passkey private material never leaves the user's authenticator. The\npaymaster policy, caps, and the bundler authentication topology are commercial tier, on\n[paymaster](/aa/paymaster).\n\n## Source and verification\n\n- SDK: `citrate-sdk-js/src/aa/` at SHA `bc5a830`.\n- Contracts: `citrate-chain/contracts/src/aa/` at SHA `9d5959e`.\n- End to end: `citrate-chain/contracts/test/aa/` (operation-hash, WebAuthn, and guardian vectors); the\n SDK pins the address and operation-hash helpers against the live factory and EntryPoint v0.7 on chain\n 40204 in `citrate-sdk-js/tests/unit/`.\n\nStatus: Specified on chain 40204, where passkey-only accounts are not yet available. The validators, factory, paymaster, recovery module, bundler, and the SDK\nencoders shipped in the EW-S1 sprint and have not had a final external audit. Treat the surface as\nexperimental and do not custody material value on it until the audit closes. Re-verify symbols against the\nsource SHAs before relying on this page.\n"},"/aa/paymaster":{"slug":"/aa/paymaster","title":"Paymaster and bundler topology","tier":"public","orgId":null,"sourceKind":"authored","source":"contracts/src/aa/paymaster/CitratePaymaster.sol + citrate-bundler (gate/src, README, Caddyfile)","syncedSha":"9d5959e","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"Categories","anchor":"categories"},{"depth":3,"text":"Caps and accounting","anchor":"caps-and-accounting"},{"depth":3,"text":"Eligibility, the registrar gate","anchor":"eligibility-the-registrar-gate"},{"depth":3,"text":"Bundler topology","anchor":"bundler-topology"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"A paymaster sponsors the gas for an operation so a person can transact with no SALT in hand, which is what\nlets someone use a Citrate Keyring on their first visit. Citrate sponsors gas with a contract paymaster\nunder per-account budgets, fronted by a bundler that authorizes and pre-checks operations at the edge. If\nyou build or operate against sponsored operations, this is the page you work from.\n\n## What it is\n\nThere is no native gas-sponsorship operation on the network; sponsorship is done with an ERC-4337 paymaster\ncontract. Sponsorship has two layers.\n\nThe authoritative layer is `CitratePaymaster`, an ERC-4337 v0.7 paymaster that extends `BasePaymaster`. It\nenforces per-account WEI budgets: every sponsored operation carries a sponsor-signer signature and a\ncategory in `paymasterAndData`, passes its `_validatePaymasterUserOp` check, and settles in `_postOp`. The\nedge layer is the bundler: a self-hosted eth-infinitism reference bundler behind Caddy, fronted by a thin\nCitrate gate that does key authorization, rate limiting, and a paymaster pre-check, so an operation that is\ncertain to fail is refused at the edge instead of taking a bundle slot. The edge is an optimization; the\ncontract re-validates everything.\n\nEvery sponsored operation is authorized by an ECDSA signature from the paymaster's own `sponsorSigner`, not\nby registration alone. The signature covers a digest that binds the chain id, this paymaster, the sender,\nthe category, a `[validAfter, validUntil]` window, and the operation `nonce` (`sponsorDigest`), so a\nsignature is single-use for exactly one operation and cannot be replayed across accounts, chains, paymasters,\ncategories, or operations. Because verifying it touches only the paymaster's own storage and `ecrecover`, a\ncounterfactual account's first operation is mempool-legal under ERC-7562.\n\nA related but separate mechanism serves the education stack. There, sponsored student actions go through an\nEIP-2771 forwarder (`contracts/src/edu/Forwarder.sol`), a meta-transaction relay where a relayer pays gas\non behalf of a device-bound signer. That is a different path from the ERC-4337 paymaster described here; we\nnote it so the two are not confused.\n\n## How to use it\n\nFor an integrator the steps are: tag the operation, send it, watch the budget.\n\n1. **Tag.** Build the operation with a one-byte category in `paymasterAndData`. The SDK does this for you;\n the category names the budget the operation should draw from.\n2. **Send.** Post the operation to the bundler at `https://bundler.citrate.ai/rpc`. When you have a `bk_`\n key, send it as a Bearer token for the higher rate limit.\n3. **Watch.** Read `remainingStandard(account)` on the paymaster to show a person how much of their daily\n sponsorship is left.\n\nA new account's first operation is sponsored under the first-op budget on the strength of the sponsor\nsignature, before the account is registered, so a person can deploy and act without SALT. After that,\nordinary operations draw from a daily allowance, and recovery operations draw from their own budget so\nrecovery is never blocked by a spent daily allowance.\n\n## Reference\n\n### Categories\n\nThe bundler assembles a signed suffix on `paymasterAndData` after the ERC-4337 v0.7 prefix of\n`paymaster(20) | verificationGasLimit(16) | postOpGasLimit(16)`. The full layout the contract reads:\n\n```\n[0:20] address paymaster\n[20:36] uint128 paymasterVerificationGasLimit\n[36:52] uint128 paymasterPostOpGasLimit\n[52] uint8 category (0 standard / 1 recovery / 2 first-op)\n[53:59] uint48 validUntil\n[59:65] uint48 validAfter\n[65:130] bytes65 sponsorSigner ECDSA signature (r || s || v)\n```\n\nThe category byte lives at `PMD_TAG_OFFSET` (52); the sponsor signature is the 65-byte tail. All three\ncategory budgets are denominated in WEI (spend, not gas units), since `requiredPreFund` is `requiredGas ×\nmaxFeePerGas`.\n\n| Tag | Category | Budget behavior |\n|---|---|---|\n| `0x00` | Standard | draws from the per-account daily WEI allowance, `dailyCap`; the counter resets at the first sponsored operation of a new UTC day. Requires the account to be registered |\n| `0x01` | Recovery | draws from a per-event WEI budget, `recoveryEventCap`, that does not touch the daily counter, so recovery works even when the daily allowance is spent, bounded by a per-account daily recovery-op count cap. Requires the account to be registered |\n| `0x02` | First-op | one sponsorship for an account's first operation, bounded by `firstOpCap`; authorized by the sponsor signature rather than registration, so a counterfactual account can spend it; the `hasUsedFirstOp` flag then flips so it cannot be reused |\n\n### Caps and accounting\n\nThe caps live in `CitratePaymaster` as owner-settable WEI values. The as-deployed defaults, set in\n`script/aa/DeployAA.s.sol`, are:\n\n| Cap | Default | Set with |\n|---|---|---|\n| `dailyCap` | 0.01 ether | `setDailyCap` |\n| `recoveryEventCap` | 0.01 ether | `setRecoveryEventCap` |\n| `firstOpCap` | 0.02 ether | `setFirstOpCap` |\n| `globalDailyCap` | 5 ether | `setGlobalDailyCap` |\n| `maxFeePerGasCeiling` | 20 gwei | `setMaxFeePerGasCeiling` |\n\nSetting any per-account cap to `0` disables that category. `globalDailyCap` is an aggregate deposit-spend\nbackstop across every account, a drain guard; `maxFeePerGasCeiling` bounds the fee a single operation may\nclaim, so a generous WEI cap cannot be drained by one inflated-fee operation. Every budget is reserved\nduring validation, not only in `_postOp`, so that several operations from one sender in the same bundle\ncannot each validate against a stale counter; `_postOp` then trues the reservation from `maxCost` up to the\nactual gas cost. The day key is `block.timestamp / 86400`, so counters reset at the UTC day boundary.\n`remainingStandard(account)` returns what is left of the daily allowance for dashboards and the SDK. An\noperator may change any of these on-chain, so treat the numbers above as the shipped defaults, not\nguarantees.\n\nIf the signature, the fee ceiling, or the relevant budget check fails, validation reverts with a precise\nerror rather than sponsoring anyway. It fails closed.\n\n| Error | Reverts when |\n|---|---|\n| `Paused()` | sponsorship is paused |\n| `InvalidSponsorSignature()` | the sponsor-signer signature is missing or does not recover to `sponsorSigner` |\n| `SponsorshipExpired()` | the current time is outside the signed `[validAfter, validUntil]` window |\n| `MaxFeePerGasCeilingExceeded(maxFeePerGas, ceiling)` | the operation's `maxFeePerGas` exceeds `maxFeePerGasCeiling` |\n| `GlobalDailyCapExceeded(spentToday, cap, wouldSpend)` | the day's aggregate spend would exceed `globalDailyCap` |\n| `NotARegisteredCitrateWallet(account)` | a standard or recovery operation is from an unregistered account |\n| `StandardCapExceeded(account, used, cap, wouldUse)` | a standard operation would exceed `dailyCap`, or `dailyCap` is 0 |\n| `RecoveryCapExceeded(account, cap, wouldUse)` | a recovery operation would exceed `recoveryEventCap`, or it is 0 |\n| `RecoveryDailyCountExceeded(account, usedToday, maxPerDay)` | the account has spent its per-day recovery-op count |\n| `FirstOpAlreadyUsed(account)` | the account already used its first-op sponsorship |\n| `FirstOpCapExceeded(cap, wouldUse)` | a first operation would exceed `firstOpCap`, or it is 0 |\n| `UnknownCategory(tag)` | the category byte is greater than 2 |\n| `MissingCategoryTag()` | `paymasterAndData` is shorter than the signed layout requires |\n\n### Eligibility, the registrar gate\n\nStandard and recovery operations require a registered account. A single `registrar` address, typically the\naccount factory, calls `registerWallet(account)`; `unregisterWallet(account)` reverses it. A standard or\nrecovery operation from an unregistered account reverts `NotARegisteredCitrateWallet`. First-op is the\nexception: it is authorized by the sponsor signature rather than registration, so a counterfactual account's\nvery first operation is sponsorable before it is registered and with no cross-entity storage write. The\nowner can rotate the registrar with `setRegistrar`, rotate the sponsor signer with `setSponsorSigner`, and\nhalt all sponsorship with `setPaused(true)` for incident response.\n\nRegistration now happens outside the validation phase (an owner passthrough on the factory), not inside\n`deployFor`, so a strict ERC-7562 tracer sees the paymaster touch only its own storage during validation.\n\n### Bundler topology\n\nThe bundler runs on its own host, so a bundler outage cannot take down the identity authority or the\ngateway. The path an operation takes:\n\n```\nclient (browser, SDK, native app)\n | HTTPS JSON-RPC\n v\nCaddy at bundler.citrate.ai, TLS, per-IP rate limit\n v\nCitrate gate (Node), bk_ Bearer auth, rate limiting, paymaster pre-check\n v\neth-infinitism bundler v0.7, standard ERC-4337 JSON-RPC\n v\nnetwork RPC, EntryPoint v0.7 on chain 40204\n```\n\nThe gate (`citrate-bundler/gate/src`) authenticates `bk_` keys by SHA-256 hash held in Redis, so a dump of\nthe store cannot be replayed as a credential. It rate-limits anonymous traffic per IP and authenticated\ntraffic per key, with the limits as operator configuration. Its pre-check (`gate/src/precheck.ts`), for an\noperation naming the Citrate paymaster, calls `CitratePaymaster.isRegistered(sender)` and\n`EntryPoint.balanceOf(paymaster)` and validates the category byte. Self-paid operations, those naming no\npaymaster, pass through untouched. The pre-check fails open if the chain is unreachable, since the\nEntryPoint re-validates on-chain; the pre-check is an optimization, not a security boundary.\n\nThe bundler exposes the standard ERC-4337 v0.7 methods, `eth_sendUserOperation`,\n`eth_estimateUserOperationGas`, `eth_getUserOperationReceipt`, `eth_supportedEntryPoints`, and\n`eth_chainId`, plus the Citrate extension `citrate_getUserAddress(userId)`, which predicts a Citrate Keyring\naddress and mirrors the on-chain factory. The SDK's bundler client defaults to\n`https://bundler.citrate.ai/rpc`. See the [bundler SDK](/sdks/bundler) for the client.\n\n## Design rationale\n\nWe made sponsorship contract-based because the network has no built-in way to sponsor gas, and a contract\npaymaster is the standard ERC-4337 answer that existing tooling already understands. Per-account budgets,\nrather than a single shared pool, mean one account cannot drain sponsorship for everyone, and the three\ncategories exist so the budgets that must never fail, a person's first operation and an account recovery,\ndraw from separate allowances than ordinary daily use. The registrar gate keeps sponsorship to accounts the\nnetwork actually issued, so an arbitrary contract cannot spend the paymaster's deposit. The edge gate is\nthere to save bundle slots and to rate-limit abuse, but we kept it strictly an optimization: it fails open,\nand the contract is the one place that decides whether an operation is sponsored. The cost is that operators\nmust keep the paymaster funded and the registrar correctly wired; we think a clear on-chain budget is worth\nthat. The economics of who funds sponsorship are in [network economics](/chain/economics).\n\n## Failure modes\n\n- **Over budget.** A standard operation past the daily cap, a recovery past the event cap, or a reused\n first operation reverts with the matching error above. The contract never sponsors past a budget.\n- **Unregistered account.** Sponsorship reverts `NotARegisteredCitrateWallet`. If the factory is not wired\n to register on deploy, new accounts cannot be sponsored until they are registered.\n- **Missing or unknown tag.** An operation with no category byte, or a byte greater than 2, reverts at\n validation rather than being sponsored under a guessed category.\n- **Bundler outage.** The bundler is on its own host; if it is down, sponsored operations cannot be\n submitted, but the identity authority and gateway keep running. Self-paid operations are unaffected.\n- **Edge fails open.** If the chain is unreachable the pre-check is skipped and the operation goes to the\n bundler, where the EntryPoint and the paymaster contract re-validate. The edge skipping a check never\n causes an over-budget sponsorship.\n- **Secrets.** No `bk_` key, multisig address, deposit balance, private RPC endpoint, or host credential\n appears in Citrate Almanac. Those live only in operator configuration.\n\n## Access and canon\n\nCommercial. The budget model and the edge topology are operator and integrator depth; publishing the full\npolicy and topology to anyone aids an abuse actor more than it helps a public developer. The public,\ndeveloper-facing piece, how to tag and send a sponsored operation, sits on [Passkeys](/aa/passkeys). SALT\nsettles the work the network performs, including the gas a paymaster fronts; it is the unit of account, not\na product to hold.\n\n## Source and verification\n\n| Surface | Source | Status |\n|---|---|---|\n| Paymaster policy, caps, errors | `contracts/src/aa/paymaster/CitratePaymaster.sol` | Implemented (pre-audit) |\n| Deployed cap defaults | `contracts/script/aa/DeployAA.s.sol` | Implemented (pre-audit) |\n| EIP-2771 forwarder (education stack) | `contracts/src/edu/Forwarder.sol` | Implemented (pre-audit) |\n| Bundler gate, pre-check, routing | `citrate-bundler/gate/src`, `README.md`, `Caddyfile` | Implemented (pre-audit) |\n\nPaymaster and forwarder verified against the contracts repo at `9d5959e`; the bundler verified against\n`citrate-bundler` at `a3287de`. The caps shown are the as-deployed WEI defaults and an operator may change\nthem on-chain. The stack has shipped and runs on testnet 40204; it has not had an external audit. Re-verify\nthe on-chain cap values and the source symbols against the SHAs before relying on this page.\n"},"/aa/tutorials/sign-in-with-a-passkey":{"slug":"/aa/tutorials/sign-in-with-a-passkey","title":"Sign in with a passkey","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-sdk-js/src/aa/{address,userop,kernel,webauthn,bundler}.ts","syncedSha":"bc5a830","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Step 1, derive the account address","anchor":"step-1-derive-the-account-address"},{"depth":3,"text":"Step 2, build the first operation with the deploy","anchor":"step-2-build-the-first-operation-with-the-deploy"},{"depth":3,"text":"Step 3, hash and sign with the passkey","anchor":"step-3-hash-and-sign-with-the-passkey"},{"depth":3,"text":"Step 4, submit to the bundler and wait","anchor":"step-4-submit-to-the-bundler-and-wait"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"What just happened","anchor":"what-just-happened"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"> **Status: passkey-only accounts are not yet available on chain 40204.** Use these pages to build and test against a local chain.\n\nCreate a passkey-backed Citrate Keyring account and send your first sponsored transaction, with no seed\nphrase, using `@citratelabs/sdk`. You will derive the account address before it exists, then deploy and use it in\na single operation. For the concepts behind each step see [passkeys](/aa/passkeys); for the contracts see\n[account-abstraction contracts](/aa/contracts).\n\n## What it is\n\nA runnable walkthrough of the four moves a surface makes to onboard a user: derive the counterfactual\naddress, build a first operation that carries the deploy, sign it with the device authenticator, and submit\nit to the bundler. Every call below is a real export of `citrate-sdk-js/src/aa/` at SHA `bc5a830`. Passkeys\nneed `navigator.credentials`, which only runs in a secure browser context, so run these steps in a browser\napp, a Vite or Next page, not a plain Node script.\n\n## How to use it\n\nYou will need Node 18 or newer with `npm install @citratelabs/sdk`; a secure context (HTTPS or `localhost`); chain\n40204 access to the Citrate identity authority and the Citrate bundler; and the deployed addresses for the\nstack (factory, account implementation, EntryPoint, paymaster, validators), read from the chain's deployed\naddresses file. The account-abstraction helpers are the `aa` namespace: `import { aa } from '@citratelabs/sdk'`.\n\n### Step 1, derive the account address\n\n```typescript\nimport { aa } from '@citratelabs/sdk';\nconst { uuidToUserId, predictWalletAddress } = aa;\n\nconst userId = uuidToUserId(citrateUserId); // keccak256(utf8(lowercase uuid))\nconst sender = predictWalletAddress(FACTORY, IMPLEMENTATION, userId);\n// `sender` is this user's one account address on every surface, counterfactual.\n```\n\n`predictWalletAddress` is pure: it computes the CREATE2 address with no chain read, so you can show the user\ntheir address before anything is deployed. It returns the same value the factory's `predictAddress` returns\non chain.\n\n### Step 2, build the first operation with the deploy\n\nFetch the deploy permit from the identity authority, then assemble the `initCode` and the call. The first\noperation carries the deploy, so the account creates itself on first use.\n\n```typescript\nimport { aa } from '@citratelabs/sdk';\nconst {\n encodeDeployFor, packInitCode, encodeExecuteSingle,\n buildPackedUserOp, packCitratePaymasterAndData, PaymasterCategory,\n} = aa;\n\n// The identity signer authorizes the deploy, see /aa/identity.\nconst permit = await fetch('https://auth.citrate.ai/aa/enroll-validator', {\n method: 'POST',\n headers: { 'content-type': 'application/json', authorization: `Bearer ${accessToken}` },\n body: JSON.stringify({ /* userId, initialValidator, initData ... */ }),\n}).then((r) => r.json());\n\nconst factoryData = encodeDeployFor({\n userId,\n initialValidator: WEBAUTHN_VALIDATOR,\n initData: permit.initData,\n expiresAt: BigInt(permit.expiresAt),\n signature: permit.signature,\n});\n\nconst op = buildPackedUserOp({\n sender,\n nonce, // EntryPoint.getNonce(sender, key)\n initCode: packInitCode(FACTORY, factoryData),\n callData: encodeExecuteSingle({ to: recipient, value: 0n, data: '0x' }),\n callGasLimit, verificationGasLimit, preVerificationGas,\n maxFeePerGas, maxPriorityFeePerGas,\n // First-ever op, sponsored under the first-op budget:\n paymasterAndData: packCitratePaymasterAndData({\n paymaster: PAYMASTER,\n paymasterVerificationGasLimit, paymasterPostOpGasLimit,\n category: PaymasterCategory.FirstOp,\n }),\n});\n```\n\nThe `nonce` and the gas fields come from a chain read and the bundler's gas estimate; `buildPackedUserOp`\ntakes them as inputs so the builder stays pure. The endpoint path and request body shape belong to the\nidentity authority, not to the SDK; the SDK exports the encoders (`encodeDeployFor`, `packInitCode`), so the\npermit fetch is described generically here.\n\n### Step 3, hash and sign with the passkey\n\n```typescript\nimport { aa } from '@citratelabs/sdk';\nconst { getUserOpHash, signUserOpWithPasskey } = aa;\n\nconst hash = getUserOpHash(op, ENTRYPOINT, 40204n);\n\n// Triggers the platform authenticator; encodes the assertion for the\n// WebAuthn validator and normalizes `s` to the lower half-order.\nconst signature = await signUserOpWithPasskey(hash);\nconst signedOp = { ...op, signature };\n```\n\n`getUserOpHash` commits to every field of the operation, including the chain id, so the signature is valid\nonly for this operation on chain 40204. `signUserOpWithPasskey` drives `navigator.credentials.get()` and\nthrows `WebAuthnSigningError` outside a secure context.\n\n### Step 4, submit to the bundler and wait\n\n```typescript\nimport { aa } from '@citratelabs/sdk';\nconst { BundlerClient } = aa;\n\nconst bundler = new BundlerClient(); // defaults to https://bundler.citrate.ai/rpc\n\n// Sanity check, the bundler must be on 40204:\nif ((await bundler.chainId()) !== 40204n) throw new Error('wrong chain');\n\nconst userOpHash = await bundler.sendUserOperation(signedOp, ENTRYPOINT);\nconst receipt = await bundler.waitForUserOperationReceipt(userOpHash);\nconsole.log('mined:', receipt);\n```\n\nOn a revert the client throws `BundlerRpcError` carrying the JSON-RPC payload, so you can surface ERC-4337\ncodes (for example `AA31`, paymaster deposit too low) directly. `waitForUserOperationReceipt` polls every\ntwo seconds for up to sixty seconds by default, which covers a normal inclusion plus a bundle interval.\n\n## Reference\n\nThe exports used above, all in `citrate-sdk-js/src/aa/`:\n\n| Symbol | Returns | Source |\n|---|---|---|\n| `uuidToUserId(uuid)` | the 32-byte userId | `address.ts` |\n| `predictWalletAddress(factory, impl, userId)` | the counterfactual account address | `address.ts` |\n| `encodeDeployFor(args)` | factory `deployFor` calldata | `userop.ts` |\n| `packInitCode(factory, factoryData)` | ERC-4337 `initCode` | `userop.ts` |\n| `encodeExecuteSingle(call)` | Kernel `execute` calldata | `kernel.ts` |\n| `buildPackedUserOp(args)` | the packed operation struct | `userop.ts` |\n| `packCitratePaymasterAndData(args)` | `paymasterAndData` with the category tag | `userop.ts` |\n| `PaymasterCategory` | `Standard`, `Recovery`, `FirstOp` | `types.ts` |\n| `getUserOpHash(op, entryPoint, chainId)` | the operation hash | `userop.ts` |\n| `signUserOpWithPasskey(hash, opts)` | the WebAuthn signature blob | `webauthn.ts` |\n| `BundlerClient` | the bundler JSON-RPC client | `bundler.ts` |\n\n## Design rationale\n\nThe deploy travels with the first operation rather than as a separate transaction, so onboarding is one\nsignature and an account that is never used costs nothing. The operation hash commits to the chain id and\nevery field, so a passkey signs exactly one operation on exactly one chain. The builder functions are pure\nand take chain reads as inputs, so the same code runs in the browser against the live bundler and in tests\nagainst pinned vectors.\n\n## What just happened\n\n- The account deployed itself on its first operation, through the `initCode`, and the factory registered it\n with the paymaster so sponsorship was allowed.\n- The passkey authorized the operation through `WebAuthnP256Validator`; no seed phrase ever existed.\n- The paymaster paid gas under the first-op budget.\n\nNext, add a recovery method in [guardians](/aa/guardians), or read the full [passkeys](/aa/passkeys)\nreference.\n\n## Access and canon\n\nPublic tier. A runnable builder tutorial; nothing here is secret. No private keys, mnemonics, or\ncredentials appear: the access token is the user's own identity token, the deploy permit is fetched at\nruntime, and passkey private material never leaves the authenticator. Use testnet values on chain 40204.\n\n## Source and verification\n\n- `citrate-sdk-js/src/aa/{address,userop,kernel,webauthn,bundler}.ts` at SHA `bc5a830`.\n- Validators and factory: `citrate-chain/contracts/src/aa/` at SHA `9d5959e`.\n\nStatus: Specified on chain 40204 (passkey-only accounts are not yet available there); the SDK helpers are implemented, pre-audit. Build against a local chain; do not custody material value. Re-verify the exports against\nthe SHAs before relying on this tutorial.\n"},"/apps/buyer":{"slug":"/apps/buyer","title":"Citrate Market (buyer)","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-buyer-webapp","syncedSha":"7d44b29","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"Verification tiers","anchor":"verification-tiers"},{"depth":3,"text":"Job lifecycle","anchor":"job-lifecycle"},{"depth":3,"text":"x402 payment bounds","anchor":"x402-payment-bounds"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The buyer side of Citrate Market, where you find a model provider, post a job, and pay for each\nrequest as it runs. It is for teams that want to buy compute and inference on the open marketplace\nand settle the cost per call rather than holding a balance.\n\n## What it is\n\nCitrate Market is where compute is bought and sold on the Citrate Network. This app\n(`citrate-buyer-webapp`) is the buyer-facing front end of that market. You browse the catalog of\nmodels, providers, and compute pools, post a job at a price you set, and the work settles back to\nyou when a provider has run it and the result has been checked.\n\nTwo purchase paths run side by side, and the app picks one for you depending on how you start. The\ngateway path posts a chat completion to the Citrate gateway and pays for each request with x402, a\nper-request payment protocol covered under [the x402 contract](/contracts/x402). The direct path\nposts the job on the public ledger itself, locking escrow at your maximum price and opening an\nauction that providers bid into. Both settle in wSALT on chain 40204. The gateway path is the\nquicker route; the direct path keeps the whole transaction on the public ledger, where you can read\nit back yourself.\n\nPayment is bounded by design. The x402 client signs for at most a fixed amount per round, defaulting\nto one SALT, and it will only ever pay in a single allowed token on a single chain. A payment\nrequest naming any other token or chain is rejected before it is signed. You can find the wider\nmarket in [the compute overview](/compute/pool) and the client library in\n[the marketplace SDK](/sdks/marketplace).\n\n## How to use it\n\n1. Open the app and browse the marketplace. Compare models by their per-1K pricing, providers by\n reputation, stake, region, load, and the verification tiers they support, and compute pools by\n mode, GPU count, throughput, and price.\n2. Open a provider to read its reputation, current capacity and load, supported models, and\n verification tiers in one place.\n3. Post a job. Pick a model, choose a verification tier, set a maximum price, and submit. The gateway\n path runs the request and auto-pays with x402; the direct path posts the job on the public ledger\n and opens the auction.\n4. Approve the payment in your account when prompted. Auto-pay stays inside the per-round ceiling and\n pays only in wSALT on chain 40204.\n5. Track the job through its states. On a bad outcome you are refunded, and a provider that misses its\n deadline is slashed.\n\n## Reference\n\nThe buyer journey is built from the screens below. Each cites the code that backs it.\n\n| Area | What you do | Source |\n|---|---|---|\n| Marketplace browse | Compare models, providers, and compute pools by price, reputation, stake, region, load, throughput, and supported verification tiers. | `app/design/DesignApp.jsx` |\n| Provider detail | Inspect one provider's reputation, capacity and load, supported models, and verification tiers. | `app/design/DesignApp.jsx` |\n| Post a job | Choose a model and a verification tier, set a maximum price, and submit through the gateway or direct path. | `lib/submitJob.ts`, `lib/submitDirectJob.ts`, `lib/submitTrainingJob.ts` |\n| Track results | Follow a job through its lifecycle, including the terminal outcomes. | `app/design/DesignApp.jsx` |\n| Pay with x402 | Pay per request with bounded auto-pay in wSALT; the client enforces the ceiling and the allowed token and chain. | `lib/submitJob.ts`, `lib/buyCredits.ts`, `lib/creditsClient.ts` |\n| Copilot | Ask marketplace and network questions in an in-app assistant that streams from the Citrate gateway. | `app/api/chat/route.ts` |\n\n### Verification tiers\n\nWhen you post a job you choose how the result is checked. The tier sets a price multiplier, defined\nin `app/design/DesignApp.jsx`.\n\n| Tier | Technique | Price multiplier |\n|---|---|---|\n| Standard | commitment | 1.0x |\n| Cryptographic proof | ZK proof, Groth16 | 1.5x |\n| Secure enclave | TEE attestation | 2.0x |\n\n### Job lifecycle\n\nA job moves through a defined set of states. The happy path is documented in `DESIGN_HANDOFF.md` and\nbacked by on-chain events such as `JobPosted`, `JobAssigned`, and `JobCompleted` parsed through the\nSDK.\n\n```text\nPosted -> Bidding -> Assigned -> Executing -> Verifying -> Completed\n```\n\nThe terminal outcomes are explicit. Completed settles to the provider. Expired refunds you when no\nbid arrives. Timeout slashes a provider that misses its deadline and refunds you. Failed refunds you\nwhen verification does not pass. Disputed is resolved by the contract.\n\n### x402 payment bounds\n\nThe x402 client enforces two limits, defined in `lib/submitJob.ts`.\n\n```ts\nexport const DEFAULT_MAX_PAY_WEI = 1_000_000_000_000_000_000n; // 1 SALT\nexport const ALLOWED_PAY_TOKENS: Address[] = [\n WRAPPED_SALT, // contracts.WrappedSALT from the address book\n];\n```\n\nThe allow-list must hold the live wSALT address from the [address book](/chain/addresses)\n(`contracts.WrappedSALT`). An older build hard-coded a pre-re-roll wSALT address that has no code on chain\n40204; if your copy of `lib/submitJob.ts` still contains a literal address, replace it with the book value\nand confirm it with `cast code`.\n\nThe per-round ceiling defaults to one SALT and the UI may set it lower. The allowed token is wSALT\nand the chain is 40204. Any other token or chain is refused.\n\n## Design rationale\n\nPaying for each request, rather than topping up a balance, keeps the buyer in control of cost at the\nfinest grain the market allows. The bounded auto-pay follows from that: a client that signs payments\non your behalf must never be able to sign an open-ended amount, so a finite per-round ceiling and a\nsingle allowed token and chain are applied to every request before it is signed. Offering the two\npurchase paths is the other deliberate trade. The gateway path is faster and hides the auction; the\ndirect path keeps the transaction on the public ledger where you can audit settlement yourself. We\nlet the buyer choose which property matters more for a given job.\n\n## Failure modes\n\nThis surface moves real funds, so it is built to fail closed.\n\n- The payment client never signs an unbounded x402 amount. A finite per-round ceiling is always\n applied, and a request naming any token other than wSALT or any chain other than 40204 is rejected\n before signing (audited as `RM-F1` and `BUYER_WEBAPP-002`).\n- The copilot route applies an IP rate limit before any gateway or inference call, because every\n request costs real money (tracked under SECREM-01 WEB-3). The default is 20 requests per minute,\n overridable by environment.\n- Outbound gateway targets are restricted by an allowlist in `lib/gatewayAllowlist.ts`, validated\n before a payment is signed.\n- No secrets appear in this page. Gateway and RPC hostnames are public; signer material lives in the\n account and environment, never in documentation.\n\n## Access and canon\n\nCommercial. This is paid marketplace operation: job posting, provider economics, and payment,\nintended for contracted buyers, and gated through the Codex chokepoint (`PLANSET/02_ARCHITECTURE.md`\nsection 4). Market participation settles in SALT, which pays for work and is not treated here as\nanything to hold. The wider network is on-premise by default and identity verification through VERI is part of membership; that envelope is described in\n[what Citrate is](/start/what-is-citrate).\n\n## Source and verification\n\n- Source repo: `citrate-buyer-webapp`, split from the Citrate monorepo on 2026-05-18.\n- Audited against: `7d44b29`.\n- Key paths: `app/page.tsx`, `app/design/DesignApp.jsx`, `app/design/sdkBridge.ts`,\n `app/api/chat/route.ts`, `lib/submitJob.ts`, `lib/submitDirectJob.ts`, `lib/marketplace.ts`,\n `lib/buyCredits.ts`, `lib/gatewayAllowlist.ts`, `lib/chatGuard.ts`, `DESIGN_HANDOFF.md`.\n- Status: **Implemented (pre-audit).** The x402 job submission, credits, server-side\n `MarketplaceClient` reads via `@citratelabs/marketplace-sdk`, and the gateway-backed copilot are\n wired and run against chain 40204. The browse catalog renders live SDK reads when a default model\n hash is configured and otherwise falls back to sample provider data, which the UI labels as\n `source: 'sample'` so the screen stays honest. Treat catalog figures as illustrative until live\n indexing is fully wired. This app has not completed an external audit. The README is monorepo-split\n boilerplate, so the screens here are audited against the app code and `DESIGN_HANDOFF.md`, not the\n README.\n"},"/apps/chatbot":{"slug":"/apps/chatbot","title":"Citrate Chat (gasless chat app)","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chatbot","syncedSha":"e3827c3","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"What gasless means here","anchor":"what-gasless-means-here"},{"depth":3,"text":"Contracts","anchor":"contracts"},{"depth":3,"text":"Inference source","anchor":"inference-source"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"A gasless chat app that runs inference on Citrate and signs in with a Citrate Keyring account, so you\ncan talk to a model on the network without holding any SALT. It is for anyone who wants to try a chat\napp native to Citrate, and for builders who want a reference for gasless sponsorship on the network.\n\n## What it is\n\nCitrate Chat is a public app that lets you talk to a model running on Citrate. Two things make it\ndifferent from an ordinary chat app, and both rest on Citrate being a substrate rather than a service\nyou have to trust.\n\nThe first is the account. You sign in with a Citrate Keyring account rather than a username and a\npassword. The account is created for you on first sign-in, so there is no key to set up by hand. The\n[passkeys page](/aa/passkeys) covers the account model in full.\n\nThe second is that it is gasless. Citrate has no native paymaster, so the app uses an EIP-2771\nmeta-transaction relay: you sign a request, which is free, and a relayer funded by Citrate Inc.\nsubmits the on-chain transaction and pays the fee. You never need SALT in hand to use it.\nThat relay is described under [the paymaster page](/aa/paymaster).\n\nThe mental model is plain. You chat normally. The model reply streams back to you, and when receipt\nanchoring is on, a short on-chain record of the exchange is written to a registry contract, with the\nnetwork covering the fee rather than you. The on-chain inference path, where a model call is itself a\nledger operation through the inference RPC methods on [chain RPC](/chain/rpc), is specified and\npartly built but not yet wired; today the model reply is served over the gateway, and the gasless\non-chain part is the receipt.\n\n## How to use it\n\n1. Open the app. Signed out, you see the hero and a sign-in button.\n2. Sign in. An account is created or linked for you, with no email or key setup required\n (`src/components/auth-provider.tsx`).\n3. Type a message and send it. The reply streams in real time from the configured inference source\n (`POST /api/chat`, `src/app/api/chat/route.ts`).\n4. When receipt anchoring is on, the app builds an EIP-2771 `ForwardRequest`, you sign it for free,\n and it is submitted through the relayer (`src/hooks/use-sponsored-write.ts`, then `POST /api/relay`).\n5. Once signed in, your chats can be saved as threads in the sidebar when a database is configured.\n\n## Reference\n\nThe screens are built from the components below, in `src/components/`.\n\n| Screen | What you see | Source |\n|---|---|---|\n| Hero | The empty state with suggested-prompt chips and the gasless explainer. | `src/components/hero.tsx` |\n| Conversation | Streaming message bubbles, an on-chain receipt chip, copy and regenerate. | `src/components/conversation.tsx` |\n| Composer | An auto-growing input, send and stop, a model badge, Cmd+Enter to send. | `src/components/composer.tsx` |\n| Header | The sign-in button or your account address chip. | `src/components/header.tsx` |\n| Sidebar | Thread history, when signed in and a database is configured. | `src/components/sidebar.tsx` |\n| Settings | Chain info, account address, balance, and a read-back of recorded receipts. | `src/components/settings.tsx` |\n\n### What gasless means here\n\nYou sign a typed-data `ForwardRequest` with your account. The relayer route\n(`src/app/api/relay/route.ts`) checks that your authenticated session owns the `from` address,\nrate-limits the request to protect the Citrate Inc. relayer account, validates the signature on-chain with the\nforwarder's `verify`, then calls the forwarder's `execute` and pays the gas. Only the relayer can call\n`execute`, and it appends your address to the call per the EIP-2771 standard.\n\n### Contracts\n\n| Contract | Role | Source |\n|---|---|---|\n| `CitrateForwarder` | EIP-2771 forwarder; verifies the signed request and executes it on the user's behalf. | `contracts/src/CitrateForwarder.sol` |\n| `ChatRegistry` | Records a content hash per thread in contract state, so receipts can be read back with `eth_call`. | `contracts/src/ChatRegistry.sol` |\n\nBoth are deployed to testnet and covered by 14 passing Foundry tests. `ChatRegistry` stores receipts\nin state rather than only emitting events, because the network's read path uses `eth_call` rather than\nevent indexing.\n\n### Inference source\n\nThe chat route resolves an inference provider and streams the reply. The working modes are a\nself-hosted local endpoint and an OpenAI-compatible gateway; the on-chain mode is present in the\ninterface but throws until it is wired (`src/lib/inference/index.ts`). The gateway reply is streamed,\nnot an on-chain call. The inference RPC methods that the on-chain mode will use are on\n[chain RPC](/chain/rpc).\n\n## Design rationale\n\nThe account and the gas rail both exist to remove the two things that usually stop a newcomer from\ntrying a network app: setting up keys, and acquiring the fee token first. A Citrate Keyring account is\ncreated on sign-in, so there is no key ceremony. The EIP-2771 relay lets the network, not the user,\npay the fee, so there is nothing to acquire before the first message. The cost of sponsoring gas is\nthat the Citrate Inc. relayer account is a target, which is why the relay rate-limits and checks session\nownership before it ever signs. We separated the receipt from the inference deliberately: the on-chain\nreceipt is gasless and live today, independent of whether the model call has moved on-chain yet.\n\n## Failure modes\n\nThe relay spends real funds on a user's behalf, so it is built to fail closed.\n\n- The relay verifies that your authenticated session owns the `from` address before sponsoring any\n transaction. A request to relay for an address you do not own is refused.\n- The relay enforces per-address and per-IP rate limits and a daily budget before it touches the\n chain, so a flood cannot drain the Citrate Inc. relayer account.\n- Only the relayer may call the forwarder's `execute`, and the signature is validated on-chain before\n execution.\n- The on-chain inference path is not wired. Calling it throws rather than silently degrading, so the\n app cannot appear to run a ledger inference when it is actually serving from the gateway.\n- The relayer key, the auth provider secret, the database URL, and any chat encryption key are\n server-only environment values and do not appear in this page. The public RPC at\n `https://rpc.citrate.ai` and the public contract addresses are not secrets.\n\n> Repo hygiene, flagged and not transcribed: the working tree carries a `.env.local` holding a live\n> testnet relayer private key, a chat encryption key, a Neon Postgres connection string, and a Vercel\n> OIDC token. The file is gitignored and not in history, but the values are live and should be rotated.\n> None of them are reproduced here.\n\n## Access and canon\n\nPublic. This is the kind of open, developer-facing app the Codex keeps public: the concepts and\nreference a developer needs to build a gasless app on Citrate. It runs against the Citrate Network\ntestnet at chain id 40204. SALT settles the work the relay performs and the user holds none of it.\n\n## Source and verification\n\n- Source repo: `citrate-chatbot`.\n- Audited against: `e3827c3`.\n- Key paths: `src/app/api/relay/route.ts`, `src/app/api/chat/route.ts`,\n `src/hooks/use-sponsored-write.ts`, `src/components/auth-provider.tsx`,\n `src/lib/inference/index.ts`, `contracts/src/CitrateForwarder.sol`, `contracts/src/ChatRegistry.sol`,\n `README.md`, `.agentile/PRODUCT_SPEC.md`.\n- Status by area:\n - Gas rail (sprint S-2): **Implemented (pre-audit).** `CitrateForwarder` and `ChatRegistry` are\n deployed to testnet, the relay route works, and the gasless receipt write is live.\n - Account sign-in and streaming chat (sprint S-1): **Implemented (pre-audit).**\n - On-chain inference (sprint S-3): **Specified.** The provider interface exists; the on-chain mode\n throws until wired. The live reply is served over a gateway or a local endpoint.\n - Encrypted thread history (sprint S-4): **Implemented (pre-audit)**, partial; persistence works and\n summarization is not fully live.\n - Open dependencies the deployer must supply: a live inference gateway URL, a model registered in\n `ModelRegistry` (the registry is empty today), and account-provider credentials.\n - No external audit has been completed.\n"},"/apps/comms":{"slug":"/apps/comms","title":"Citrate Comms","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-comms/README.md, citrate-comms/crates, citrate-comms/PLANSET","syncedSha":"67557cf","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Citrate Comms is an end-to-end encrypted team workspace, messaging, a customer record, and project\nmanagement in one self-hostable binary, where agents take part as ordinary members of a conversation. It is\nfor any team that needs to collaborate privately, including on-premise or air-gapped, without trusting a\nserver to keep their secrets.\n\n## What it is\n\nCitrate Comms is built on one boundary, and that boundary explains everything else. A relay moves messages\nbetween members, but it never reads them. It is trusted to keep the lights on and to put messages in order;\nit is never trusted with what the messages say.\n\nConcretely, members sign in with a cryptographic handshake tied to their Citrate account, and from then on\nevery message is encrypted on the sending member's machine and decrypted only on the receiving members'\nmachines. The relay stores and forwards ciphertext and routing information, nothing more. All plaintext, all\ngroup secrets, and all customer and project records live only on member clients. An agent reading a channel\nis cryptographically the same as a person reading it: there is no shadow key and no plaintext kept in escrow\nfor the server.\n\nThis server-blind property is not a promise in a policy document, it is held by the way the code is\ncompiled. The relay links the core library with its message-group module switched off, so the only place\ngroup secrets could be handled is simply not present in the relay binary. Code in the relay that tried to\nread a group secret would fail to compile.\n\n## How to use it\n\nCitrate Comms is one binary you run yourself, alongside the rest of your tools. The shape of using it is:\n\n1. Run the relay where your team can reach it, on your own hardware, on-premise, or on an air-gapped network\n beside an on-premise agent.\n2. Open the native client and sign in with the cryptographic handshake against your Citrate account. Your\n account address is your identity in the workspace.\n3. Create channels, forums, and direct messages, and bring your customer records and project tracking into\n the same encrypted space.\n4. Enroll an agent as a member when you want one. The agent holds its own keys and joins the group like any\n other member, reachable over a local socket bridge.\n\nStep by step tutorials for self-hosting the relay, enrolling an agent, and anchoring an audit log to the\nCitrate Network follow as the remaining components land.\n\n## Reference\n\nThe workspace is a set of Rust crates. The cryptographic and transport spine is built and tested; the\nremaining crates fill in on the published plan.\n\n| Crate | Status | Role |\n|---|---|---|\n| `comms-proto` | Implemented | Wire types: envelope, group id, commit, welcome, application message, role assertion, audit record |\n| `comms-core` | Implemented (mls, identity, audit, store) | Group messaging over OpenMLS, sign-in identity, the audit chain, and the ciphertext store; the role and domain modules follow |\n| `comms-relay` | Implemented | The server-blind delivery service: total order per group, the key-package directory, the audit log |\n| `comms-wire` | Implemented | The client-half relay wire protocol, with no MLS present |\n| `comms-session` | Implemented | A member session: sign-in identity, key package, and send and receive |\n| `comms-member-daemon` | Implemented | An account-owned MLS member with an in-process relay over a loopback socket |\n| `comms-agent-bridge` | Implemented | A local socket bridge that lets an agent join as a member holding its own keys |\n| `comms-client` | Implemented (shell, primary channel) | The native client; the shell and the main channel screen are translated from the design handoff |\n| `comms-release` | Implemented | A reproducibility manifest and an Ed25519 release signer (COMMS-S4) |\n| `comms-client-proof` | Implemented | A visual golden-image test harness (COMMS-S5) |\n\nThe cryptography is standard and named:\n\n```text\ngroup messaging MLS (RFC 9420) via OpenMLS\nciphersuite MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519\n X25519 key exchange, AES-128-GCM, Ed25519 signatures\nat rest RocksDB column families encrypted with AES-256-GCM-SIV\n (nonce-misuse-resistant, RFC 8452); classical today, with a\n Kyber-768 + X25519 hybrid key wrapping roadmapped (PLANSET/07)\naudit BLAKE3 hash-chained append-only log,\n optionally anchored to the Citrate Network for tamper-evidence\n```\n\nTo build and test the workspace:\n\n```bash\ncargo build --workspace --release --locked\ncargo test --workspace\ncargo clippy --workspace --all-targets -- -D warnings\n```\n\nFormal invariants are written in TLA+ (commit ordering and audit-chain contiguity), and each capability is\nspecified as a Gherkin feature. Agents reach the workspace through the same conversation surface they reach\nthe rest of the network with, described under [chain RPC](/chain/rpc), and the research that the audit and\nverification design rests on is in [research](/research/learning).\n\n## Design rationale\n\nMost team tools put the server in the middle and trust it to behave: it can read everything, and you are\nasked to believe it will not. For a team working under a compliance regime, or on an air-gapped network, that\ntrust is the thing they cannot grant. Citrate Comms removes the question by removing the server's ability to\nread, and it does so where it cannot quietly be undone, in the build graph rather than in configuration. The\nsame decision is what lets an agent be a full member rather than a privileged listener: if the server cannot\nread the channel, an agent that reads it must be a member with keys, exactly like a person. The cost is that\nthe relay cannot offer server-side features that depend on reading content, such as server-side search; that\nwork moves to the clients, which is where the plaintext already is.\n\n## Access and canon\n\nCommercial. Citrate Comms is a paid-seat product, and this page documents the public architecture and crate\nmap at a pinned commit. It carries no secrets: no keys, no tokens, and no private endpoints. The relay's\nloopback administration and bearer-token operational details, and any operator deployment credentials, stay\nout of every tier. The system's security rests on the protocol and the build-graph-enforced server-blind\nrelay, not on keeping this page vague. It runs on-premise and air-gapped alongside the on-premise compliance\nagent in [the air-gapped agent sidecar](/apps/nist-agent).\n\n## Source and verification\n\n- Source repo: `citrate-comms`, `README.md` and `PLANSET/`.\n- Audited against SHA: `0a4989e`.\n- Key paths: `crates/comms-proto`, `crates/comms-core` (`mls`, `identity`, `audit`), `crates/comms-relay`,\n `crates/comms-agent-bridge`, `crates/comms-client`.\n- Status: Implemented, accepted into the federation on 2026-06-14. The native Rust workspace has shipped\n through the cryptographic and transport spine and is in interface hardening (COMMS-S5 active, S0 through\n S4 complete), with 303 tests passing across the workspace (121 Rust + 182 TypeScript). It is pre-1.0: the agent bridge is built (the\n socket IPC and the account-owned MLS member), while the privileged agent runtime is not yet wired, and\n the at-rest encryption is classical with a post-quantum hybrid roadmapped. Only an internal self-audit has\n run; there is no external audit yet.\n"},"/apps/dashboard":{"slug":"/apps/dashboard","title":"Learning dashboard","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-dashboard/{app/, lib/daemon-api.ts}","syncedSha":"727e62d","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The learning dashboard is the live window onto Citrate Orchard, the federated-learning surface where models\ntrain across nodes without the training data leaving them. It shows each learning cycle as it runs, who\ncontributed, and which mentor pairings were committed, all read from a public, read-only view of the\nnetwork.\n\n## What it is\n\nCitrate Orchard runs a federated-learning loop: one round roughly every 25 seconds, in which nodes submit\nembeddings, an on-chain Belnap-FOUR aggregator picks a canonical signal, the routing model retrains, and\nmentor pairings are committed on chain. The dashboard is the observability layer over that loop. It is a\nNext.js application, and it holds no canonical state of its own. Live cycle state, embeddings, mentor\npairings, and contribution scores are read through from the network: from chain RPC and from a read-only\ndaemon API. The only thing it persists locally is account-facing convenience, profiles, invite tokens, and\ncycle subscriptions. The chain is the source of truth, and the dashboard does not mirror it.\n\nThe loop matters because of what it does not move. Embeddings are the only thing nodes publish; the data\nthose embeddings were computed from stays on the node. The dashboard makes that loop legible to the people\nwatching it without becoming a second copy of it.\n\n## How to use it\n\nThe dashboard is read-mostly. Anyone can follow the cycles; participating in a cycle needs an account.\n\n1. Open the dashboard and pick Cycles to see what is running now.\n2. Click any cycle to see its contributors and the mentor pairings it committed.\n3. Open Experiments to follow the research hypotheses and their status.\n4. To take part, open Profile, sign in, link your Citrate account, and join a cycle. Your contribution\n score updates as cycles run.\n\n## Reference\n\nThe screens and what they read (`app/`, `lib/daemon-api.ts`).\n\n| Screen | Route | What you see | Code |\n|---|---|---|---|\n| Home | `/` | The loop explained, cards into Cycles, Experiments, and Profile, and the public daemon and RPC endpoints. | `app/page.tsx` |\n| Cycles | `/cycles` | A live list of learning cycles with status, participant count, and start and finalize times. | `app/cycles/page.tsx` |\n| Cycle detail | `/cycles/[id]` | One cycle's embedding contributors and committed mentor pairings. A missing cycle renders a standard 404. | `app/cycles/[id]/page.tsx` |\n| Experiments | `/experiments` | The three research hypotheses, what each measures, and its current status. | `app/experiments/page.tsx` |\n| Profile | `/profile` | Sign in, link your Citrate account, set display name, bio, and timezone, view subscriptions, and join a cycle. | `app/profile/page.tsx` |\n\nA cycle moves through a fixed lifecycle, and the status badges map one-to-one onto it\n(`lib/daemon-api.ts`):\n\n```text\nembeddings_open → embeddings_closed → aggregated → trained → matched → finalized\n```\n\nThe Experiments page tracks three hypotheses from the second research paper. They are spec-locked and\nawaiting testnet measurement, so the page renders the plan, not results\n(`app/experiments/page.tsx`):\n\n| Hypothesis | Question | Status |\n|---|---|---|\n| H1 | Belnap-FOUR aggregation versus a flat mean under injected mislabels, on a 4-node setup. | spec-locked, awaiting testnet |\n| H2 | Whether adapter-composition accuracy follows a power law as adapters are added, to 100. | spec-locked, awaiting testnet |\n| H3 | Routing-model convergence with Byzantine validators below the BFT threshold, to 30 nodes. | spec-locked, awaiting testnet |\n\nData sources are read-only. The daemon API at `federated.citrate.ai/api/{cycles, embeddings, mentors}` is\nGET-only and enforced as such at the source by a tripwire that forbids mutating verbs; chain reads go to\n`rpc.citrate.ai` on chain 40204 (`lib/daemon-api.ts`).\n\n## Design rationale\n\nA dashboard over a live network has one temptation, to cache the network into itself and slowly drift out\nof truth. This one refuses that. Cycle pages render on every request with no caching, because cycle state\nis live, and the dashboard reads through to the chain and the daemon rather than mirroring them, so the\nchain stays canonical. The daemon API it depends on is read-only by construction, which means the\nobservability layer cannot become an accidental control surface. Identity is the same discipline: the\naccount a request acts as is derived server-side from a verified session token, never from an identifier\nsupplied in the URL or body, so one account can never read or write another's profile.\n\n## Failure modes\n\nThe dashboard depends on services it does not own, so it is built to degrade rather than crash or leak.\n\n- When the daemon is unreachable, a cycle page shows a clear \"Daemon API unavailable\" panel instead of\n failing, and a transient error on one of the three reads behind a cycle detail degrades that panel\n rather than the page (`app/cycles/page.tsx`, `app/cycles/[id]/page.tsx`).\n- Upstream daemon error text, status lines, body snippets, connection strings, is never reflected to the\n client. The server logs it and returns a fixed-shape 503 (audit `CITRATE_DASHBOARD-2026-05-31-006`).\n- The profile and invite APIs derive identity from a verified session token server-side and reject any\n client-supplied identifier, closing an account-enumeration path found in audit\n (`CITRATE_DASHBOARD-2026-05-31-001`).\n\n## Access and canon\n\nTier: commercial. The dashboard surfaces operator- and participant-facing learning operations, cycle\ninternals, contribution scoring, and experiment tracking, intended for contracted principals rather than\nanonymous scraping.\n\nThis is the observability window onto Citrate Orchard, not a place where learning data lives. Citrate\nOrchard's premise is that models train across nodes without the training data leaving them: a node\npublishes embeddings, not its underlying data, so the data stays on the node. The dashboard reads only the\npublic, read-only view of that loop and holds no canonical state. No secrets appear in this page; the\nendpoints shown are public hostnames, and configuration lives in environment. See\n[federated learning](/research/learning) for Citrate Orchard and the research the loop rests on, and\n[the compute pool](/compute/pool) for how nodes join.\n\n## Source and verification\n\n- Source repo: `citrate-dashboard`, split from the Citrate monorepo on 2026-05-18, audited against SHA\n `727e62d`.\n- Key paths: `app/page.tsx`, `app/cycles/page.tsx`, `app/cycles/[id]/page.tsx`, `app/experiments/page.tsx`,\n `app/profile/page.tsx`, `app/api/profile/route.ts`, `app/api/invites/route.ts`, `lib/daemon-api.ts`.\n- Stack: Next.js 16, React 19, Prisma on Vercel Postgres for profiles and invites, ethers for chain reads,\n Privy for account sign-in.\n- Status: Implemented (pre-audit), pilot, labelled `RM-FL-5` in the application. The Experiments page\n renders the plan: the three hypotheses are spec-locked and awaiting testnet, so treat experiment results\n as pre-data until the measurement work lands. The screens here are verified against the application code\n at this SHA; the repository README is monorepo-split boilerplate and is not the source of these claims.\n Tier 1 audit applies before a stable release.\n"},"/apps/explorer":{"slug":"/apps/explorer","title":"CitrateScan Explorer","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-explorer/README.md, src/scan/screens, src/app/api","syncedSha":"6faab8a","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"Screens","anchor":"screens"},{"depth":3,"text":"Read API, Etherscan request shape","anchor":"read-api-etherscan-request-shape"},{"depth":3,"text":"MCP server","anchor":"mcp-server"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"CitrateScan is the public explorer for the Citrate Network. It reads the BlockDAG, transactions,\naccounts, contracts, and the model contracts that run on chain, and it is built so you can read it with\nyour eyes, ask it in plain English, or drive it from an agent.\n\n## What it is\n\nCitrateScan is a DAG-native, agentic block explorer for [Citrate Network](/chain/rpc), live on chain id\n40204. Most explorers assume a single line of blocks and count confirmations. Citrate is a BlockDAG under\nGhostDAG, so a block carries a blue score rather than a plain height, has one selected parent and up to ten\nmerge parents, and gains confirmation by depth. CitrateScan shows a depth≥100 flag once the current blue\nscore minus the block's blue score is at least 100. That flag is a display heuristic, not protocol\nfinality: confirmation on the testnet is probabilistic and checkpoint finality is specified, not running. The consensus model behind this is covered under [Citrate Network consensus](/chain/consensus).\n\nIt is agentic in two senses. Every entity page leads with a plain-English summary of what you are looking\nat, and a built-in \"Ask CitrateScan\" capability answers questions using read-only on-chain tool calls,\nlinking the reads behind each answer. The same tools are exposed to outside agents over a Model Context\nProtocol server, so a tool like Claude, ChatGPT, or Cursor can treat CitrateScan as its read surface for\nthe network.\n\nThe product is a client single-page app served from one route, with a hash router and a command palette,\nbacked by a read API. An always-on indexer worker streams new heads into a Postgres database; the app reads\nthat index and falls through to live RPC when the index is not provisioned, so it degrades honestly rather\nthan failing. (Source: `citrate-explorer/README.md`; `src/app/page.tsx`, which loads `@/scan/app`.)\n\n## How to use it\n\n1. Open CitrateScan and type into the search bar: an account address, a transaction hash, a block id, a\n contract, or a plain-English question. Press Enter, or press `⌘K` for the command palette, which\n classifies your input and either routes to the right screen or asks the agent.\n2. On an entity page, read the plain-English summary first, then drill into the detail below it.\n3. To ask a follow-up, open Ask CitrateScan and type your question. The answer cites the on-chain reads it\n rests on.\n4. To verify a contract, open the contract's page and submit its source for recompile-and-diff.\n5. To use it from a script or an agent, get an API key from the developer hub, then call `/api/v1` for\n tooling that already speaks the Etherscan request shape, or point your agent at `/api/mcp`.\n\nFor a guided run, see [explore a transaction](/apps/tutorials/explore-a-transaction).\n\n## Reference\n\n### Screens\n\nThe app is a single page whose hash router swaps between these screens. Code lives in\n`citrate-explorer/src/scan/screens/`.\n\n| Screen | What you see | Source |\n|---|---|---|\n| Home and search | Search bar that accepts an address, transaction hash, block, contract, or a question; live chain status; recent activity. `⌘K` opens a command palette. | `screens/home.tsx`, `src/scan/app.tsx` |\n| Transaction | One transaction with a plain-English explanation, status, value in dual units (SALT and raw grains), and decoded detail. | `screens/tx.tsx` |\n| Block | A DAG block: blue score, selected parent and merge parents, depth (the depth≥100 flag), included transactions. | `screens/entity.tsx` |\n| Address | Balance, transaction history, and activity for an account. | `screens/entity.tsx` |\n| Token | Credit overview and transfers. | `screens/entity.tsx` |\n| Contract | Contract code, ABI, and a read surface; an address is treated as a contract only after `eth_getCode` confirms it carries code. | `screens/contract.tsx` |\n| Verify | Submit source for multi-version `solc` recompile-and-diff verification, run in a sandboxed microVM. | `screens/verify.tsx` |\n| Live DAG | A real-time view of the DAG: multiple tips, selected and merge parents, blue ordering. | `screens/dag.tsx` |\n| Ask CitrateScan | An agent drawer that answers questions with read-only chain tool calls and links its evidence. | `screens/agent.tsx` |\n| Settings and developer hub | Theme and verbosity settings, plus API keys and an endpoint reference. | `screens/settings.tsx` |\n\n### Read API, Etherscan request shape\n\n`GET /api/v1?module=&action=&...&apikey=` returns the `{ status, message, result }` envelope that existing\nEtherscan-shaped tooling expects, so those scripts work against CitrateScan with the base URL changed. The\n`proxy` module is a JSON-RPC passthrough restricted to an allowlist of read methods; `account`,\n`transaction`, `block`, `logs`, `stats`, and `gastracker` modules wrap indexed or RPC reads. Requests are\nAPI-key-gated and rate-limited. Index-dependent actions return an honest \"no data\" or \"pending\" message when\nthe indexer and database are not provisioned, rather than inventing a result.\n(Source: `citrate-explorer/src/app/api/v1/route.ts`; `EXPLORER_SPEC.md` section 3.)\n\nDedicated read endpoints sit under `/api/`:\n\n| Endpoint | Returns | Source |\n|---|---|---|\n| `/api/tx/[hash]` | A transaction and receipt, enriched with the block timestamp, blue score, and the depth≥100 flag (JSON field `finalized`, a display heuristic, not protocol finality), in one fetch. | `src/app/api/tx/[hash]/route.ts` |\n| `/api/blocks`, `/api/blocks/[id]` | Recent blocks and a single block. | `src/app/api/blocks/` |\n| `/api/address/[addr]` | Account balance and activity. | `src/app/api/address/[addr]/route.ts` |\n| `/api/contract/[addr]` | Contract code, ABI, and read surface. | `src/app/api/contract/[addr]/route.ts` |\n| `/api/dag`, `/api/dag/stream` | DAG stats, and a live stream of new heads. | `src/app/api/dag/` |\n| `/api/search` | Classifies a query and resolves it to an entity. | `src/app/api/search/route.ts` |\n| `/api/latest`, `/api/health` | Latest activity and a health check. | `src/app/api/latest/`, `src/app/api/health/` |\n| `/api/verify`, `/api/verify/[guid]` | Submit and poll a contract verification. | `src/app/api/verify/` |\n\n### MCP server\n\n`/api/mcp` is a read-only Model Context Protocol server. It speaks JSON-RPC 2.0 over HTTP POST\n(`initialize`, `tools/list`, `tools/call`, `ping`, and `notifications/initialized`); a `GET` returns a\ndiscovery manifest. The tools are generated from the same `citrateTools()` the in-app agent uses, so the\ntwo surfaces cannot drift, and they include `getBlock`, `getTransaction`, `getAddress`,\n`searchTransactions`, `getContractCode`, `getToken`, `findTransfers`, and `ledger`. Server info advertises\n`readOnly: true` and dual-unit amounts (SALT and raw grains). Calls are rate-limited per IP, or per API key\nwhen one is presented, and audited under the caller's key identity.\n(Source: `citrate-explorer/src/app/api/mcp/route.ts`, `src/lib/ai/tools.ts`.)\n\n## Design rationale\n\nA linear-chain explorer would read Citrate wrongly: it would show a height where the network orders by blue\nscore, and it would offer a confirmation countdown that does not exist here. CitrateScan reads the DAG in\nthe network's own terms so that what you see matches what the network actually decided. The agent and the\nMCP server share one tool set rather than two, which is the reason the in-app answers and the external\nanswers stay consistent: there is no second list to fall out of date. The indexer is kept off the request\npath and falls through to live RPC, so the app still answers when the database is absent, at the cost of\nsome history that only the index can serve.\n\n## Failure modes\n\n- The read API and the MCP server expose read-only surfaces. The MCP server advertises `readOnly: true`,\n and the `proxy` module is held to an allowlist of read methods, so a write method routed through it is\n refused rather than passed along.\n- Write and relay paths, including the gasless relayer, are out of scope for the read surfaces documented\n here.\n- `getblockcountdown` on the Etherscan-shaped surface returns an error on purpose, because confirmation on\n Citrate is measured by depth. Read the DAG stats and the depth rule instead of waiting for a countdown.\n- Index-dependent reads return an explicit \"pending\" or \"no data\" message when the indexer is not\n provisioned. They do not fabricate a result, so a degraded deployment is visible rather than silent.\n\n## Access and canon\n\nPublic. CitrateScan is shared network infrastructure: open, self-hostable, and read-only across the surfaces\ndocumented here. A developer needs it to build, and it exposes no write path, so it stays public. No\nsecrets appear on this page. Endpoints are public routes, and API keys are issued to you inside the app and\nmust never be pasted into shared docs.\n\n## Source and verification\n\n- Source repo: `citrate-explorer` (brand: CitrateScan), Apache-2.0, Citrate Inc.\n- Audited against: `6faab8a`.\n- Key paths: `src/app/page.tsx`, `src/scan/screens/`, `src/app/api/v1/route.ts`,\n `src/app/api/mcp/route.ts`, `src/app/api/tx/[hash]/route.ts`, `src/lib/ai/tools.ts`. Reference specs:\n `README.md`, `EXPLORER_SPEC.md`.\n- Status: Implemented (pre-audit). Per the repo, bootstrap is complete and the indexer and agent\n foundation are in progress; treat indexed and agent features as pre-GA, since they read through to live\n RPC and skip persistence until a database is provisioned. This page describes code at the pinned SHA and\n links to it rather than copying it.\n\nSee also [Citrate-native operations](/apps/native) for the chain operations the explorer reads.\n"},"/apps/landing":{"slug":"/apps/landing","title":"The Citrate marketing site","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-landing/README.md, citrate-landing/src/app, citrate-landing/src/lib","syncedSha":"63adc44","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The public website for the Citrate Network is the first place most people meet us. It explains what the\nnetwork is, who it serves, and how to reach the team, and it holds none of your plaintext: anything you type\ninto a form is encrypted before it is written down.\n\n## What it is\n\nThe marketing site is a plain website with one unusual property. It is built on Next.js 16 with the App\nRouter, backed by Neon Postgres through Drizzle, and runs on Vercel. Around twenty pages describe the\nnetwork and the institutions it is built for, from the core marketing surfaces to the American Learning\nFederation, Groves, membership, join, and desktop-download flows. Three contact forms let you reach us.\n\nThe property worth knowing is that the site never keeps what you type in readable form. Every form\nsubmission is encrypted with AES-256-GCM before it reaches the database, so the stored columns are\nciphertext and nothing else. The only plaintext use is a notification email so the team can write back. The\nsite is a public front door for the network described in [what Citrate is](/start/what-is-citrate); it makes\nno claim of its own beyond that.\n\n## How to use it\n\nYou read the pages, and if you want to talk to us, you submit a form.\n\n1. Browse the pages: the core surfaces are home, solutions, technology, compliance, constitution, about,\n products, enterprise, resources, FAQ, and legal, with further pages for the American Learning Federation,\n Groves, membership, the join flow, open source, and the desktop download. Each is a server-rendered page\n under `src/app/`.\n2. Choose the form that fits. Contact is for general inquiries, host-compute is to apply to run hardware on\n the network, and verification-packet is to request our compliance and security documentation.\n3. Fill in the fields and submit. You receive a confirmation, and the team is notified by email and follows\n up.\n\nThere is no account to create and nothing to install. The site is read and submit.\n\n## Reference\n\nThe core pages and the three form endpoints, each citing its path in `citrate-landing`.\n\n| Page | Route | What it covers |\n|---|---|---|\n| Home | `/` | Hero, the network at a glance, the sectors and public doors |\n| Solutions | `/solutions` | What you can build and run on the network |\n| Technology | `/technology` | The substrate, consensus, and on-premise model |\n| Compliance | `/solutions/compliance` | The compliance posture by deployment context |\n| Constitution | `/constitution` | Network governance |\n| Products | `/products` | The applications and surfaces on the network |\n| Enterprise | `/enterprise` | The on-premise enterprise offering |\n| About | `/about` | The team and the mission |\n| Resources | `/resources` | Documentation and reading |\n| Legal | `/legal` | Terms and policies |\n\nFurther pages cover the American Learning Federation (`/alf`), Groves (`/groves`), membership\n(`/membership`), the join flow (`/join`), open source (`/open-source`), and the desktop download\n(`/download/desktop`, `/download/get`). The host-compute application posts to `/api/host-compute`; it has no\ndedicated page.\n\n| Form endpoint | Method | Purpose |\n|---|---|---|\n| `/api/contact` | `POST` | General contact |\n| `/api/host-compute` | `POST` | Apply to host compute |\n| `/api/verification-packet` | `POST` | Request the verification packet |\n| `/api/challenge` | `GET` | Issues a short-lived, single-use submission token |\n\nSearch and machine readers are served by a sitemap, a `robots.txt` written to welcome agents, JSON-LD for\n`Organization` and `WebSite`, and dynamic Open Graph images from `/api/og`.\n\n## Design rationale\n\nA site that gathers inquiries from schools, hospitals, and contractors is gathering names and email\naddresses, which are exactly the records those institutions are careful with. So the site is built to hold\nnone of it in the clear. Submissions are written as AES-256-GCM ciphertext through envelope encryption\n(`src/lib/encryption.ts`), and email is deduplicated with a keyed HMAC blind index, so even the lookup value\nis not your address in plaintext. Submissions pass an origin check, a hidden honeypot, a Cloudflare Turnstile\nchallenge, a Postgres-backed sliding-window rate limit, and the single-use token from `/api/challenge`. The\ntrade is that a form submission does a little more work before it lands; for the records involved, that is\nthe right trade.\n\n## Access and canon\n\nPublic. A marketing site is public by definition, and this page carries no secrets, keys, or private\nendpoints. None live in the repository either: `.env*` files are ignored by git, only `.env.example` is\ncommitted, and the real values for the encryption key, database URL, and SMTP password live in Vercel project\nenvironment variables. The encryption is the load-bearing fact for a visitor: forms are stored as ciphertext\nonly, and the single plaintext use is the team's reply.\n\n## Source and verification\n\n- Source repo: `citrate-landing`, `README.md`.\n- Audited against SHA: `63adc44`.\n- Key paths: `src/lib/encryption.ts`, `src/lib/schemas.ts`, `src/app/api/contact/route.ts`,\n `src/app/api/host-compute/route.ts`, `src/app/api/verification-packet/route.ts`,\n `src/app/api/challenge/route.ts`, `src/app/` (nine pages).\n- Status: Implemented. The site is in production and actively maintained, with a strict Content Security\n Policy carrying a per-request nonce, HSTS, an OWASP ZAP baseline scan in CI, and Playwright tests across\n five viewports. The posture statements on the site are descriptive; they are not a third-party\n certification.\n"},"/apps/learning-center":{"slug":"/apps/learning-center","title":"Citrate Learning Center","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-learning-center/{README.md, gui/, cli-school-bootstrap/, Cargo.toml}","syncedSha":"a34f976","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Citrate Learning Center is the classroom application for school pilots: students do their coursework,\nteachers run their classrooms, and administrators provision and oversee the school. It is a native desktop\napplication, and it is built around one rule, student and guardian data stays on the school's own hardware,\non Citrate Ground, and never leaves it for the public Citrate Network.\n\n## What it is\n\nLearning Center is a desktop client written in Rust with a Slint interface, so it runs as a native window\nbacked by a local service layer rather than a web page. A school installs it; it is not picked up ad hoc by\nindividuals. The data it works with, rosters, corrections, classroom membership, lives encrypted on the\nmachine it runs on. The public Citrate Network is consulted only for what genuinely belongs there: a\nparticipant's role is read from chain 40204 through a gateway, never asserted by the client, and students\nappear on chain only as pseudonymous identifiers, never by name.\n\nThe application is one of three crates in a Cargo workspace (`Cargo.toml`):\n\n- `gui/citrate_learning_center`, the desktop window and every classroom and administration screen.\n- `gui/citrate_edu_app`, the education backend: encryption, identity, roles, the roster, classroom,\n budget, and institutional services, and the encrypted local store.\n- `cli-school-bootstrap`, a command-line tool that provisions a school before staff ever open the desktop\n application.\n\nIt is role-aware. The same window shows a different left sidebar depending on whether you sign in as a\nstudent, a teaching assistant, a teacher, an administrator, an IT director, or a charter-management\noperator. The role itself comes from an on-chain query, not from the interface, so hiding a sidebar item is\nnever what keeps a user out of an action.\n\n## How to use it\n\nA school is brought online in a deliberate order. An operator provisions it first, then hands the desktop\napplication to staff.\n\n1. An operator runs `cli-school-bootstrap init` to stand up the school, choosing a charter-management\n organization, a standalone district, or a school under an existing CMO. Version 0.4 supports self-host\n mode only; the `--hosted` path reports that it is not yet available (`cli-school-bootstrap/src/cli.rs`).\n2. The bootstrap can run a Docusign Connect receiver (`daemon`) so consent and agreement envelopes update\n the bootstrap state as signers complete them, which can take days and survives restarts idempotently.\n3. The operator generates per-guardian setup packets from an imported roster and distributes them\n (`generate-guardian-packets`). Guardian PII appears only inside that guardian's own packet; logs use\n pseudonymous identifiers.\n4. Build and run the desktop application:\n\n ```bash\n cargo build --release -p citrate-learning-center\n cargo run --release -p citrate-learning-center\n ```\n\n5. Staff and students sign in. A password gate unlocks the local store, the backend decrypts the data on\n the machine, and the role-appropriate screens appear.\n\n## Reference\n\nThe screens are Slint views under `gui/citrate_learning_center/ui/`; the sidebar groups and labels below\nare quoted from `ui/shell/sidebar.slint` and are gated by the on-chain role.\n\n| Role | Sidebar groups and items |\n|---|---|\n| Student, TA | Home, Assignments, Progress |\n| Teacher | CLASSROOM: Home, Students, Assignments. FINANCE: Budget |\n| Admin, SuperAdmin | INSTITUTION: Overview, Classrooms, Staff. FINANCE: Budget, Approvals |\n| IT | ACCOUNTS: User Accounts, Bulk Import. DEVICES: Fleet. INFRASTRUCTURE: Node Status, Security |\n| No role | Getting Started |\n\nSettings is always present, and the shell adds onboarding and a password-gated lock screen. The\n`CMOSuperAdmin` role exists on chain and drives cross-school administration through the CMO portal service,\nbut this build renders no distinct CMO sidebar group; a CMOSuperAdmin sees the administrator views.\n\nThe backend services that stand behind those screens (`gui/citrate_edu_app/src/services/`):\n\n| Service | What it does |\n|---|---|\n| `roster.rs` | Bulk import from SIS exports (Infinite Campus, PowerSchool), as CSV, TSV, or XLSX. |\n| `classroom.rs` | Classrooms, devices, and assignments. |\n| `budget.rs` | Budget allocation and cashout requests. |\n| `institutional.rs` | Vault status and the cashout approval lifecycle. |\n| `cmo_portal.rs` | Cross-school administration for charter-management operators. |\n\nThe provisioning CLI subcommands (`cli-school-bootstrap/src/cli.rs`):\n\n| Command | What it does |\n|---|---|\n| `init` | Start a bootstrap workflow; `--config` skips prompts for scripted runs. |\n| `status` | Show which steps are complete, in progress, or next. |\n| `resume` | Resume from the last checkpoint, idempotently. |\n| `reset` | Delete the local state directory. Does not void already-sent Docusign envelopes. |\n| `daemon` | Run the Docusign Connect receiver (default `127.0.0.1:8091`, behind a TLS-terminating proxy). |\n| `generate-guardian-packets` | Produce and distribute per-guardian setup packets from a roster. |\n| `revoke-guardian-packet` | Revoke a guardian's packet for a right-to-erasure request. |\n\nGlobal flags include `--state-dir` and `--hosted` (reserved for a later release). Guardian delivery over\nSMTP (`--smtp`) and as PDF (`--print-pdf`) are reserved for v1.1; the filesystem channel is the v1 default.\n\n## Design rationale\n\nA school's most sensitive asset is its students' records, and the regulation around them is unforgiving.\nSo Learning Center keeps that data where it already is, on the school's hardware, and treats the public\nnetwork as a place for roles and proofs, not for names. Identity is pseudonymous on chain: a student\nbecomes a keyed hash of their provider identifier, derived with an institution-held secret, so the chain\ncan route and reward learning without ever holding a name. The role a user holds is read from chain and\nchecked in the backend, which is why the sidebar is a convenience and not a control. The provisioning step\nis a separate CLI rather than a button in the application because standing up a school, with consent\nenvelopes and guardian packets, is operator work that can take days and must be auditable.\n\n## Failure modes\n\nThis application handles K-12 student and guardian data, so its boundaries fail closed.\n\n- Local data is encrypted at rest with AES-256-GCM through `citrate-security`, with the associated data\n bound into the GCM tag, so a swapped ciphertext fails verification rather than decrypting\n (`src/encryption.rs`, `src/local_store.rs`). No plaintext correction lands on disk.\n- Role separation is enforced in the backend (`src/role.rs`, `src/it_elevation.rs`), not by hiding\n sidebar items. A locked account resolves to no role. Privileged actions carry dedicated coverage tests\n (`tests/k1_4_privileged_actions_coverage.rs`).\n- Privileged actions require a fresh password reauthentication within a 60-second window\n (`tests/rem_g_02_fresh_password_gate.rs`). The IT-elevation bridge that lets a small-district\n administrator act as IT is time-bounded, audited on entry and exit, and gated on the same reauth.\n- `reset` is destructive and does not cancel sent Docusign envelopes; those must be voided in the Docusign\n tenant separately.\n\n## Access and canon\n\nTier: academic. This is education and institutional material tied to school pilots, not a public consumer\nsurface.\n\nUS K-12 public schools have free Citrate access in perpetuity, and Learning Center is the classroom that\naccess opens onto. The school runs it on its own hardware as part of Citrate Ground; student and guardian\ndata, rosters, corrections, and identity mappings, stay there, encrypted at rest, and never reach the\npublic Citrate Network. Students appear on chain only as pseudonymous identifiers. The compliance floor for\nschools is FERPA, COPPA, and CIPA; the right-to-erasure path for guardian records is built into the\nprovisioning CLI. No secrets are reproduced here: the institution's org secret is loaded from its\nencrypted keystore in production, and Docusign credentials are operator configuration. See\n[enterprise compliance](/enterprise/compliance) for the FERPA, COPPA, and CIPA model and [Citrate Schools](/contracts/edu)\nfor the program.\n\n## Source and verification\n\n- Source repo: `citrate-learning-center`, audited against SHA `a34f976`.\n- Key paths: `README.md`, `Cargo.toml`, `gui/citrate_learning_center/ui/shell/sidebar.slint`,\n `gui/citrate_edu_app/src/` (`role.rs`, `identity.rs`, `it_elevation.rs`, `encryption.rs`,\n `local_store.rs`, `key_rotation.rs`, `services/`), `cli-school-bootstrap/src/cli.rs`,\n `gui/citrate_learning_center/tests/`.\n- Status: Implemented (pre-audit), pre-1.0 at version 0.4.0. Version 1 is self-host only; the hosted\n parent portal (`--hosted`) and the SMTP and PDF guardian-delivery channels are reserved for later\n releases. The repo carries a planning directory (`cli-edu/`) that has no `Cargo.toml` and is excluded\n from the workspace; build only the three real crates. Tier 1 audit applies:\n no stable release ships without a written external attestation against an exact SHA.\n"},"/apps/memories":{"slug":"/apps/memories","title":"Memrizz (agent-memory DAG and MCP)","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-memories","syncedSha":"a616e75","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"The webapp","anchor":"the-webapp"},{"depth":3,"text":"Connecting an agent over MCP","anchor":"connecting-an-agent-over-mcp"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"Webapp surfaces","anchor":"webapp-surfaces"},{"depth":3,"text":"MCP tools","anchor":"mcp-tools"},{"depth":3,"text":"Gateway and engine","anchor":"gateway-and-engine"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Memrizz is the webapp and MCP server for a federated agent-memory DAG, \"git for agents.\" It gives a\nteam fast, provenance-carrying access to its organizational memory across many repositories, and it\ngives an agent durable, auditable memory through the same store over MCP.\n\n## What it is\n\nMemrizz (repo `citrate-memories`) holds a team's memory as a knowledge DAG and presents it through two\nfaces. The webapp renders that memory as a 2.5D constellation you can fly through, with conversational\nrecall, time-travel, and a review center where a person confirms or rejects what the system proposes.\nThe MCP server exposes the same memory to agents as a set of tools, so a model in Claude Desktop,\nClaude Code, Cursor, or a generic client can recall, search, verify, and contribute to it.\n\nUnderneath, the store is two-plane. A Derived plane rebuilds deterministically from git, markdown, and\nmanifests, so it can always be reconstructed from the repositories themselves. An Asserted plane holds\nsigned human and agent claims, append-only, canonical for its own content. The split keeps the\ndistinction between what was reconstructed and what was asserted, and it is the same kind of memory\nsubstrate the [research pages](/research/learning) describe.\n\nEverything is scoped to an Org and isolated per Org. Every call is authorized by a signed capability\ngrant, and every read, write, and denial is recorded to a tamper-evident hash-chained audit log. The\nmemory in an Org is treated as client property; the platform operator role deliberately has no access\nto memory content.\n\n## How to use it\n\n### The webapp\n\n1. Sign in with Citrate over OIDC with PKCE, then pick your Org.\n2. Explore the constellation, or use Ask to query in natural language and get answers with citations\n that light up the nodes they came from.\n3. Use the node inspector to follow a claim's provenance and verify it, and the review center to\n confirm or reject proposed edges, keeping a person in the loop.\n\n### Connecting an agent over MCP\n\nThe MCP server speaks JSON-RPC over stdio as a headless daemon, or Streamable-HTTP through the gateway\nat `POST /mcp/u/:sub`. From the Connect page you mint a short-lived token and copy a config block into\nClaude Desktop, Claude Code, Cursor, or a generic client. The gateway authorizes every call against\nyour Org membership and your capability grant, and audits it. The agentic side, including the MCP\nbridge and the RPC surface, is covered under [chain RPC](/chain/rpc).\n\n## Reference\n\n### Webapp surfaces\n\nDefined in `PLANSET/06_WEBAPP_FRONTEND_SPEC.md`.\n\n| Surface | What you see |\n|---|---|\n| Constellation | A 2.5D DAG explorer with layout modes, an `as_of` time-scrubber, and blast-radius focus. |\n| Ask | Conversational recall with a model picker; citations light up the nodes they draw from. |\n| Node inspector | Identity, plane and trust badges, a source pointer that links rather than copies, the verify verdict, and neighbors. |\n| Review Center | An HIC (Human In Control) inbox of edge proposals, contradictions, supersessions, and self-critic findings. |\n| Org and Audit | A federation overview and the integrity-verified, hash-chained audit log. |\n| Connect | Mints your personal MCP endpoint and a short-lived token, with copy-paste client config. |\n\n### MCP tools\n\nDefined in `crates/mem-mcp/src/lib.rs`. Read tools return content; write tools record signed\nassertions and are quarantined by default for inferred content.\n\n| Kind | Tools |\n|---|---|\n| Read | `memory.recall`, `memory.search`, `memory.neighbors`, `memory.as_of`, `memory.verify`, `memory.critique`, `memory.analogy` |\n| Write | `memory.assert`, `memory.propose_edge`, `memory.confirm_edge`, `memory.merge_diff` |\n\nEvery response carries provenance, a trust tier, and a freshness watermark.\n\n### Gateway and engine\n\nThe system runs as three parts, described in `PLANSET/07_IMPLEMENTATION_AND_HARDENING_PLAN.md`.\n\n| Part | Role | Source |\n|---|---|---|\n| Webapp | The browser front end and OIDC relying party. | `webapp/` |\n| Gateway | Authentication, Org resolution, authorization, the HTTP and JSON API, and the SSE stream. | `crates/mem-gateway/` (`auth.rs`, `control.rs`, `oidc.rs`, `http.rs`) and the `crates/mem-authz/` policy crate |\n| Engine | The memory store and vector index. | the engine crates |\n\n## Design rationale\n\nTwo planes exist because reconstructed knowledge and asserted knowledge carry different guarantees and\nmust not be confused. The Derived plane can always be rebuilt from the repositories, so it never needs\nto be trusted on faith; the Asserted plane is append-only and signed, so a claim's author and time are\nfixed. Org isolation, signed capability grants, and the audit chain follow from treating memory as\nclient property: the operator who runs the platform should be able to keep it healthy without being\nable to read what it holds. Inferred writes are quarantined by default and surfaced in the review\ncenter, so a person decides what becomes canonical rather than the model deciding silently.\n\n## Failure modes\n\nThis surface holds client memory, so it is built to fail closed.\n\n- Authentication is OIDC with the signing algorithm pinned to RS256 and read from configuration rather\n than the token header, with issuer, audience, and expiry enforced. A token that does not satisfy\n these is rejected, and the gateway refuses to start without its OIDC configuration.\n- Every call is checked against Org membership and a signed, resource-scoped capability grant. A call\n outside the grant is denied, and the denial is recorded.\n- The audit log is a hash chain. A break in the chain is detectable, and the Org view shows whether\n the chain is intact.\n- Memory content is encrypted at rest with XChaCha20-Poly1305, with a per-tenant data key sealed under a\n per-Org keyring.\n- The platform operator role has no access to memory content by design.\n- No secrets appear in this page. None of the OIDC, capability, or encryption keys are reproduced here.\n\n## Access and canon\n\nCommercial. This is paid, contracted, multi-Org product depth, and memory is client property isolated\nper Org. The store reuses pieces of the Citrate Network engine, and its audit roots are designed to\nanchor periodically to the public ledger; the federated-learning surface it sits alongside is\n[Citrate Orchard](/research/learning). On-premise sovereignty and in-house identity verification (VERI) hold\nacross Citrate, as described in [what Citrate is](/start/what-is-citrate).\n\n## Source and verification\n\n- Source repo: `citrate-memories`. The product is named Memrizz; an earlier working codename still\n lingers in some spec and crate comments and is not used in Almanac.\n- Audited against: `a616e75`.\n- Key paths: `crates/mem-mcp/src/lib.rs`, `crates/mem-gateway/` (`auth.rs`, `control.rs`, `oidc.rs`,\n `http.rs`), `crates/mem-authz/`, `crates/mem-store/src/shred.rs`, `PLANSET/00` to `07`, `webapp/`,\n `README.md`.\n- Status by area:\n - Security foundation (milestone M0, landed 2026-06-14): **Implemented (pre-audit).** Org\n control-plane and isolation, OIDC verification, capability-grant authorization, the HTTP read and\n write API, the MCP-over-HTTP bridge, and durable control-plane persistence.\n - See, Ask, and Steward MVP (milestone M1): **Specified**, in progress. The constellation,\n conversational recall, the review center, signed assert and confirm, and the SSE audit tail are\n being built. Later milestones add the admin console, model-bring-your-own revoke, operations, and\n crypto-shred forget.\n - Known limits: encryption is classical today, with a post-quantum hybrid (Kyber-768 and X25519)\n roadmapped; crypto-shred is per-Org rather than per-recipient; v1 is exploratory with a v2\n greenfield rebuild planned.\n - A Tier-1 external audit is required before any non-internal exposure, and none has been completed.\n"},"/apps/native":{"slug":"/apps/native","title":"The Citrate Keyring desktop app","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-native/README.md, Cargo.toml, gui/citrate_native/ui/","syncedSha":"bc0e8ba","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The Citrate Keyring desktop app is a native desktop application that holds a Citrate Keyring account and,\nin the same window, gives you a reader for the BlockDAG. It is for anyone who wants the account and the\nnetwork in a real desktop window rather than a browser tab.\n\n## What it is\n\nThe app is built with Slint, a native Rust user-interface toolkit, so it opens as a desktop window with a\nlocal service layer behind it that talks to the chain. It is a Cargo workspace with three crates\n(`Cargo.toml`): `gui/citrate_ui_kit`, the shared interface kit; `gui/citrate_native`, the desktop\napplication and its screens, which is the default build target; and `gui/citrate_desktop_app`, the backend\nservice layer that holds the chain client, the account, the mempool, and the RPC.\n\nThe window is organized around a left sidebar with grouped navigation (`gui/citrate_native/ui/shell/\nsidebar.slint`). A regular account sees the `BLOCKCHAIN`, `AI`, `DEVELOPER`, `LEARNING`, and `OPERATIONS`\ngroups. An\naccount whose identity is a school operator, a CMOSuperAdmin, additionally sees a CMO group for\nadministering a charter or management organization. The mental model is one encrypted local identity that\nopens onto your account, a reader for the network, and an on-ramp to the compute and learning marketplaces.\n\nThe account is local-first. It is encrypted with a password you choose, and the password never leaves the\ndevice. The DAG view here is a local reader against your own node; the full public explorer is\n[CitrateScan](/apps/explorer), and the account abstraction it shares with the browser extension is covered\nunder [passkeys](/aa/passkeys) and [guardians](/aa/guardians).\n\n## How to use it\n\nYou build the app from source and run it. The short version is below; the full walk-through, including the\nprerequisites, is in [run the Citrate Keyring desktop app](/apps/tutorials/run-the-desktop-wallet).\n\n1. Install a stable Rust toolchain. The repository pins `channel = \"stable\"` with `rustfmt` and `clippy`\n in `rust-toolchain.toml`, so rustup picks it up.\n2. Make sure your personal GitHub SSH key can read the sibling repositories `citrate-chain`,\n `citrate-learning-center`, and `citrate-agent-runtime`. The build pulls chain crates over SSH, and\n organization membership grants the access.\n3. Install a C and C++ toolchain and the system libraries Slint and `rocksdb` need for your platform.\n4. Build and run:\n\n```bash\ncargo build --release\ncargo run --release -p citrate-native\n```\n\n`citrate-native` is the workspace default member, so `cargo run --release` without `-p` launches the same\napplication. For a faster iteration loop, omit `--release`.\n\n5. On first launch the onboarding flow opens: a welcome screen, a password of at least eight characters, a\n provisioning step that generates and shows your recovery phrase, and a confirmation that you backed the\n phrase up. After that the app shell opens to the sidebar. If you already have an account, use the import\n option to restore from a recovery phrase or a key.\n\n## Reference\n\nThe screens below are the Slint views under `gui/citrate_native/ui/`. Sidebar labels are quoted from\n`ui/shell/sidebar.slint`.\n\n| Group | Screen | What it does | Source |\n|---|---|---|---|\n| Onboarding | Onboarding | Welcome, password, provisioning with a recovery phrase, confirmation | `ui/onboarding/onboarding.slint` |\n| Shell | Lock screen | Locks the app behind your password between sessions | `ui/shell/lock_screen.slint` |\n| `BLOCKCHAIN` | Dashboard | The account and network overview | `ui/dashboard/dashboard.slint` |\n| `BLOCKCHAIN` | `Wallet` | Balances, transaction history, import | `ui/wallet/wallet.slint` |\n| `BLOCKCHAIN` | DAG Explorer | A local reader of the BlockDAG with a transaction detail modal | `ui/dag/dag_explorer.slint` |\n| AI | Chat | A chat view from the shared interface kit | `ui/app.slint` |\n| AI | Models | Browse and manage models | `ui/models/models.slint` |\n| Developer | Compute | Opt-in compute sharing, detects your hardware, shows provider status | `ui/compute/compute.slint` |\n| Developer | Files | File storage entries | `ui/storage/storage.slint` |\n| Learning | Learn | Contribution pools, your stake, and earnings in SALT | `ui/learning/learning.slint`, `ui/learning/edu_panel.slint` |\n| Operations | Agent Center | An activity trail and an approvals queue for agent operations | `ui/operations/operations_view.slint` |\n| Settings | Settings | Environment, AI config, system health, peers, node control, knowledge graph, integrations, appearance, and a danger zone | `ui/settings/` |\n| CMO | Dashboard, Tenancy, Compliance | School administration, role-gated to CMOSuperAdmin | `ui/cmo/` |\n\nThe send dialog (`ui/wallet/send_dialog.slint`) takes a to address and an amount in SALT, shows a review\nof recipient, amount, and gas, then sends on Confirm & Send. When your account is linked to a Citrate\nKeyring smart account, a sponsored toggle appears; with it on, the send goes from the smart account and\nthe gas line reads \"Sponsored by Citrate\" instead of a gas figure. This is the EW-S1 sponsored-send work\nand it shows only when a linked smart account is available.\n\n## Design rationale\n\nThe app is native rather than a web page so the account, the node reader, and the marketplaces share one\nlocal process and one encrypted identity, with the data staying on the machine. The DAG view is a local\nreader rather than a second public explorer because the device already has a node to read; when you want\nthe shared, queryable view of the network you go to [CitrateScan](/apps/explorer). The marketplace and\nschool-administration screens are honest about reach: they say plainly when a contract is not reachable\nand show empty states rather than inventing numbers, which is why several CMO aggregates are stubbed\nbehind an environment flag until their wiring lands.\n\n## Failure modes\n\n- The account is encrypted with your password, and the password never leaves the device. There is no\n server-side recovery; the recovery phrase shown at provisioning is the backup. Write it down and store\n it offline.\n- The app locks between sessions. Reopening it lands on the lock screen, and you unlock with your password.\n- The Settings danger zone performs destructive actions. Read the in-app warnings before using it.\n- The build pulls chain crates over SSH using per-host aliases, while a few sibling repositories still use\n plain `github.com`. Cargo can then fetch a chain crate such as `citrate-wallet-core` twice and treat the\n copies as different sources. It compiles today; if you hit a type mismatch at a chain-API boundary, this\n double-fetch is the likely cause. A planned follow-up normalizes the URL convention across these repos.\n- The CMO aggregates are demo-stubbed behind the `CITRATE_CMO_DEMO` environment flag in version 1. Treat\n those numbers as illustrative until the wiring lands.\n\n## Access and canon\n\nPublic. This is an end-user guide to the desktop account and its screen map. No keys, recovery phrases,\nprivate endpoints, or credentials appear here. The account password and the recovery phrase are created\nand held on your device. Building from source uses your own GitHub SSH access to the sibling\nrepositories; the CI deploy keys named in the README are operator infrastructure, not user-facing, and\nare not reproduced here. The CMO screens are role-gated to CMOSuperAdmin identities.\n\n## Source and verification\n\n- Source repo: `citrate-native`, audited against SHA `bc0e8ba`.\n- Files read: `README.md`, `Cargo.toml`, `rust-toolchain.toml`,\n `gui/citrate_native/ui/shell/sidebar.slint`, `ui/app.slint`, `ui/onboarding/onboarding.slint`,\n `ui/wallet/wallet.slint`, `ui/wallet/send_dialog.slint`, `ui/dag/dag_explorer.slint`,\n `ui/compute/compute.slint`, `ui/learning/learning.slint`, `ui/cmo/`, `ui/operations/operations_view.slint`,\n `ui/settings/`, `gui/citrate_native/src/main.rs`, `gui/citrate_native/tests/e2e_wallet.rs`.\n- Status: Implemented, version 0.4.0, pre-1.0. Account creation, import from a recovery phrase or key,\n the lock screen, send with the sponsored toggle, and the DAG reader are Implemented and covered by\n end-to-end tests. Some marketplace and CMO aggregates are Specified, scaffolded or demo-stubbed as noted.\n Not externally certified.\n"},"/apps/studio":{"slug":"/apps/studio","title":"Citrate Studio","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-studio (BUSL-1.1)","syncedSha":"39cadf3","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Citrate Studio is the agent-control interface for node operators, a native application for driving the\nCitrate agent runtime and watching its safety machinery work. This page is the overview; the full source is\npublic in the `citrate-studio` repository under BUSL-1.1.\n\n## What it is\n\nCitrate Studio is the native interface that an operator uses to run a compliance-first agent. It puts the\nruntime's safety machinery, the approvals, the role quorum, the pre-flight checks, the tripwires, and the\naudit replay, in front of the operator as the main thing on screen rather than hidden plumbing. It is built\nin Slint, descends from the Citrate Market design system, and renders the agent runtime's own primitives\ndirectly.\n\nThe runtime it drives is the [agent runtime](/compute/agent-runtime), and the people it is for are the\n[node operators](/operators/run-a-node) who run agents on their own hardware and want the safety controls\nto be visible and usable.\n\n## Access and canon\n\nPublic. This page is the overview; the full implementation is public in the `citrate-studio` repository\nunder BUSL-1.1 (source-available, converting to Apache-2.0 on its Change Date). The design specification,\nthe map of what is built against what is modeled, the policy, signer-roster, approval-queue, and\nCapsule-dispatch implementation, and the packaging and release detail all live in that repository. This page\nsummarizes and links to the source rather than reproducing it, and it contains no secrets.\n\n## Source and verification\n\n- Source repo: `citrate-studio` (public, BUSL-1.1). Overview audited against SHA `39cadf3`.\n- Status: Implemented. The application is a hardened release candidate with real authentication, policy\n core, signer roster, and chain reads; the precise built-versus-modeled map and the remaining 1.0 work are\n tracked in the repository.\n"},"/apps/tutorials/explore-a-transaction":{"slug":"/apps/tutorials/explore-a-transaction","title":"Tutorial: Explore a transaction","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-explorer/src/app/api/tx/[hash]/route.ts, src/app/api/v1/route.ts, src/app/api/mcp/route.ts","syncedSha":"6faab8a","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Step 1, find the transaction in the explorer","anchor":"step-1-find-the-transaction-in-the-explorer"},{"depth":3,"text":"Step 2, read the consensus context","anchor":"step-2-read-the-consensus-context"},{"depth":3,"text":"Step 3, fetch the same facts from the API","anchor":"step-3-fetch-the-same-facts-from-the-api"},{"depth":3,"text":"Step 4, use the Etherscan-shaped API, optional","anchor":"step-4-use-the-etherscan-shaped-api-optional"},{"depth":3,"text":"Step 5, ask the agent, optional","anchor":"step-5-ask-the-agent-optional"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"What you learned","anchor":"what-you-learned"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"A short walk-through of looking up one transaction in CitrateScan, reading what it did, and reading its\nconfirmation depth the way the BlockDAG measures it. You can do this with your eyes in the explorer, from the\nread API, or by asking the built-in agent.\n\n## What it is\n\nA guided lookup of a single transaction on the Citrate Network, chain id 40204. You will find the\ntransaction, read its plain-English summary, and read its depth rather than a confirmation count. The reads here are public and write nothing.\n\n## How to use it\n\nYou need a transaction hash on Citrate, a 64-hex value prefixed with `0x`. The API steps use an API key,\nwhich you can get from the explorer's developer hub. Set two variables so the commands stay short; point\n`EXPLORER` at your CitrateScan deployment.\n\n```bash\nexport EXPLORER=\"https://explorer.citrate.ai\" # your CitrateScan base URL\nexport TXHASH=\"0x\"\n```\n\n### Step 1, find the transaction in the explorer\n\n1. Open CitrateScan.\n2. Paste the transaction hash into the search bar, or press `⌘K` to open the command palette and paste it\n there.\n3. CitrateScan classifies the input as a transaction hash and opens the transaction screen.\n\nYou land on a page that leads with a plain-English summary of what the transaction did, followed by its\nstatus, value in dual units, and decoded detail. (Screen: `citrate-explorer/src/scan/screens/tx.tsx`.)\n\n### Step 2, read the consensus context\n\nOn the transaction page, note the block the transaction landed in and that block's blue score. Citrate is a\nBlockDAG under GhostDAG, so confirmation is measured by depth. CitrateScan sets its depth≥100 flag once:\n\n```text\ncurrent_blue_score − block.blue_score ≥ 100\n```\n\nThe flag is a display heuristic, not protocol finality. Confirmation on the testnet is probabilistic and\ncheckpoint finality is specified, not running. If you settle value against a transaction, pick your own confirmation depth as a risk decision. For the underlying concepts, see [read the DAG](/chain/tutorials/read-the-dag).\n\n### Step 3, fetch the same facts from the API\n\nThe transaction detail endpoint returns the transaction and receipt enriched with the block's `timestamp`,\n`blueScore`, and the depth≥100 flag (JSON field `finalized`), in one call:\n\n```bash\ncurl -s \"$EXPLORER/api/tx/$TXHASH\" | jq\n```\n\nThe response carries the core facts the explorer renders, including `methodId`, `isCreate`, and\n`finalized` (the depth≥100 heuristic, not protocol finality), so you can read block depth from a script. If the block lookup fails, the endpoint still\nreturns the transaction core without the consensus fields rather than erroring.\n(Source: `citrate-explorer/src/app/api/tx/[hash]/route.ts`.)\n\n### Step 4, use the Etherscan-shaped API, optional\n\nIf you already have tooling built for the Etherscan request shape, the same lookups work through `/api/v1`\nwith the `{ status, message, result }` envelope. The `proxy` module is a JSON-RPC passthrough over\nallowlisted read methods:\n\n```bash\n# Raw transaction through the JSON-RPC proxy\ncurl -s \"$EXPLORER/api/v1?module=proxy&action=eth_getTransactionByHash&txhash=$TXHASH&apikey=$CITRATE_API_KEY\" | jq\n\n# Receipt status (1 = success, 0 = reverted)\ncurl -s \"$EXPLORER/api/v1?module=transaction&action=gettxreceiptstatus&txhash=$TXHASH&apikey=$CITRATE_API_KEY\" | jq\n```\n\nThe `getblockcountdown` action returns an error on purpose, since confirmation on Citrate is measured by\ndepth, not a countdown. Read the DAG stats and the depth rule from step 2 instead.\n(Source: `citrate-explorer/src/app/api/v1/route.ts`.)\n\n### Step 5, ask the agent, optional\n\nOpen Ask CitrateScan and ask, in plain English:\n\n> Explain transaction `$TXHASH` and tell me whether it is final.\n\nThe agent answers using read-only on-chain tool calls and links the reads behind its answer. The same tools\nare available to outside agents over the read-only MCP server at `/api/mcp`, so you can do this from Claude,\nChatGPT, or Cursor as well. (Source: `citrate-explorer/src/scan/screens/agent.tsx`, `src/app/api/mcp/route.ts`.)\n\n## Reference\n\nThe surfaces this tutorial touches:\n\n| Surface | What it does | Source |\n|---|---|---|\n| `/api/tx/[hash]` | Transaction and receipt with block timestamp, blue score, and the depth≥100 flag (`finalized`). | `src/app/api/tx/[hash]/route.ts` |\n| `/api/v1` (`proxy`, `transaction`) | Etherscan-shaped reads over allowlisted JSON-RPC and receipt status. | `src/app/api/v1/route.ts` |\n| `/api/mcp` | Read-only MCP server exposing the same tools as the in-app agent. | `src/app/api/mcp/route.ts` |\n\n## What you learned\n\n- How to resolve a transaction in CitrateScan through the search bar or `⌘K`.\n- How to read confirmation depth the DAG-native way, blue score plus the depth≥100 flag, instead of confirmations. Confirmation is probabilistic; checkpoint finality is specified, not running.\n- Three ways to get the same facts: the `/api/tx/[hash]` endpoint, the Etherscan-shaped `/api/v1` surface,\n and the agent, in the explorer or over MCP.\n\n## Failure modes\n\n- An invalid hash, anything other than a `0x`-prefixed 64-hex value, is rejected with a 400 before any\n lookup runs.\n- A transaction the node cannot find returns a 404.\n- `getblockcountdown` on `/api/v1` returns an error by design. Use the depth rule, not a countdown.\n\n## Access and canon\n\nPublic and read-only. Nothing here writes state. API keys are issued to you inside the app and must never\nbe pasted into shared docs.\n\n## Source and verification\n\n- Repo: `citrate-explorer` (CitrateScan), audited against `6faab8a`.\n- Endpoints used: `/api/tx/[hash]`, `/api/v1` (`proxy`, `transaction`), `/api/mcp`.\n- Status: Implemented (pre-audit). These are read-only public surfaces.\n\nSee also [read the DAG](/chain/tutorials/read-the-dag) and the [JSON-RPC reference](/chain/rpc).\n"},"/apps/tutorials/install-the-wallet-extension":{"slug":"/apps/tutorials/install-the-wallet-extension","title":"Install the Citrate Keyring extension","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-wallet-extension/README.md, .github/workflows/release.yml, manifest.json","syncedSha":"930594c","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Step 1, download the release","anchor":"step-1-download-the-release"},{"depth":3,"text":"Step 2, verify the checksum","anchor":"step-2-verify-the-checksum"},{"depth":3,"text":"Step 3, unzip to a stable location","anchor":"step-3-unzip-to-a-stable-location"},{"depth":3,"text":"Step 4, open the extensions page","anchor":"step-4-open-the-extensions-page"},{"depth":3,"text":"Step 5, enable developer mode","anchor":"step-5-enable-developer-mode"},{"depth":3,"text":"Step 6, load the unpacked extension","anchor":"step-6-load-the-unpacked-extension"},{"depth":3,"text":"Step 7, create your first account","anchor":"step-7-create-your-first-account"},{"depth":3,"text":"Step 8, confirm it works","anchor":"step-8-confirm-it-works"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This walks you through loading the Citrate Keyring extension into Chrome, Edge, or Brave as an unpacked\nextension and creating your first account. It targets Citrate Network, chain id `40204`. The software is\nprerelease and pre-audit, so use small testnet values only, and note there is no recovery phrase for the\naccount you create here; if you forget the password, the account is gone.\n\n## What it is\n\nThe Citrate Keyring extension ships as a release archive of the Manifest V3 source plus an Argon2\nWebAssembly module that the project builds per release with `wasm-pack` (`wasm/build.md`). You download the\narchive, verify its checksum, unzip it, and load it from disk with the browser's developer mode. There is\nno JavaScript build step on your side.\n\n## How to use it\n\nYou will need a Chromium-based browser, Chrome, Edge, or Brave, and the two release files from the\nproject's GitHub Releases: the archive `citrate-wallet-extension.zip` and its checksum\n`citrate-wallet-extension.zip.sha256`.\n\n### Step 1, download the release\n\nDownload `citrate-wallet-extension.zip` and `citrate-wallet-extension.zip.sha256` from GitHub Releases into\nthe same directory.\n\n### Step 2, verify the checksum\n\n```bash\nshasum -a 256 -c citrate-wallet-extension.zip.sha256\n```\n\nThe output should end in `: OK`. If it does not match, stop and download again.\n\n### Step 3, unzip to a stable location\n\n```bash\nmkdir -p ~/citrate-wallet-extension\nunzip citrate-wallet-extension.zip -d ~/citrate-wallet-extension\n```\n\nKeep this folder. The browser loads the extension from disk, so do not move or delete it afterward.\n\n### Step 4, open the extensions page\n\nOpen the extensions page for your browser:\n\n- Chrome: `chrome://extensions/`\n- Edge: `edge://extensions/`\n- Brave: `brave://extensions/`\n\n### Step 5, enable developer mode\n\nTurn on the Developer mode toggle, usually at the top right of the page.\n\n### Step 6, load the unpacked extension\n\nClick Load unpacked and select the unzipped folder, the one that contains `manifest.json`. The extension\nappears in the list with its icon.\n\n### Step 7, create your first account\n\n1. Open the extension popup.\n2. Choose `Create New Wallet` and set a strong password.\n3. The extension generates your address and stores it encrypted, AES-256-GCM under an Argon2id key\n derivation. There is no recovery phrase in this version, so the password is the only way back in.\n\n### Step 8, confirm it works\n\nThe popup shows your new address and Citrate Network, chain id 40204. Open a Citrate page, for example\n[Citrate Chat](/apps/chatbot). The page discovers the provider and asks to connect; a per-origin\nconnection window opens. Approve it, then confirm that any transaction or message after that opens its own\napproval window. Nothing is signed without your approval.\n\n## Reference\n\n| Item | Detail | Source |\n|---|---|---|\n| Distribution | Release archive plus checksum, loaded unpacked in developer mode | `.github/workflows/release.yml` |\n| Build step | None on your side; the Argon2 WebAssembly is built per release | `wasm/build.md` |\n| Network | Fixed to Citrate Network, chain id 40204 | `manifest.json`, `js/provider.js` |\n| Account storage | AES-256-GCM under Argon2id, in `chrome.storage.local` | `js/crypto.js`, `js/background.js` |\n\nThe full account model, the provider methods, and the linked smart-account path are documented on\n[the Citrate Keyring extension](/apps/wallet-extension).\n\n## Design rationale\n\nThe extension is loaded unpacked from a checksum-verified archive rather than from a store so the exact\nfiles you run are the ones you verified, which matters for a key-holding surface that is still pre-audit.\nThe checksum step is the point of the tutorial, not a formality: it is what lets you trust the archive\nbefore the browser ever reads it.\n\n## Failure modes\n\n- The extension will not load: make sure you selected the folder that contains `manifest.json`, not the\n archive itself or a parent folder.\n- A page does not see the account: reload the page after loading the extension; the provider injects at\n document start, so a page opened earlier will not have it.\n- The checksum does not match: do not load the folder. Download the release files again.\n\nFor deeper troubleshooting, signing-path behaviour, and recovery, see\n[the Citrate Keyring extension](/apps/wallet-extension), [passkeys](/aa/passkeys), and\n[guardians](/aa/guardians). To drive the same account from code, see the [JavaScript SDK](/sdks/js).\n\n## Access and canon\n\nPublic. Nothing here is a secret. The password and the key you create are held on your device. The hosts\nthe extension reaches are public endpoints in its manifest. The software is prerelease and pre-audit; the\naccount created here has no recovery phrase, so keep the password safe.\n\n## Source and verification\n\n- Source repo: `citrate-wallet-extension`, audited against SHA `930594c`.\n- Files read: `README.md`, `.github/workflows/release.yml`, `manifest.json`, `js/background.js`,\n `js/crypto.js`, `js/provider.js`, `wasm/build.md`.\n- Status: Implemented (pre-audit), version 0.2.0. The download, verify, load-unpacked, and account-creation\n steps reflect the release workflow and the source as built. The account created here has no recovery\n phrase.\n"},"/apps/tutorials/run-the-desktop-wallet":{"slug":"/apps/tutorials/run-the-desktop-wallet","title":"Run the Citrate Keyring desktop app","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-native/README.md, Cargo.toml, rust-toolchain.toml, gui/citrate_native/ui/","syncedSha":"bc0e8ba","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Step 1, install the Rust toolchain","anchor":"step-1-install-the-rust-toolchain"},{"depth":3,"text":"Step 2, set up GitHub SSH access","anchor":"step-2-set-up-github-ssh-access"},{"depth":3,"text":"Step 3, install the platform build tools","anchor":"step-3-install-the-platform-build-tools"},{"depth":3,"text":"Step 4, get the repository","anchor":"step-4-get-the-repository"},{"depth":3,"text":"Step 5, build","anchor":"step-5-build"},{"depth":3,"text":"Step 6, run","anchor":"step-6-run"},{"depth":3,"text":"Step 7, create your account on first launch","anchor":"step-7-create-your-account-on-first-launch"},{"depth":3,"text":"Step 8, look around","anchor":"step-8-look-around"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This builds the Citrate Keyring desktop app from source with Cargo and opens it for the first time. It uses\nonly the build steps that exist in the `citrate-native` repository. The commands take a few minutes plus a\nfirst-time Rust compile, which can take a while.\n\n## What it is\n\nThe desktop app is a Cargo workspace; you build it and run the default member. The full screen map is on\n[the Citrate Keyring desktop app](/apps/native). This tutorial gets you from a clean checkout to an open\nwindow with an account.\n\n## How to use it\n\n### Step 1, install the Rust toolchain\n\nThe repository pins a stable toolchain in `rust-toolchain.toml`, so rustup picks it up automatically.\nInstall rustup if you do not have it:\n\n```bash\ncurl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh\n```\n\n### Step 2, set up GitHub SSH access\n\nThe build pulls chain crates over SSH from sibling repositories. Per the README, make sure your personal\nSSH key is on GitHub and you have read access to `citrate-chain`, `citrate-learning-center`, and\n`citrate-agent-runtime`; organization membership grants it. Check the key works:\n\n```bash\nssh -T git@github.com\n```\n\n### Step 3, install the platform build tools\n\nInstall a C and C++ toolchain and the system libraries Slint and `rocksdb` need: `build-essential`,\n`cmake`, and `clang` on Linux, or the Xcode command-line tools on macOS.\n\n### Step 4, get the repository\n\n```bash\ngit clone git@github.com:CitrateNetwork/citrate-native.git\ncd citrate-native\n```\n\n### Step 5, build\n\nThe default build target is `citrate-native`, the workspace default member in `Cargo.toml`:\n\n```bash\ncargo build --release\n```\n\nThe first build compiles the whole dependency tree, the chain crates, Slint, and `rocksdb`, so it takes\nseveral minutes. For a faster, unoptimized iteration build, drop `--release`. If you hit a type-mismatch\nerror at a chain-API boundary, it is the known double-fetch issue documented in the README, a chain crate\npulled through both an SSH host alias and plain `github.com`. It is tracked as a planned follow-up.\n\n### Step 6, run\n\n```bash\ncargo run --release -p citrate-native\n```\n\n`-p citrate-native` is explicit, but since it is the default member, plain `cargo run --release` launches\nthe same application.\n\n### Step 7, create your account on first launch\n\nThe onboarding flow opens (`gui/citrate_native/ui/onboarding/onboarding.slint`):\n\n1. Welcome, continue past the intro.\n2. Create a password of at least eight characters. This encrypts your account locally and never leaves\n your device.\n3. Provisioning, the app generates and shows your recovery phrase. Write it down and store it offline.\n4. Security confirmation, acknowledge that you backed up the phrase.\n\nIf you already have an account, use the import option to restore from a recovery phrase or a key instead.\n\n### Step 8, look around\n\nYou land in the app shell with the sidebar (`ui/shell/sidebar.slint`). Try `Wallet` for your balance and\ntransactions, and `DAG Explorer` to read the BlockDAG and open a transaction. When you reopen the app it\nwill be locked; unlock it with the password from step 7.\n\n## Reference\n\n| Step | Command or file | Source |\n|---|---|---|\n| Toolchain | `channel = \"stable\"` | `rust-toolchain.toml` |\n| Build | `cargo build --release` | `README.md`, `Cargo.toml` |\n| Run | `cargo run --release -p citrate-native` | `README.md`, `Cargo.toml` default member |\n| Onboarding | Welcome, password, provisioning, confirmation | `ui/onboarding/onboarding.slint` |\n| Shell | Sidebar groups and screens | `ui/shell/sidebar.slint` |\n\nThe DAG view here is a local reader; the shared public explorer is [CitrateScan](/apps/explorer). The\naccount abstraction the app shares with the browser extension is covered under [passkeys](/aa/passkeys)\nand [guardians](/aa/guardians), and you can drive the same account from code with the\n[JavaScript SDK](/sdks/js).\n\n## Design rationale\n\nThe app is built from source rather than shipped as a signed binary at this stage because it is pre-1.0 and\nits chain dependencies move with the network. Building it yourself means the account, the node reader, and\nthe marketplaces all run from the same checked-out tree, with nothing fetched at runtime that you did not\ncompile.\n\n## Failure modes\n\n- A native window does not open, or the build fails at link time: confirm the C and C++ toolchain and the\n Slint and `rocksdb` system libraries from step 3 are installed.\n- A type mismatch at a chain-API boundary: this is the README's double-fetch issue from step 5, a chain\n crate pulled twice through different URL conventions. It compiles today and is tracked as a planned\n follow-up.\n- The app opens locked on a later run: that is expected. Unlock with your password. There is no\n server-side recovery, the phrase from onboarding is the only backup.\n\n## Access and canon\n\nPublic. Nothing here is a secret. Your password and recovery phrase are created on your machine; never\npaste your recovery phrase into a website, a chat, or a file you do not control. Building requires your own\nGitHub SSH access to the sibling repositories; no shared credential is needed or embedded here.\n\n## Source and verification\n\n- Source repo: `citrate-native`, audited against SHA `bc0e8ba`.\n- Files read: `README.md` Quick start (the build and run commands), `Cargo.toml` (default member),\n `rust-toolchain.toml` (toolchain), `gui/citrate_native/ui/onboarding/onboarding.slint` (onboarding steps),\n `gui/citrate_native/ui/shell/sidebar.slint` (the shell).\n- Status: Implemented, version 0.4.0, pre-1.0. The build and run commands and the onboarding steps reflect\n the repository as built. The double-fetch caveat is an open, README-documented issue.\n"},"/apps/wallet-extension":{"slug":"/apps/wallet-extension","title":"The Citrate Keyring extension","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-wallet-extension/manifest.json, js/, popup/","syncedSha":"930594c","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The Citrate Keyring extension is a Manifest V3 browser extension that holds a Citrate Keyring account,\nconnects pages to Citrate Network, and signs every transaction and message behind an explicit approval.\nIt is for anyone who wants to use a Citrate page from Chrome, Edge, or Brave, and for developers wiring a\nstandard provider into a page.\n\n## What it is\n\nThe extension is the account surface you reach from a browser tab. It generates and holds a key locally,\nexposes a standard `window.ethereum` provider so a page can talk to it, and gates every signature behind a\nconfirmation window. The chain is fixed to Citrate Network, chain id `40204`, so there is no network to\nchoose and no chain to switch.\n\nThe account model has two layers. The extension creates a local account, an externally owned account whose\nkey is generated in the background service worker and stored encrypted at rest. Separately, you can link\nthat account to your Citrate Keyring identity, the same identity you can hold with a passkey at\n`auth.citrate.ai`, which gives you a smart account whose operations can be sponsored. The passkey root and\nthe recovery path live with the identity service, covered under [passkeys](/aa/passkeys) and\n[guardians](/aa/guardians); the extension is the device-held signer that links to it.\n\nThree properties hold throughout. Keys are generated and decrypted only inside the background worker, never\nin the page. Every signature and every transaction needs an explicit, per-action approval, there is no\nsilent signing. The set of network destinations the extension can reach is a fixed allow-list, mirrored\ninto the manifest content security policy.\n\n## How to use it\n\n1. Install the extension and create a local account, set out step by step in\n [install the Citrate Keyring extension](/apps/tutorials/install-the-wallet-extension).\n2. Open a Citrate page. It discovers the provider through EIP-6963 and through `window.ethereum`, and calls\n `eth_requestAccounts`. A per-origin connection window opens; approve it once for that site.\n3. When the page asks to send a transaction or sign a message, a confirmation window opens showing the\n from address, the to address, the amount in SALT, the gas, and any calldata. Approve or reject. If you\n do neither, it times out and is rejected.\n4. To use a sponsored smart account, open the popup and run `Link this wallet`. This signs you in to your\n Citrate Keyring identity, predicts your smart account address, and, if the account is not yet deployed,\n installs this device's key as its root signer. After linking, a page can call\n `wallet_sendUserOperation` and the operation is sponsored by the Citrate paymaster.\n\n## Reference\n\nThe surfaces below are read from the extension source at the SHA noted in the last section.\n\n| Surface | What it does | Source |\n|---|---|---|\n| Background service worker | Holds keys, signs, gates approvals, forwards RPC | `js/background.js` |\n| Page provider, MAIN world | Defines `window.ethereum` and `window.citrate`, announces over EIP-6963 | `js/provider.js` |\n| Content relay, ISOLATED world | Validates and relays page requests across the trust boundary | `js/content.js` |\n| Account abstraction helpers | Smart account address prediction, EntryPoint v0.7 UserOperation encoding | `js/aa.js` |\n| Popup | Account view, send and receive, settings, `Link this wallet` | `popup/index.html`, `popup/js/app.js` |\n| Connect window | Per-origin connection approval | `popup/connect.html`, `popup/js/connect.js` |\n| Confirm window | Per-action approval, fails closed on timeout | `popup/confirm-tx.html`, `popup/js/confirm-tx.js` |\n\nManifest V3 permissions, from `manifest.json`:\n\n| Permission | Why it is requested |\n|---|---|\n| `storage` | Store the encrypted account and settings in `chrome.storage.local` |\n| `activeTab` | Interact with the page the user is on |\n| `identity` | Run the Citrate Keyring identity sign-in through `chrome.identity.launchWebAuthFlow` |\n| `alarms` | Drive the service-worker lock and session timers |\n\nContent scripts inject only on `https://*/*`, `http://localhost/*`, and `http://127.0.0.1/*`, so the\nprovider never appears on `chrome://` pages or `file://` URLs. The content security policy `connect-src`\nlimits the worker's network reach to the Citrate RPC, faucet, auth, and bundler hosts plus localhost. The\nworker enforces a stricter allow-list in `RPC_URL_ALLOWLIST` (`js/background.js`), and a CI parity check\nholds that allow-list as a subset of the manifest policy.\n\nProvider methods, handled in `js/background.js`:\n\n| Method | Behaviour |\n|---|---|\n| `eth_chainId`, `net_version` | Return `0x9d0c`, that is 40204 |\n| `eth_requestAccounts`, `eth_accounts` | Return the account after a per-origin approval |\n| `eth_sendTransaction` | Opens a confirmation window, then signs and submits |\n| `personal_sign` | Opens a confirmation window, then signs an EIP-191 message |\n| `wallet_sendUserOperation` | Sends a sponsored operation from the linked smart account, requires linking first |\n| `wallet_switchEthereumChain`, `wallet_addEthereumChain` | Handled, but the chain is fixed to 40204 |\n| `eth_call`, `eth_getBalance`, `eth_blockNumber`, and other reads | Forwarded to the allow-listed node for an approved origin |\n| `eth_sign` | Disabled; it is an arbitrary-data signing primitive |\n| `eth_signTypedData`, `eth_signTypedData_v3`, `eth_signTypedData_v4` | Not yet supported (Specified), use `personal_sign` |\n\nAccount creation derives a 32-byte key, computes the address as the keccak256 of the public key in EIP-55\nchecksum form, and stores it encrypted with AES-256-GCM under an Argon2id key derivation (version 2,\nm=64 MiB, t=3, p=1). The Argon2 step runs in a small WebAssembly module from the `citrate-wallet-sdk`\ncrate; an older PBKDF2 path remains read-only so existing accounts still unlock (`js/crypto.js`,\n`wasm/build.md`). The decrypted key is zeroed immediately after each signature.\n\n## Design rationale\n\nThe extension is built so the page can never reach a key. The provider that a page sees runs in the MAIN\nworld and does no cryptography and no extension messaging; it posts narrow, origin-scoped messages to the\nISOLATED-world relay, which forwards them to the worker that holds the keys. This is why `window.ethereum`\nis visible to a page while the key never is. The same caution explains the fixed network and the fixed\nnetwork allow-list: an account that can only ever talk to Citrate Network and a known set of hosts cannot\nbe quietly pointed at a destination that would see every signed transaction. The cost is that the\nextension is single-purpose, it is not a general account for other networks, and we think that is the\nright trade for the account a person uses on Citrate.\n\n## Failure modes\n\nThis is a key-holding surface, so the relevant failures are the ones where it must fail closed.\n\n- A signature or transaction with no approval is rejected. If the confirmation window times out, the\n request is rejected, not held. A CI tripwire fails the build if a signing path stops gating on the\n approval check.\n- `eth_sign` is refused outright, because it signs attacker-chosen bytes. Legitimate message signing goes\n through `personal_sign`, which is gated by its own confirmation.\n- An unapproved origin cannot forward arbitrary methods to the node. Read methods are proxied only for an\n origin you have approved, which closes a confused-deputy path to a private node.\n- A request to point the account at an RPC outside the allow-list is rejected with the allowed set named.\n- The service worker locks the account on suspend, and the in-memory session ticket is lost when the\n worker is killed, so an idle browser does not leave the account unlocked.\n\nThere is no seed-phrase recovery for the local account. If you forget the password you lose that account.\nThe smart-account path is a different recovery story, handled by your Citrate Keyring identity and its\nguardians, see [guardians](/aa/guardians).\n\n## Access and canon\n\nPublic. This is the account a developer or a person uses to reach Citrate Network from a browser, and it is\nopen by design. No keys, credentials, or private endpoints appear here. The only sensitive values, your\npassword and your key, are created and held on your device and never leave it. The hosts in the manifest\ncontent security policy are public endpoints, not secrets.\n\nThe smart-account path is pre-audit. The repository is classified Tier 1, full external audit before the\nfirst stable release, in `AUDIT_TIER.md`. Use small testnet values while the audit is pending.\n\n## Source and verification\n\n- Source repo: `citrate-wallet-extension`, audited against SHA `930594c`.\n- Files read: `manifest.json`, `js/background.js`, `js/provider.js`, `js/content.js`, `js/crypto.js`,\n `js/aa.js`, `popup/`, `.github/workflows/release.yml`, `.github/workflows/ci.yml`, `wasm/build.md`,\n `AUDIT_TIER.md`, `README.md`.\n- Status: Implemented (pre-audit), version 0.2.0. The local-account creation, the EIP-1193 provider, the\n per-action approval gates, the disabled `eth_sign`, and the RPC allow-list are Implemented. The linked\n smart-account path and `wallet_sendUserOperation` are Implemented but pre-audit, small-value only. EIP-712\n typed-data signing is Specified, not yet supported. The Argon2 WebAssembly binary is built per release\n with `wasm-pack` and is not committed to the repository.\n"},"/chain/bridge":{"slug":"/chain/bridge","title":"Cross-chain bridge","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/core/bridge/ (internals gated)","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This is a brief, sober overview of the Citrate cross-chain bridge. The bridge is pre-alpha and its\nimplementation internals are gated; this page states what it is and where it stands, and nothing more.\n\n## What it is\n\nThe bridge is a cross-chain relay between Ethereum and the Citrate Network. It moves value in by watching\nfor events on Ethereum, on the Sepolia testnet today, and crediting the corresponding amount on Citrate once\nthose events are independently confirmed.\n\nConfirmation does not rest on a single observer. An M-of-N oracle set independently verifies each Ethereum\nevent and signs an attestation over it, using ed25519 signatures with a five-minute freshness window so that\nstale attestations are not honoured. The relay acts only once a quorum of attestations has been collected,\nand it rejects duplicate or inconsistent attestations rather than acting on a contested view. Deposit\npricing uses a bonding curve, with per-transaction limits and a hard cap on total deposits.\n\nThe bridge is pre-alpha. It runs only in a development configuration today, and the audited mainnet bridge\nceremony has not been scheduled. Do not treat it as production-ready.\n\n## Access and canon\n\nThis page is public, and intentionally brief. The bridge's implementation internals, its trust model,\noracle and relay design, signature and freshness handling, and the bonding-curve and limit parameters, are\npublic in the `citrate-chain` repository under `core/bridge/` and its `SECURITY.md`; this page summarizes\nand links to them rather than reproducing them. No keys or endpoints appear on this page.\n\n## Source and verification\n\n- Source: `citrate-chain/core/bridge/`, internals gated; this public page does not transclude or summarise\n them.\n- Audited against SHA: `9d5959e`.\n- Status: Specified, pre-alpha. Runs in a development configuration only; no external audit has been\n completed and the mainnet ceremony is not yet scheduled.\n"},"/chain/cli":{"slug":"/chain/cli","title":"The citrate command-line tool","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/cli/src/{main.rs,config.rs,commands/}","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"account (`cli/src/commands/account.rs`)","anchor":"account-clisrccommandsaccountrs"},{"depth":3,"text":"model (`cli/src/commands/model.rs`)","anchor":"model-clisrccommandsmodelrs"},{"depth":3,"text":"contract (`cli/src/commands/contract.rs`)","anchor":"contract-clisrccommandscontractrs"},{"depth":3,"text":"network (`cli/src/commands/network.rs`)","anchor":"network-clisrccommandsnetworkrs"},{"depth":3,"text":"governance (`cli/src/commands/governance.rs`)","anchor":"governance-clisrccommandsgovernancers"},{"depth":3,"text":"advanced (`cli/src/commands/advanced.rs`)","anchor":"advanced-clisrccommandsadvancedrs"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The `citrate` command-line tool that ships with `citrate-chain`. It manages accounts, deploys and calls\nmodels and contracts, queries the network, and submits governance changes, all against a Citrate Node over\nJSON-RPC. This page is for developers and node operators. If you want to read the chain directly instead,\nsee the [JSON-RPC reference](/chain/rpc).\n\n## What it is\n\nOne binary, `citrate`, with a handful of subcommands. Every subcommand talks to a Citrate Node through the\nsame JSON-RPC interface documented under [chain RPC](/chain/rpc), so the tool is a convenience over the\nnetwork, not a separate authority. Defaults come from `cli/src/config.rs`: the RPC endpoint is\n`http://localhost:8545`, the chain id is 40204, and the keystore lives under `~/.citrate/keystore`. A global\n`--rpc ` flag (or `-r`) overrides the endpoint on any command, and `--config ` points at a\ndifferent config file.\n\nThe subcommands, each verified against `cli/src/main.rs`:\n\n| Subcommand | What it does |\n|---|---|\n| `init` | Writes a starter config file. |\n| `account` | Creates, lists, imports, and exports Citrate Keyring accounts; reads balances. |\n| `model` | Deploys and inspects on-chain models, and runs inference against them. |\n| `contract` | Deploys contracts, calls and reads methods, and submits source for verification. |\n| `network` | Read-only chain and network queries: status, blocks, transactions, gas price, peers. |\n| `governance` | Queues and executes governance parameter changes through the governance precompile. |\n| `advanced` | Network monitoring, benchmarking, stress tests, and DAG and mempool introspection. |\n| `wizard` | Interactive, terminal-guided setup and deployment flows. |\n| `devx` | Developer tools: prints the federation contract table and predicts an embedded Keyring address for a user. |\n\nThe testnet faucet is a separate service, not a subcommand. `citrate-faucet` is an HTTP server, configured\nby environment variables, that drips test SALT to an address; you run it or call its endpoint, you do not\nreach it through `citrate`.\n\n## How to use it\n\nBuild the tool from the workspace root, then confirm it points where you expect:\n\n```bash\ncargo build --release -p citrate-cli # produces the `citrate` binary\ncitrate init # writes a starter config\ncitrate network status # net_version, eth_blockNumber, eth_syncing\n```\n\nA typical first session creates an account, checks its balance, and reads a block:\n\n```bash\ncitrate account create\ncitrate account balance 0x00000000000000000000000000000000000000a1\ncitrate network block latest\n```\n\nTo point at a node other than the local default, pass `--rpc` on any command:\n\n```bash\ncitrate --rpc https://rpc.example.invalid network status\n```\n\nTo deploy a contract end to end, follow\n[deploy a contract with the CLI](/chain/tutorials/deploy-a-contract-with-the-cli), which walks through\n`contract deploy`, `contract call`, and `contract read` against a live node.\n\n## Reference\n\nRepresentative subcommands, verified against the files in `cli/src/commands/`. A flag not listed here does\nnot exist in the binary at this SHA.\n\n### account (`cli/src/commands/account.rs`)\n\n| Subcommand | Purpose |\n|---|---|\n| `account create` | Generates a new account, prompting for a password. |\n| `account list` | Lists keystore files. |\n| `account balance
` | Reads a balance via `eth_getBalance`. |\n| `account import` | Imports a key from `--key-stdin`, `--key-file`, or the loud `--insecure-key-from-arg`. |\n| `account export
` | Exports a key to a `0600` file with `--out`, or to stdout only with `--confirm-stdout`. |\n\n### model (`cli/src/commands/model.rs`)\n\n| Subcommand | Purpose |\n|---|---|\n| `model deploy ` | Registers a model on-chain (`citrate_deployModel`). |\n| `model inference` | Runs inference against a model id (`citrate_runInference`). |\n| `model list` | Enumerates registered models (`citrate_listModels`). |\n| `model info ` | Reads model detail (`citrate_getModel`). |\n\n### contract (`cli/src/commands/contract.rs`)\n\n| Subcommand | Purpose |\n|---|---|\n| `contract deploy ` | Deploys a contract (`eth_sendTransaction`). |\n| `contract call
` | Sends a state-changing call. |\n| `contract read
` | Reads a method with no transaction (`eth_call`). |\n| `contract verify
` | Submits source for runtime-bytecode verification. |\n\nThe built-in ABI encoder supports `address`, `bool`, `bytes32`, and `uint{8..256}` only; dynamic `string`\nand `bytes` are not yet encoded.\n\n### network (`cli/src/commands/network.rs`)\n\n| Subcommand | Underlying call |\n|---|---|\n| `network status` | `net_version`, `eth_blockNumber`, `eth_syncing` |\n| `network block [BLOCK]` | `eth_getBlockByNumber` |\n| `network transaction ` | `eth_getTransactionByHash` and receipt |\n| `network gas-price` | `eth_gasPrice` |\n| `network dag-stats` | `citrate_getDagStats` |\n\n### governance (`cli/src/commands/governance.rs`)\n\nEncodes ABI calls to the governance precompile at `0x0000000000000000000000000000000000001003`:\n`set-admin`, `queue-param`, `execute-param`, and the read-only `get-param`.\n\n### advanced (`cli/src/commands/advanced.rs`)\n\nMonitoring and introspection: `advanced monitor` polls height, peers, and gas price (with `--dag` and\n`--mempool` it adds DAG and mempool detail); `advanced benchmark` and `advanced stress-test` drive load;\n`advanced topology` maps peers; `advanced tx-debug ` traces a transaction; `advanced model-stats`\nreads model usage.\n\n## Failure modes\n\nThe tool fails closed at the points that touch keys and the network:\n\n- Key input never defaults to the argument vector. The old `--key` flag was removed; `account import --key`\n no longer parses. Use `--key-stdin`, `--key-file`, or the explicit `--insecure-key-from-arg`, which warns.\n- `account export` refuses to print a secret to the terminal unless you pass `--out ` or\n `--confirm-stdout`; the file it writes is mode `0600` on Unix.\n- A call against an unreachable node surfaces a connection error rather than a partial result. Confirm the\n endpoint with `citrate network status` or override it with `--rpc`.\n- The underlying RPC errors are standard JSON-RPC: `-32601` method not found, `-32602` invalid params,\n `-32600` invalid request.\n\n## Access and canon\n\nPublic. This is the open command surface a developer needs. The tool generates or prompts for keys; nothing\nsensitive is embedded, and no credentials appear on this page. Deeper interpretation of the `advanced`\nintrospection output is gated to the academic tier, since it exposes consensus and mempool internals; the\nflag list above is public, the internals analysis is not.\n\n## Source and verification\n\nVerified against `citrate-chain` at `9d5959e`. The subcommand set is read from `cli/src/main.rs`\n(`enum Commands`); defaults from `cli/src/config.rs` (RPC `http://localhost:8545`, chain id 40204); each\nsubcommand group from `cli/src/commands/{account,model,contract,network,governance,advanced,wizard}.rs`. The\nfaucet is a separate HTTP service in `faucet/`, not a `citrate` subcommand. Status: Implemented (testnet,\npre-audit). The ABI encoder is intentionally a static-types-only subset.\n"},"/chain/consensus":{"slug":"/chain/consensus","title":"Citrate Consensus, GhostDAG","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/core/consensus/src/types.rs, citrate-chain/core/consensus/src/ghostdag.rs, citrate-chain/core/consensus/src/ecvrf.rs, citrate-chain/core/consensus/src/finality.rs, citrate-chain/core/consensus/src/checkpoint.rs","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Current status","anchor":"current-status"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"GhostDAG engine, `src/ghostdag.rs`","anchor":"ghostdag-engine-srcghostdagrs"},{"depth":3,"text":"Parameters, `src/types.rs`","anchor":"parameters-srctypesrs"},{"depth":3,"text":"Proposer election, `src/ecvrf.rs`, `src/vrf.rs`","anchor":"proposer-election-srcecvrfrs-srcvrfrs"},{"depth":3,"text":"Depth-based finality, `src/finality.rs`","anchor":"depth-based-finality-srcfinalityrs"},{"depth":3,"text":"Committee checkpoint finality, `src/checkpoint.rs` (specified, not running)","anchor":"committee-checkpoint-finality-srccheckpointrs-specified-not-running"},{"depth":3,"text":"Example","anchor":"example"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Consensus is how Citrate Network turns many blocks into one agreed history. The ledger is a BlockDAG, so a block may name several parents, and the GhostDAG protocol reads that graph and produces a single order that every honest node computes the same way. This page is for developers and operators who want the mental model first and the audited surface after.\n\n## What it is\n\nA single-parent chain is a line. Citrate Network is a graph: each block names a selected parent and, optionally, a few merge parents, so the ledger grows like a grafted orchard rather than a single stem. GhostDAG sorts that growth into a total order so the ledger reads as one history.\n\nThe protocol divides every block into two sets. The blue set holds the blocks that agree with the honest majority, judged by a k-cluster rule that tolerates a bounded number of blocks seen in parallel. The red set holds the rest. Walking from genesis to the selected tip and interleaving each block's merge set gives the canonical order. The ordering key is blue score, the cumulative count of a block's blue ancestors. The tip with the highest blue score is the head the network builds on, with deterministic tie-breaking when two tips draw level.\n\nOne property matters above all the rest: blue score is recomputed, never trusted from the block header. A block that arrives claiming an inflated `blue_score` is checked against the feasible range derived from its own ancestry, and a value outside that range is rejected at admission. This is enforced in `ghostdag.rs` and covered by regression tests.\n\nThe default parameters are network constants.\n\n| Parameter | Default | Meaning |\n|---|---|---|\n| `k` | 18 | k-cluster width, how many parallel blocks are tolerated as blue |\n| `max_parents` | 10 | the most parents a block may name |\n| `max_blue_score_diff` | 1000 | the blue-score gap a reorg may span |\n| `pruning_window` | 100000 | how far back the DAG retains full detail |\n| `finality_depth` | 100 | the depth parameter for depth-based finality tracking (see [current status](#current-status)) |\n\nThe testnet node configuration (`node/config/testnet.toml`) sets a one-second target, but the measured block interval on the active testnet is about two seconds; see [chain parameters and genesis](/chain/genesis) for the cadence of each. For where these blocks come from, see [the sequencer](/chain/sequencer); for the broader picture, see [the primer](/start/primer).\n\n## How to use it\n\nYou do not run GhostDAG directly; you read its output. To follow the live order against a running node:\n\n1. Ask the node for its current tips, the heads it is building on.\n2. Read a block and note its blue score; the higher the blue score, the closer to the selected tip.\n3. Walk the selected-parent chain back from the tip to see the order the network agreed on.\n4. Check a block's depth behind the selected tip. The deeper it is, the more work a competing branch would need to displace it. There is no protocol finality point today: see [current status](#current-status).\n\nThe runnable steps, with the exact JSON-RPC calls, are in [read the DAG](/chain/tutorials/read-the-dag). To construct the engine in Rust, see the example at the end of this page.\n\n## Current status\n\nConfirmation on the testnet is probabilistic: a block gains weight as later blocks build on it. Checkpoint finality is specified, not running. The node constructs `CheckpointManager`, but no production code path calls `propose()` yet. `FinalityTracker` and `ChainSelector::with_finality` are exercised by tests only. The RPC does not serve the `finalized` block tag.\n\nThe public testnet runs a single block producer operated by Citrate. Stake-gated proposer eligibility is staged: it is off by default and turns on when a validator registry is configured (`CITRATE_VALIDATOR_REGISTRY`). The one-hundred-member committee with a quorum of sixty-seven, described below, is the target design, not the current state.\n\nIf you credit deposits or settle payments against Citrate testnet blocks, choose your own confirmation depth and treat it as a risk decision, not a protocol guarantee. The machine-readable record of these facts is `verification/claims.json` in citrate-chain (`deterministic_checkpoint_finality`, `consensus_ghostdag`).\n\n## Reference\n\nThe consensus stack lives in `core/consensus/`. The audited surface follows, each item with its code path.\n\n### GhostDAG engine, `src/ghostdag.rs`\n\n- `GhostDag::new(params, dag_store)`, construct the engine over a DAG store.\n- `GhostDag::calculate_blue_set(block)`, compute a block's blue set under the k-cluster rule.\n- `GhostDag::calculate_blue_score(block)`, recompute blue score from the blue set, not from the header.\n- `GhostDag::add_block(block)`, admit a block, update relations and tips.\n- `GhostDag::select_tip()` and `GhostDag::get_tips()`, the current best tip and all tips.\n\nA header claiming a blue score outside its feasible range is rejected here (`GhostDagError`, `src/ghostdag.rs:34`).\n\n### Parameters, `src/types.rs`\n\n`GhostDagParams::default()` sets `k = 18`, `max_parents = 10`, `max_blue_score_diff = 1000`, `pruning_window = 100000`, and `finality_depth = 100` (`src/types.rs:175`).\n\n### Proposer election, `src/ecvrf.rs`, `src/vrf.rs`\n\nProposer eligibility uses an elliptic-curve verifiable random function over NIST P-256, specifically ECVRF-P256-SHA256-TAI per RFC 9381 (`src/ecvrf.rs:3`). Each candidate produces an output bound to their secret key and a public input, so the network can check who was entitled to propose without anyone being able to grind the result.\n\n- `ecvrf::prove(secret, alpha)`, RFC 9381 section 5.1.\n- `ecvrf::verify(...)`, RFC 9381 section 5.3.\n- `VrfProposerSelector` (`src/vrf.rs`) can apply stake-weighted eligibility on top of the VRF output. The node leaves stake gating off by default; without a configured validator registry the fallback selector checks the VRF proof and key binding, not stake.\n\n### Depth-based finality, `src/finality.rs`\n\n`FinalityTracker` marks a block final once it sits under enough confirmations, `confirmation_depth = 100` by default (`FinalityConfig`, `src/finality.rs:42`). `FinalityStatus` is `Finalized`, `PendingFinalization`, or `Unfinalized`. When a `ChainSelector` is built `with_finality`, it refuses a reorganization that would rewrite a block the tracker marked final (`src/chain_selection.rs:25`). The node does not build it that way today, so this protection is specified, not running.\n\n### Committee checkpoint finality, `src/checkpoint.rs` (specified, not running)\n\nThe checkpoint layer is designed to add deterministic finality on top of depth tracking. It is implemented and tested as a library, but no production code path proposes checkpoints yet. `CheckpointManager` coordinates a deterministically selected committee that signs over `(height || block_hash)` with ed25519, and the signatures are aggregated. A checkpoint finalizes once a quorum signs (`CheckpointState::has_quorum`). The chain id is bound into the signed message so a vote on one chain cannot replay onto another (`src/checkpoint.rs:90`).\n\n`CheckpointConfig::default()` sets `interval = 50` blocks, `committee_size = 100`, and `quorum_threshold = 67`, which is two-thirds of one hundred plus one (`src/checkpoint.rs:98`).\n\nIn wall-clock terms under current testnet parameters (measured block time about 2 s), a block is included in about 2 s. Once checkpoints run, one would be due every 50 blocks, about 100 s at the measured block time. Until then there is no finality latency to quote.\n\n### Example\n\nConstruct the engine and read the order in Rust.\n\n```rust\nuse citrate_consensus::*;\nuse std::sync::Arc;\n\nlet dag_store = Arc::new(DagStore::new());\nlet params = GhostDagParams::default(); // k=18, max_parents=10, finality_depth=100\nlet ghostdag = GhostDag::new(params, dag_store.clone());\n\ndag_store.store_block(genesis_block).await?;\nghostdag.add_block(&child_block).await?;\n\n// Deterministic total order from genesis to a tip:\nlet ordering = TotalOrdering::new(dag_store.clone(), Arc::new(ghostdag));\nlet order = ordering.get_total_order(tip_hash).await?;\n\n// Depth-based finality (default depth 100):\nlet tracker = FinalityTracker::with_defaults(dag_store.clone());\nlet finalized = tracker.update_finality(&tip_hash, tip_height).await?;\n```\n\n## Design rationale\n\nA graph orders work better than a line under load. When two proposers produce blocks at nearly the same moment, a single-parent chain has to discard one; a BlockDAG keeps both as parents and lets GhostDAG decide their order later. That is why blocks may name up to ten parents and why the target time can sit near one second without the orphan waste a line would suffer.\n\nTwo choices guard the ledger. Blue score is recomputed rather than trusted, so a block cannot lie its way to the front by claiming a large score. And finality is designed in layers: depth tracking over one hundred blocks for every block, and committee checkpoints designed to add a signed, deterministic guarantee every fifty blocks. The cost is a checkpoint committee that must be selected and must sign; the benefit, once it runs, is that a settled block is settled by both depth and signature. Today checkpoint finality is specified, not running (see [current status](#current-status)).\n\n## Failure modes\n\n- A block arriving with a forged `blue_score` is rejected at admission; ordering ignores the header value and uses the recomputed one.\n- As designed, a reorganization that would rewrite a finalized block is refused by a finality-aware `ChainSelector`, which returns a finality error rather than reverting history. That path is specified, not running, on the testnet.\n- In the checkpoint design, a checkpoint cannot finalize without a quorum of sixty-seven of one hundred committee signatures, so a minority of the committee cannot force a checkpoint. Chain-id binding stops a valid vote from one network being replayed onto another. Neither applies on the live testnet until checkpoints run.\n\n## Access and canon\n\nPublic. The GhostDAG model, the parameters, and the audited surface are protocol a developer needs to reason about ordering and finality, and GhostDAG itself is published research. No keys, validator secrets, or private endpoints appear here. ECVRF `prove` takes a secret key as a parameter; no secret value is documented.\n\n## Source and verification\n\n- Source files: `core/consensus/src/types.rs`, `src/ghostdag.rs`, `src/ecvrf.rs`, `src/vrf.rs`, `src/finality.rs`, `src/chain_selection.rs`, `src/checkpoint.rs`.\n- Audited against SHA `9d5959e`.\n- Status: GhostDAG ordering and VRF checks are implemented; checkpoint finality is specified, not running. Pre external audit. The crate is internally tested and TLA+-checked in several areas; it has not completed a third-party audit, so read \"tested\" as tested, not certified.\n"},"/chain/economics":{"slug":"/chain/economics","title":"Network economics","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/core/economics/","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This is the unit of account on the Citrate Network and the rules that move it. SALT settles the work the\nnetwork performs, fees, block rewards, and staking; it is not a product to hold, and this page does not\ntreat it as one. It is for builders and operators who need to reason about what the network charges, what\nit pays, and how supply behaves over the long view.\n\n## What it is\n\nSALT is the credit the network counts in. When a transaction pays a fee, when a node earns a reward for\nsealing a block, or when stake is placed and returned, the amount is denominated in SALT. The supply is\nfixed at genesis: one trillion SALT, never more. The smallest unit is wei-style, so one SALT divides into\n10^18 base units, the same granularity a developer already expects from an account balance.\n\nThe economics live in one crate, `core/economics/`. It holds the token itself (`token.rs`), the per-block\nreward schedule (`enhanced_rewards.rs`), and the constants that bound the whole system (`lib.rs`). The\nreward schedule is the part worth understanding early, because it is what gives a node a reason to keep the\nnetwork running, and it shrinks on a fixed cadence so that early seasons are more generous than late ones.\n\nA block reward is built from a base reward plus four bonus pools, each expressed as a percentage of that\nbase reward. The four pools recognise four kinds of contribution: validator performance, AI contribution,\nnetwork health, and long-term staking. A node that does more of the work the network values earns a larger\nshare of the pools. The base reward halves every 2,100,000 blocks, about 24 days at the one-second\ntestnet cadence, so issuance tapers toward zero over the network's life rather than running flat forever.\n\n## How to use it\n\nYou rarely set these values yourself; you read them, so you can price work and project earnings.\n\n1. **Price a transaction.** Estimate the fee the way you would on any account-based ledger: gas used times\n the prevailing price. SALT carries 18 decimals, so amounts and balances behave like the smallest units\n you are used to.\n2. **Read the live economic state.** Call `citrate_getEconomicState` for current network metrics and\n `citrate_getToken` for token fundamentals over JSON-RPC. Both are documented in the\n [chain RPC reference](/chain/rpc).\n3. **Reason about rewards.** If you operate a node, the reward you can expect for a sealed block is the base\n reward plus your earned share of the four bonus pools, adjusted for where the chain sits in its halving\n schedule. See [run a node](/operators/run-a-node) for the operator path.\n\n## Reference\n\nToken constants, verified in `core/economics/src/lib.rs` and `core/economics/src/token.rs`:\n\n| Constant | Value | Source |\n|---|---|---|\n| Symbol (`TOKEN_SYMBOL`) | `SALT` | `lib.rs` |\n| Name (`TOKEN_NAME`) | `Citrate` | `lib.rs` |\n| Total supply (`TOTAL_SUPPLY`) | 1,000,000,000,000 (one trillion) | `lib.rs` |\n| Decimals (`DECIMALS`) | 18 | `token.rs` |\n\nTotal supply in base units is `1_000_000_000_000 × 10^18`. The token tracks balances, total minted, and total\nburned; circulating supply is minted minus burned, and minting is capped at the one trillion ceiling.\n\nBlock reward schedule, verified in `core/economics/src/enhanced_rewards.rs`:\n\n| Element | Value | Source |\n|---|---|---|\n| Base reward | a configurable base reward | `enhanced_rewards.rs` (`base_block_reward`) |\n| Validator performance pool | percentage of the base reward | `enhanced_rewards.rs` (`performance_bonus_pool`) |\n| AI contribution pool | percentage of the base reward | `enhanced_rewards.rs` (`ai_contribution_pool`) |\n| Network health pool | percentage of the base reward | `enhanced_rewards.rs` (`network_health_pool`) |\n| Long-term staking pool | percentage of the base reward | `enhanced_rewards.rs` (`staking_bonus_pool`) |\n| Halving interval | 2,100,000 blocks (~24 days at the 1 s testnet cadence) | `enhanced_rewards.rs` (`halving_interval`) |\n\nThe base reward and the four pool percentages are defaults in the source; we describe the base as a\nconfigurable base reward rather than asserting a fixed number, since governance can move it. What does not\nmove is the halving cadence and the fixed supply.\n\n```rust\nuse citrate_economics::*;\n\n// Token fundamentals are constants:\nassert_eq!(TOKEN_SYMBOL, \"SALT\");\nassert_eq!(TOTAL_SUPPLY, 1_000_000_000_000); // 18 decimals; base units = value × 10^18\n```\n\n## Design rationale\n\nA fixed supply with a halving base reward keeps the accounting honest: the network can settle the work it\nperforms without an open-ended issuance that quietly dilutes everyone who came before. Splitting the reward\ninto four pools, rather than paying a flat amount per block, lets the network pay for the behaviours it\nactually depends on, uptime, useful compute, a healthy peer set, and committed stake, instead of paying\nthe same for a block whether or not the node contributed anything beyond sealing it. The trade is more\nmoving parts to reason about; the benefit is that incentives point at the work rather than at the clock.\n\n## Failure modes\n\nThe supply cap is enforced at mint: an attempt to mint past one trillion SALT is rejected, so no path\nthrough the reward schedule can inflate beyond the ceiling. Burned credits are tracked separately, so\ncirculating supply stays an honest minted-minus-burned figure rather than drifting. Because the base reward\nand pool percentages are governance-configurable, the load-bearing invariant is the supply cap and the\nhalving cadence, not any single reward number; treat a published base-reward figure as a default, not a\nguarantee.\n\n## Access and canon\n\nPublic. SALT settles the work the network performs; it is not an investment instrument, and Almanac does not\ndescribe it as one. The token fundamentals, the reward structure, and the halving cadence are exactly what a\nbuilder or operator needs to reason about the economy. No keys, balances, or private allocations appear\nhere.\n\n## Source and verification\n\n- Source: `citrate-chain/core/economics/`, constants in `src/lib.rs` and `src/token.rs`, reward schedule in\n `src/enhanced_rewards.rs`.\n- Live state over JSON-RPC: `citrate_getEconomicState` and `citrate_getToken`\n (`core/api/src/economics_rpc.rs`); see [chain RPC](/chain/rpc).\n- Audited against SHA: `9d5959e`.\n- Status: Implemented (testnet), internally tested, pre external audit. The base reward and pool\n percentages are configurable defaults in source, not certified values.\n"},"/chain/genesis":{"slug":"/chain/genesis","title":"Chain parameters and genesis","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/node/config/, citrate-chain/node/src/genesis.rs","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"These are the chain identity and genesis parameters you need to point an account or a node at the Citrate\nNetwork and to reason about how the network agrees on its first block. It is for builders connecting to the\nnetwork and operators bringing up a node.\n\n## What it is\n\nThe Citrate Network ships several configurations that share one chain id and one set of consensus constants\nbut differ in block cadence and committee sizing. The active public network is the testnet, on chain id\n40204. A mainnet configuration exists in the tree on chain id 1, but it is pre-launch and not yet live; its\nbootstrap-node list is still a placeholder to be filled before launch. Use 40204 today.\n\nThe chain id is permanent. It is the number an account and a node check to confirm they are talking to\nCitrate and not some other network, and we do not plan to change it. Over JSON-RPC, `eth_chainId` returns\n`0x9d0c` on testnet, which is 40204 in hex.\n\nGenesis is deterministic. The same configuration produces the same state root and the same first-block hash\non every node, which is what lets independently started nodes agree on block 0 without coordinating. That\nproperty is built from a single shared path (`core/economics/src/genesis.rs`, called by\n`node/src/genesis.rs`) rather than reconstructed separately in each binary.\n\n## How to use it\n\n1. **Connect an account.** Point it at the testnet using chain id 40204 and the currency SALT at 18\n decimals. Fetch the current public endpoint from the testnet docs rather than copying an address out of a\n config file, since operational endpoints change.\n2. **Confirm you reached Citrate.** Ask the node for its chain id; a correct testnet node answers `0x9d0c`.\n\n ```bash\n curl -s http://127.0.0.1:8545 -H 'content-type: application/json' \\\n -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_chainId\",\"params\":[]}'\n # {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x9d0c\"} # 0x9d0c is 40204\n ```\n\n3. **Bring up a node on a network.** Select the matching configuration. The testnet config carries chain id\n 40204, one-second blocks, and strict VRF.\n\n ```bash\n citrate-node --config node/config/testnet.toml\n ```\n\n## Reference\n\nChain id by configuration, verified in `node/config/*.toml`:\n\n| Network | Chain id | Status |\n|---|---|---|\n| Testnet (`node/config/testnet.toml`) | 40204 | active public network |\n| Team testnet (`node/config/team-testnet.toml`) | 40204 | internal |\n| Devnet (`node/config/devnet.toml`) | 40204 | local development |\n| Mainnet (`node/config/mainnet.toml`) | 1 | pre-launch, not yet live |\n\nBlock cadence by configuration, verified in the same files. Consensus constants such as GhostDAG `k = 18`\nare shared across networks; see [Citrate Network consensus](/chain/consensus).\n\n| Network | `block_time` |\n|---|---|\n| Testnet | 1 s configured (about 2 s measured) |\n| Team testnet | 2 s |\n| Devnet | 2 s |\n| Mainnet (pre-launch) | 5 s |\n\nGenesis block, built deterministically from the shared genesis path\n(`core/economics/src/genesis.rs`):\n\n| Field | Value | Source |\n|---|---|---|\n| Canonical timestamp | 2026-01-01T00:00:00Z | `node/src/genesis.rs` (`CANONICAL_GENESIS_TIMESTAMP`) |\n| Base fee per gas | 1 Gwei (1e9 wei) | `core/economics/src/genesis.rs` (`base_fee_per_gas`) |\n| Gas limit | 30,000,000 | `core/economics/src/genesis.rs` (`gas_limit`) |\n\nThe genesis allocation categories sum to the one trillion SALT supply cap; the allocation structure is\ncovered under [network economics](/chain/economics). No private keys or mnemonics appear in any\nconfiguration in the repository, and we do not enumerate specific genesis account addresses in public docs.\n\n## Design rationale\n\nA permanent chain id and a single shared genesis path exist for the same reason: agreement should not depend\non operators coordinating by hand. If every node derives block 0 from the same code and the same config, two\nnodes started a continent apart still land on the same first-block hash, and a misconfigured node fails\nvisibly rather than quietly forking. The cost is that genesis is rigid; changing it is a deliberate,\nnetwork-wide event, not a per-node setting. For a network meant to outlast any single operator, that\nrigidity is the point.\n\n## Failure modes\n\nThe deterministic-genesis property is the load-bearing invariant: a node that computes a different state\nroot from a different config will not agree on block 0, which surfaces the misconfiguration immediately\nrather than letting it linger as a silent fork. The mainnet configuration is pre-launch; its bootstrap list\nis an empty placeholder, so a node pointed at mainnet today has nothing to connect to. Stay on 40204 until\nmainnet is announced on the [roadmap](/start/roadmap).\n\n## Access and canon\n\nPublic. Chain id, block cadence, consensus constants, and genesis block parameters are exactly what a\nbuilder or operator needs to connect and reason about the network. No genesis private keys or mnemonics\nexist in the repository; the code carries only public addresses, and we do not enumerate genesis account\naddresses or paste live bootstrap addresses here, fetch operational endpoints from the testnet docs.\n\n## Source and verification\n\n- Source: `citrate-chain/node/config/*.toml`, `citrate-chain/node/src/genesis.rs`,\n `citrate-chain/core/economics/src/genesis.rs`.\n- Chain id 40204 confirmed in `node/config/testnet.toml`; mainnet chain id 1 in `node/config/mainnet.toml`;\n `eth_chainId` returns `0x9d0c` (`core/api/src/eth_rpc.rs`, `cli/src/commands/advanced.rs`).\n- Audited against SHA: `9d5959e`.\n- Status: Implemented, testnet 40204 is the active public network; mainnet is Specified but pre-launch (the\n config exists, the network is not yet live). The path to mainnet is on the [roadmap](/start/roadmap).\n"},"/chain/lvm":{"slug":"/chain/lvm","title":"LVM, EVM execution and the parallel executor","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/core/execution/src/revm_adapter.rs, citrate-chain/core/execution/src/parallel/, citrate-chain/core/execution/src/mvcc/","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"The MVCC primitives","anchor":"the-mvcc-primitives"},{"depth":3,"text":"Scheduling","anchor":"scheduling"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The LVM is the execution layer of the Citrate Network: a full EVM that runs ordinary Solidity, with an\noptimistic parallel path underneath so that transactions which do not touch the same accounts run at the\nsame time. This page is for contract authors and node operators.\n\n## What it is\n\nThe LVM executes standard EVM bytecode. It does not fork the opcode set, so contracts compiled by Solidity\nor Vyper for Ethereum run unmodified, and the tooling you already use works against it. Underneath, the\nchain embeds REVM behind a thin adapter, `StateDBAdapter`, that bridges REVM's database trait to Citrate's\nown state store. From the outside it behaves like any EVM node; the difference is in how it schedules work.\n\nOn top of byte-for-byte EVM execution, the LVM adds an optimistic parallel path built on multi-version\nconcurrency control, MVCC. Within a block, transactions that do not conflict execute concurrently. Each one\nrecords the accounts it read, and at commit the executor checks whether any of those accounts changed since\nit started. If none did, the writes apply and the transaction commits; if one did, that transaction is the\nloser, and it retries against fresh state. The result is the same state root you would get from running the\nblock one transaction at a time, so single-threaded equivalence is preserved, the work just finishes\nsooner when the block allows it. Think of it as harvesting several rows of an orchard at once, then setting\naside and re-picking only the trees two crews reached for together.\n\nThe consensus that orders blocks before the LVM runs them is GhostDAG, covered under\n[Citrate Network](/chain/consensus).\n\n## How to use it\n\nThere is nothing Citrate-specific to do. Deploy and call contracts the way you would on Ethereum.\n\n1. Point your tooling, Foundry, ethers, or viem, at the Citrate RPC. See [chain RPC](/chain/rpc) for\n connection details.\n2. Set the chain id to 40204. The LVM writes this into the REVM configuration per executor, and\n transactions signed for another chain id are rejected.\n3. Deploy your contract and call it. Events, logs, and `eth_getLogs` behave as on Ethereum, so subgraphs\n and event assertions work without change.\n4. Read state with `eth_call`. State is correct after a node restart, because a cold in-memory cache falls\n through to the persistent store rather than returning empty data.\n\nThe parallel path is internal to the executor. You do not opt in, and you cannot observe it from a\ntransaction; it only affects how quickly a block is processed.\n\n## Reference\n\nThe audited surface, each item with its code path under `citrate-chain`.\n\n| Item | Behavior | Code path |\n|---|---|---|\n| Hardfork spec | `SpecId::CANCUN`, set with `.with_spec_id(SpecId::CANCUN)`. CANCUN enables MCOPY (EIP-5656), which Solidity 0.8.25+ emits for dynamic-bytes return encoding (the `BFR-VM-1` note). | `core/execution/src/revm_adapter.rs` |\n| Chain id | Supplied per executor with `Executor::with_chain_id(..)` and written into the REVM config. The Citrate Network uses 40204. | `core/execution/src/revm_adapter.rs` |\n| Precompiles | The nine standard Ethereum precompiles, ECRECOVER through BLAKE2F at `0x01` to `0x09`, plus Citrate extensions. | see [Precompiles](/chain/precompiles), [verification, inference, and attestation precompiles](/chain/precompiles-zkp) |\n| Logs and events | REVM logs are converted one to one into Citrate receipt logs by `convert_revm_log` (fix PIL-48), so `eth_getLogs` returns real event topics. | `core/execution/src/revm_adapter.rs` |\n| State source | `StateDBAdapter` is backed by an in-memory cache and a persistent store; cache misses fall through to the store (fix PIL-13b). | `core/execution/src/revm_adapter.rs` |\n| BLOCKHASH | Returns the hash for the 256 most recent blocks; older or unknown blocks return zero, per EVM spec. | `core/execution/src/revm_adapter.rs` |\n\n### The MVCC primitives\n\nThe parallel path lives in `core/execution/src/mvcc/` and `core/execution/src/parallel/`. It implements the\nBlock-STM, snapshot-versioning model proven by the TLA+ spec `specs/tla/consensus/ExecutorMVCC.tla`; the\nspec-to-code mapping is tabulated in `core/execution/src/mvcc/mod.rs`. Each concurrent worker holds three\nthings:\n\n- a pinned read version, the state version at the moment it started (`mvcc/read_set.rs`, `ReadVersion`);\n- a read set, the accounts it observed during execution (`mvcc/read_set.rs`, `ReadSet`);\n- a scratch journal, the writes it is holding for the transaction (`mvcc/scratch_journal.rs`,\n `ScratchJournal`).\n\nAt commit, the `CommitCoordinator` (`mvcc/commit.rs`) checks the read set against the current state: if no\naccount in the read set was written since the worker pinned, it applies the journal and advances the\nversion; otherwise the worker aborts and retries against a fresh pin. The `RetryHarness` (`mvcc/retry.rs`)\nbounds this at `DEFAULT_MAX_RETRIES`, which is 8, then falls back to serial execution so a contended\ntransaction always makes progress.\n\n### Scheduling\n\n`ParallelExecutor` (`parallel/executor.rs`) groups transactions into non-conflicting batches using a\n`ConflictScheduler` and a `DefaultAccessSetExtractor`, then runs the groups concurrently as Tokio tasks.\nConflicts are detected by `AccessSet::conflicts_with` (`parallel/conflict.rs`), which flags write-write,\nread-write, and write-read overlaps. The default extractor marks the sender as a writer, for nonce and\nbalance, and derives recipient access from the transaction type.\n\nThe executor microbenchmark (`benches/tps_parallel.rs`) records about a 2.41 times parallel speedup at 8\nworkers over a single worker on a disjoint-senders run. That is a property of the in-memory executor alone,\nnot network throughput, which is bound by RPC, signature checking, and the block gas limit and sits well\nbelow it. On the current testnet, sustained network throughput for simple transfers is about **750 TPS** - an arithmetic, gas-limit bound: the 30M block gas limit divided by 21,000 gas per transfer over ~2s blocks,\nwith no 10,000 headroom today. Sustained **5,000–10,000 TPS is a mainnet target** pursued through proposed\nprotocol upgrades (a higher block gas limit and faster blocks); it has not been reproduced on-chain. Treat\nthe executor microbench as informational, not a network guarantee.\n\n## Design rationale\n\nMost chains execute a block strictly in order, one transaction after another, because that is the simplest\nway to get a single agreed state. The cost is that a block of independent transactions, say a thousand\ntransfers between a thousand distinct pairs of accounts, runs no faster than a block where every\ntransaction touches the same contract. The LVM takes the optimistic bet that most transactions in a block\nare independent, runs them together, and pays the retry cost only for the few that actually collide. The\nbet is safe because the read-set check at commit is exact: if two transactions touched the same account,\nthe loser re-runs, so the committed result is identical to serial execution. The trade is added\ncomplexity in the executor, which is why the commit protocol is pinned to a TLA+ model rather than left to\nreview alone.\n\n## Failure modes\n\n- A transaction that loses the read-set check retries against fresh state. After `DEFAULT_MAX_RETRIES`\n attempts it falls back to serial execution, so a heavily contended account never stalls; it just stops\n benefiting from parallelism.\n- The committed state root is identical to serial execution by construction. If parallel and serial\n execution ever diverged, that would be a consensus fault, which is exactly the property the\n `ExecutorMVCC.tla` spec is written to exclude.\n- On a cold cache after restart, reads fall through to the persistent store rather than returning zero\n (PIL-13b), so `eth_call` does not silently return wrong data.\n\n## Access and canon\n\nPublic. EVM compatibility, the hardfork spec, the chain id, and the parallel-execution model are all things\na developer or operator needs to build and run on the Citrate Network, and none of it is sensitive. The\nMVCC section is research-provenance material; the prose here links to the TLA+ model rather than\nreproducing it. No keys, endpoints, or credentials appear on this page.\n\n## Source and verification\n\n- Source repo: `citrate-chain`\n- Source files: `core/execution/src/revm_adapter.rs`, `core/execution/src/parallel/{executor.rs,conflict.rs}`,\n `core/execution/src/mvcc/{commit.rs,read_set.rs,scratch_journal.rs,retry.rs,mod.rs}`\n- TLA+ spec: `specs/tla/consensus/ExecutorMVCC.tla`\n- Audited against SHA: `9d5959e`\n- Status: Implemented (pre-audit). The EVM path runs on testnet 40204; the MVCC commit protocol is\n Verified against its TLA+ model.\n"},"/chain/network":{"slug":"/chain/network","title":"Peer-to-peer networking","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/core/network/","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This is how Citrate nodes find each other, talk securely, and spread blocks and transactions across the\nnetwork. It is for operators bringing up a node and anyone who needs to understand how a new node reaches\nthe network head.\n\n## What it is\n\nThe networking layer is a peer-to-peer mesh: there is no central server that nodes phone home to, only other\nnodes. A node joins by reaching out to a small set of configured bootstrap nodes, learning about more peers\nfrom them, and then maintaining its own set of connections from there. Every connection is encrypted and\nauthenticated using the Noise protocol over TCP, so a peer both proves who it is and keeps the conversation\nprivate.\n\nThree flows do the real work, and they layer cleanly on top of one another:\n\n- **Discovery and bootstrap.** A fresh node starts from its configured bootstrap nodes\n (`bootnode.rs`), connects to them, and uses peer discovery (`discovery.rs`) to widen its set of known and\n connected peers. Bootstrap nodes are an entry point, not a dependency; once a node has peers, it keeps\n going without them.\n- **Gossip propagation.** New blocks and transactions spread by gossip (`gossip.rs`,\n `block_propagation.rs`): each node forwards what it has not seen before to its peers, and a seen-message\n cache stops the same item from circulating endlessly. A block reaches the whole network in a handful of\n hops without any node needing a global view.\n- **Reaching peers behind NAT.** Many nodes sit behind home or institutional routers, so the layer includes\n NAT traversal (`nat.rs`) to keep those peers reachable rather than stranded.\n\nThe layer also carries message types for the network's AI and learning traffic (`ai_handler.rs`,\n`learning_messages.rs`), so model and training-related messages travel the same authenticated mesh as\nordinary blocks and transactions.\n\n## How to use it\n\nYou configure the networking layer, you do not call it directly; the node drives it for you.\n\n1. **Supply bootstrap nodes.** Point your node at a known set of bootstrap nodes for the network you are\n joining. The node connects to them first, then discovers the rest of the mesh on its own.\n2. **Let the node sync.** Once connected, the node downloads from the network head and applies blocks until\n it is caught up. From then on it stays current through gossip.\n3. **Check peer health.** Confirm your node has peers and is keeping up using the node's status over\n JSON-RPC. See [run a node](/operators/run-a-node) for the operator walkthrough and the\n [consensus reference](/chain/consensus) for how the blocks it receives are ordered.\n\n## Reference\n\nThe components of `core/network/`, each citing its file:\n\n| Component | File | What it does |\n|---|---|---|\n| Encrypted transport | `noise.rs` | Noise handshake and encrypted, authenticated transport over TCP |\n| Peer discovery | `discovery.rs` | finds peers and tracks connected ones |\n| Bootstrap nodes | `bootnode.rs` | the configured entry points a new node starts from |\n| Block gossip | `gossip.rs`, `block_propagation.rs` | spreads blocks with seen-message de-duplication |\n| Transaction gossip | `transaction_gossip.rs` | relays transactions with a seen-transaction cache |\n| Chain sync | `sync.rs` | brings a node up to the network head |\n| NAT traversal | `nat.rs` | keeps peers behind routers reachable |\n| AI and learning messages | `ai_handler.rs`, `learning_messages.rs` | message types for model and training traffic |\n\nWe do not list live bootstrap addresses here. They are deployment configuration, not documentation, and a\nnode operator supplies them for the network being joined; fetch current values from the testnet operator\ndocs.\n\n## Design rationale\n\nA gossip mesh seeded by a few bootstrap nodes is the design that keeps the network from depending on any one\nmachine. There is no coordinator to take down, no single node whose failure stops propagation, and a new\noperator needs only a couple of known entry points to join. Encrypting every link with Noise means a peer is\nauthenticated before it can influence a node's view, which matters on a network where participation is\nidentity-checked rather than anonymous. The trade is that propagation is probabilistic rather than directed;\ngossip accepts a little redundant traffic in exchange for not needing a global map of the network.\n\n## Failure modes\n\nBootstrap nodes are an entry point, not a single point of failure: once a node has discovered peers it no\nlonger needs them, so a bootstrap node going offline does not cut a synced node off. The seen-message caches\nin gossip stop a block or transaction from looping forever, which bounds the traffic a single item can\ngenerate. Because every connection is authenticated through the Noise handshake, an unauthenticated peer\ncannot inject blocks or transactions into a node's view. NAT and sync surfaces are internally tested but not\nyet externally audited; treat them as production-track, pre-certification.\n\n## Access and canon\n\nPublic. Transport, discovery, gossip, and sync are what a node operator needs to join the network and stay\ncurrent. No bootstrap addresses, node keys, or relay credentials appear here; each node generates its own\nNoise identity, and nothing is hardcoded in these docs.\n\n## Source and verification\n\n- Source: `citrate-chain/core/network/` (`noise.rs`, `discovery.rs`, `bootnode.rs`, `gossip.rs`,\n `block_propagation.rs`, `sync.rs`, `nat.rs`, `ai_handler.rs`, `learning_messages.rs`).\n- Operator path: [run a node](/operators/run-a-node); block ordering: [consensus](/chain/consensus).\n- Audited against SHA: `9d5959e`.\n- Status: Implemented (testnet), internally tested, pre external audit.\n"},"/chain/precompiles-zkp":{"slug":"/chain/precompiles-zkp","title":"Verification, inference, and attestation precompiles (summary)","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/core/execution/src/precompiles/","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":3,"text":"Verification, `0x0107` to `0x0109`","anchor":"verification-0x0107-to-0x0109"},{"depth":3,"text":"Hosted inference, `0x0100` to `0x0106`","anchor":"hosted-inference-0x0100-to-0x0106"},{"depth":3,"text":"Deterministic Q16.16 compute, `0x010A` to `0x010F`","anchor":"deterministic-q1616-compute-0x010a-to-0x010f"},{"depth":3,"text":"Attestation gate","anchor":"attestation-gate"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This is a summary of three precompile families on the Citrate Network. It tells you what they do and where\ntheir addresses sit. Their proving internals and circuit design are public in the `citrate-chain`\nrepository and linked below rather than reproduced here. It is for anyone deciding how to build against\nverifiable AI work on the chain.\n\n## What it is\n\nThe Citrate Network ships one precompile family at `0x0100` to `0x010F`, in three groups that let an\non-chain contract trust off-chain AI work without re-running it, plus an attestation gate over\nnon-deterministic inference. The interfaces and addresses are public, and the full implementation is public\nin the `citrate-chain` repository (Apache-2.0); this page summarizes and links to it.\n\n| Group | Addresses | What it does |\n|---|---|---|\n| Hosted inference | `0x0100` to `0x0106` | Model deployment and registration, single and batch inference, metadata, benchmarking, and model encryption. Model-runtime-backed: a hosted-inference call returns a **signed receipt** over the result, gated by hardware attestation - **not** a pure deterministic on-chain proof of correctness. |\n| Proof verification (incl. ZK-verified inference) | `0x0107` to `0x0109` | Commit to tensors and verify claims and proofs with hashes, Merkle paths, and a Halo2-KZG pairing verifier. Deterministic; the result is verifiable on-chain. |\n| Deterministic Q16.16 compute | `0x010A` to `0x010F` | Fixed-point tensor primitives (matmul, dot, softmax, relu, linear, transpose); bit-identical across nodes. |\n| Attestation gate | consulted by `0x0101` and `0x0102` | Decides whether a non-deterministic inference path may run, based on hardware attestation. |\n\nThe cryptography underneath rests on three publicly nameable building blocks: Q16.16 fixed-point math for\ndeterminism, Halo2-KZG proof verification, and TEE attestation. Poseidon over BN254\n(`zkp/poseidon_bn254.rs`) is the commitment hash. The proving-system internals and circuit design are public\nin `citrate-chain` and linked below.\n\n### Verification, `0x0107` to `0x0109`\n\nThese three precompiles let a contract check off-chain AI work without redoing it.\n\n- `0x0107 TENSOR_COMMIT` produces a Poseidon commitment over a canonical-format tensor and returns a 32-byte\n field element.\n- `0x0108 INFERENCE_PROOF_VERIFY` verifies an inference proof with a Halo2-KZG verifier and returns a\n 32-byte boolean.\n- `0x0109 MERKLE_VERIFY_TENSOR` verifies that a tensor element is part of a committed tensor via a Merkle\n path and returns a 32-byte boolean.\n\nAll three are deterministic by construction, hash plus pairing plus integer math, and their byte-level\noutput is frozen: drift would fork the chain and invalidate prior commitments. The tensor wire format these\nconsume is the public version 1 format documented in [Precompiles](/chain/precompiles).\n\n### Hosted inference, `0x0100` to `0x0106`\n\nThis group is the hosted AI inference runtime: model deployment and registration, single and batch\ninference, metadata query, benchmarking, and model-encryption operations. It is model-runtime-backed, so an\ninference call returns a **signed receipt** over the result - an attestable statement about what ran, not a\ncryptographic proof that the output is correct. Whether a non-deterministic floating-point inference path is\npermitted at all is decided by the attestation gate below. When no runtime is hosted, these precompiles\nsurface a discoverable error rather than silently returning fake data.\n\nFor a result that is *verifiable on-chain* rather than merely signed, use the proof-verification group\n(`0x0107`–`0x0109`) - a ZK-verified inference proof checked by the Halo2-KZG verifier - or keep the\ncomputation inside the deterministic Q16.16 compute group (`0x010A`–`0x010F`).\n\n### Deterministic Q16.16 compute, `0x010A` to `0x010F`\n\nSix fixed-point tensor primitives - matmul, dot, softmax, relu, linear, and transpose - computed in\nsaturating Q16.16 integer arithmetic so the result is bit-identical on every node. This is the deterministic\nfloor the proof and commitment machinery rests on; see [Precompiles](/chain/precompiles) for the reference.\n\n### Attestation gate\n\nA trait-based gate (`core/execution/src/precompiles/attestation/`) consulted by `0x0101 MODEL_INFERENCE` and\n`0x0102 BATCH_INFERENCE` before they run. The default on mainnet validator binaries is always-reject, with\nno silent allow-by-omission: in strict inference mode the precompile refuses to run rather than execute an\nunattested non-deterministic path. A later phase adds live verification of hardware attestation, a\ncloud-attestation JWT plus GPU remote-attestation claims, so inference can run against an attested,\nTEE-hosted model. That verifier is not yet enabled.\n\n## Design rationale\n\nThe hard problem these families solve is letting a contract believe a model's output without paying to run\nthe model on-chain. There are two honest answers, and the docs keep them distinct. For work that can be made\ndeterministic, the chain commits to the work and verifies a proof of it (the `0x0107`–`0x0109` group and the\n`0x010A`–`0x010F` compute primitives), so a contract checks a small proof instead of repeating a large\ncomputation - that result is verifiable on-chain. For hosted inference that cannot be made bit-identical\n(`0x0100`–`0x0106`), the chain does not claim a cryptographic proof of correctness: it returns a signed\nreceipt gated by hardware attestation, an attestable statement about what ran and where. Commitments use\nPoseidon, proofs use a fixed Halo2-KZG verifier, and the byte output is frozen. The attestation gate\naddresses the one place determinism cannot reach, floating-point inference on a GPU: rather than trust it\nblindly, the gate defaults to refusing it until hardware attestation proves where it ran. The deliberate\nchoice to fail closed, to reject by default, is the safe trade for a path that touches non-deterministic\ncompute.\n\n## Failure modes\n\n- The inference precompiles fail discoverably: with no runtime hosted, a call returns an error a contract\n can detect, not fabricated output.\n- The attestation gate defaults to always-reject. An unattested non-deterministic inference path does not\n run; there is no allow-by-omission, so a missing or stale attestation fails closed.\n- The verification precompiles have frozen byte output. Any drift in their result would fork the chain and\n invalidate every prior commitment, which is why the format is fixed rather than versioned in place.\n\n## Access and canon\n\nThis page is a summary. It carries the address map, the input and output shapes, for example\n\"returns a 32-byte boolean\", and plain-English behavior.\n\nThe implementation detail of all three families - circuit construction, prover and verifier internals, the\ninference runtime, the attestation-verification logic, and the exact ABIs - is public in the `citrate-chain`\nrepository (Apache-2.0). This page summarizes and links to that source rather than reproducing it. Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity. No keys, ceremony secrets, or\ncredentials appear on this page.\n\nFor the full internals - the circuit specs, verifier code, or attestation-verification design - read the\nprecompile and ZKP sources in `citrate-chain` linked below.\n\n## Source and verification\n\n- Source repo: `citrate-chain` (public, Apache-2.0)\n- Public anchors audited for this summary:\n `core/execution/src/precompiles/{verify.rs,inference.rs,attestation/}`,\n `core/execution/src/zkp/{poseidon_bn254.rs,halo2/}`, dispatch in\n `core/execution/src/precompiles/mod.rs`\n- Audited against SHA: `9d5959e`\n- Status: Implemented (pre-audit) for the deterministic verification path on testnet 40204. The\n attestation gate is Implemented in its always-reject default; live attestation verification is Specified,\n not yet enabled.\n"},"/chain/precompiles":{"slug":"/chain/precompiles","title":"Precompiles, address pages, tensor, x402, q16","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/core/execution/src/precompiles/","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"Tensor primitives","anchor":"tensor-primitives"},{"depth":3,"text":"x402 payment precompiles","anchor":"x402-payment-precompiles"},{"depth":3,"text":"Belnap-q16 lattice aggregation","anchor":"belnap-q16-lattice-aggregation"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The Citrate Network keeps the nine standard Ethereum precompiles and adds its own at higher addresses:\ndeterministic tensor primitives, x402 payment verification, and Belnap-q16 lattice aggregation. This page\nis the reference for the address pages and for the tensor, x402, and q16 surfaces, with each item cited to its\ncode path. It is for contract authors.\n\n## What it is\n\nA precompile is a contract address that runs native code rather than EVM bytecode, so a common operation\nruns faster and cheaper than the equivalent Solidity. The Citrate Network keeps the standard nine at `0x01`\nto `0x09`, ECRECOVER through BLAKE2F, and adds several address pages above them, routed by\n`PrecompileExecutor` in `core/execution/src/precompiles/mod.rs`.\n\n| Page | Range | Purpose |\n|---|---|---|\n| AI, verification, compute | `0x0100` to `0x010F` | inference runtime, proof verification, deterministic Q16 compute |\n| Learning | `0x0110` to `0x011F` | Belnap-q16 aggregation, routing inference |\n| Signature verification | `0x0120` to `0x012F` | Ed25519 signature verification |\n| Recursive-fold verification | `0x0130` to `0x013F` | CommD proof verification (feature-gated) |\n| x402 payments | `0x0200` to `0x0209` | EIP-712 and EIP-3009 payment verification |\n\n`is_precompile()` recognizes an address by matching its leading zero bytes plus the page bytes, and\n`execute()` dispatches by the same prefix. This page documents the tensor, x402, and q16 surfaces. The\n`0x0100` to `0x0109` inference, verification, and attestation surfaces are summarized\non a separate page, see [inference, verification, and attestation precompiles](/chain/precompiles-zkp).\n\n## How to use it\n\nCall a precompile like any EVM precompile: `staticcall` the address with ABI-packed input and read the\nreturn bytes.\n\n1. Pack the input for the precompile you are calling. The exact byte offsets are defined by the source for\n each one; the layout there is the authoritative ABI.\n2. `staticcall` the precompile address. For example, x402 EIP-712 verification is a `staticcall` to\n `0x0000…0200`.\n3. Read the return value. For the x402 verifiers the 32-byte return holds the recovered address in its low\n 20 bytes, mirroring ECRECOVER, with the zero address on failure.\n4. For exact encodings, work from the test vectors in the precompile's source file rather than from prose.\n\nSee [chain tutorials](/chain/tutorials/call-citrate-rpc) for a worked x402 verification, and [chain RPC](/chain/rpc) for\nconnection details.\n\n## Reference\n\n### Tensor primitives\n\nCode: `core/execution/src/tensor/` and the wire format in\n`core/execution/src/precompiles/tensor_format.rs`.\n\nThe in-VM tensor engine (`tensor/engine.rs`, `TensorEngine`) allocates tensor values, an `ArrayD` plus\na shape and an optional gradient, keyed by a `U256` id, with a configurable memory cap; `tensor/ops.rs`\nprovides the operations. Every AI precompile that accepts or returns tensor data does so in the canonical\nbinary tensor format, version 1, which is frozen:\n\n```text\n[ 1 byte rank ] # 0 to 4; rank 5 or more is rejected\n[ rank x 4 bytes shape (u32 BE) ] # each dim at least 1, row-major\n[ 1 byte dtype ] # selector; unknown maps to UnknownDtype\n[ data bytes ] # element_count x dtype.byte_size, big-endian\n```\n\nThat the format is frozen is what keeps the `0x0107 TENSOR_COMMIT` commitments stable across versions. An\nincompatible change requires a new dtype byte or a new precompile address. The deterministic Q16.16 compute\nprecompiles (`0x010A` to `0x010F`) are implemented as six fixed-point tensor primitives - matmul, dot,\nsoftmax, relu, linear, and transpose - and dispatched to `compute::execute` (`compute.rs`); treat the\ntensor-engine API as the documented surface here.\n\n### x402 payment precompiles\n\nCode: `core/execution/src/precompiles/x402.rs`. Addresses `0x0200` to `0x0202`. These accelerate Coinbase\nx402 payment verification at the precompile level, about nine times cheaper than the equivalent Solidity\nper the source. Each is deterministic, ecrecover plus keccak.\n\n| Address | Name | Input | Output | Gas |\n|---|---|---|---|---|\n| `0x0200` | `EIP712_VERIFY` | EIP-712 typed-data digest material and signature (r, s, v) | 32 bytes: 12 zero bytes then the 20-byte recovered address; zero address on failure | 3,450 |\n| `0x0201` | `TRANSFER_AUTH_VERIFY` | EIP-3009 `TransferWithAuthorization` fields and signature | 32 bytes: bytes 12 to 31 hold the recovered signer; verifies signer equals `from` | 4,200 |\n| `0x0202` | `BATCH_PAYMENT_VERIFY` | count then repeated `TransferWithAuthorization` records | per-record recovered addresses | `BATCH_BASE` 2,000 plus `BATCH_PER_PAYMENT` 3,800 times n |\n\nThe EIP-3009 type hash is\n`keccak256(\"TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)\")`.\nConstants live in `x402::addresses` and `x402::gas_costs`. A Level-3 upgrade path, a validator-embedded\nfacilitator with implicit header payments and cross-shard settlement, is described in the source header and\nADR-005; it is not yet implemented.\n\n### Belnap-q16 lattice aggregation\n\nCode: `core/execution/src/precompiles/q16/`. Address `0x0110`, `BELNAP_AGGREGATE`. A second learning slot,\n`0x0111` (`ROUTING_INFERENCE`), is wired in the dispatcher but is future work (RM-FL-2).\n\nThe substrate is a hand-rolled, integer-only, saturating Q16.16 fixed-point library (`q16/mod.rs`, type\n`Q16(i64)`, where the real value is `inner / 2^16`). Every operation uses only `i64` math, with an `i128`\nintermediate only inside multiply and divide, no floats and no `unsafe`, so results are bit-identical on any CPU. Overflow saturates to `Q16::MAX` or\n`Q16::MIN`, division by zero returns `MAX` or `MIN` by the numerator's sign, and nothing panics. `f64`\nconversions exist only behind `#[cfg(test)]`.\n\n`0x0110 BELNAP_AGGREGATE` (`q16/belnap.rs`) aggregates per-validator embedding contributions into a Q16\nweighted-mean value per dimension and a Belnap-FOUR state per dimension drawn from {Neither, True, Both,\nFalse}. The output is bit-deterministic; gas is `2000 + 50 * dim`.\n\n- Input: a header giving `dim` and `n`, the participant count, then for each participant and dimension a Q16\n embedding, a Q16 confidence, and a Q16 weight, plus a Q16 positive threshold and a Q16 negative threshold.\n- Output: per dimension, 8 bytes of Q16 (i64) aggregated value then 1 byte of Belnap state, so `dim * 9`\n bytes in all.\n- The off-chain f32 reference is `core/learning/src/belnap.rs`. The precompile matches it at the semantic\n level, sign and confidence regime, not at byte equality, because the reference uses f32.\n\n## Design rationale\n\nThese precompiles exist because the operations they perform need to be both cheap and exactly reproducible\non every node. Payment verification is signature recovery, which is far cheaper as native code than as\nSolidity, so x402 is a precompile. Aggregation of model contributions has to produce the identical result\non every validator or the chain would fork, and floating-point math does not, so the q16 path is built on\ninteger fixed-point that saturates rather than panicking and is bit-identical across hardware. The frozen\ntensor format follows the same discipline: a stable wire format is what lets a commitment made today still\nverify tomorrow.\n\n## Failure modes\n\n- The x402 verifiers fail closed: on a bad signature they return the zero address rather than reverting, so\n a caller that does not check the return treats a failed verification as an unrecognized signer, not a\n success.\n- Q16.16 arithmetic never panics; overflow saturates and division by zero returns a signed extreme. The\n cost is that a saturated value is a clamped value, not an error, so contracts that care about the\n difference must check ranges themselves.\n- The tensor format rejects rank 5 or higher and unknown dtypes rather than guessing, so malformed tensor\n input does not silently decode into a wrong shape.\n\n## Access and canon\n\nThis page is staged commercial. It documents implementation depth, precompile ABIs and gas economics, that\na contracted, identity-verified builder should have but that we would rather not have vacuumed up\nanonymously. Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity. Nothing here is secret: addresses, public ABIs,\ngas constants, and type hashes are all observable on-chain. The genuinely sensitive precompiles, the\ninference, proof-verification, and attestation internals, are not on this page; see\n[verification, inference, and attestation precompiles](/chain/precompiles-zkp).\n\n## Source and verification\n\n- Source repo: `citrate-chain`\n- Source files: `core/execution/src/precompiles/mod.rs`,\n `core/execution/src/precompiles/{x402.rs,tensor_format.rs}`,\n `core/execution/src/precompiles/q16/{mod.rs,belnap.rs}`, `core/execution/src/precompiles/compute.rs`,\n `core/execution/src/tensor/`, `core/learning/src/belnap.rs`\n- Audited against SHA: `9d5959e`\n- Status: Implemented (pre-audit). The tensor format, the `0x010A` to `0x010F` Q16 compute primitives, and\n the x402 and q16 precompiles run on testnet 40204. `0x0111 ROUTING_INFERENCE` is wired in the dispatcher\n but is future work (RM-FL-2), and the `0x0130` recursive-fold CommD verifier is feature-gated.\n"},"/chain/rpc":{"slug":"/chain/rpc","title":"JSON-RPC reference","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/core/api/src/{eth_rpc.rs,server.rs,ai_rpc.rs,economics_rpc.rs,methods/}","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"Ethereum-compatible (`eth_*`)","anchor":"ethereum-compatible-eth_"},{"depth":3,"text":"Chain and DAG (`chain_*`, `citrate_*`)","anchor":"chain-and-dag-chain_-citrate_"},{"depth":3,"text":"Economics (`citrate_*`)","anchor":"economics-citrate_"},{"depth":3,"text":"AI (`citrate_*`)","anchor":"ai-citrate_"},{"depth":3,"text":"Network and operations (`citrate_*`)","anchor":"network-and-operations-citrate_"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The JSON-RPC interface a Citrate Node serves over HTTP and WebSocket. It speaks the Ethereum-compatible\n`eth_*` methods you already know, plus a `chain_*` and `citrate_*` set for reading the BlockDAG, calling\non-chain models, and reading network economics. This page is for developers building against the Citrate\nNetwork. If you have never made a call, start with [your first 10 minutes](/start/tutorials/your-first-10-minutes).\n\n## What it is\n\nA Citrate Node exposes one JSON-RPC endpoint. A local node serves it at `http://127.0.0.1:8545`. Every\nrequest follows JSON-RPC 2.0, and quantities come back as `0x`-prefixed hex strings, the same convention\nEthereum uses:\n\n```json\n{ \"jsonrpc\": \"2.0\", \"id\": 1, \"method\": \"eth_chainId\", \"params\": [] }\n```\n\nThere are roughly ninety methods, registered across three namespaces:\n\n- `eth_*` is the Ethereum-compatible surface, so existing Solidity tooling and signing libraries work\n unchanged.\n- `chain_*` reads the BlockDAG directly: tips, height, a block, a transaction.\n- `citrate_*` is the Citrate-native surface for DAG statistics, network economics, on-chain inference, and\n operator controls.\n\nThe one fact worth confirming before anything else is that you are pointed at Citrate. Ask the node for its\nchain id; `0x9d0c` is 40204, and 40204 is the testnet:\n\n```bash\ncurl -s http://127.0.0.1:8545 -H 'content-type: application/json' \\\n -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_chainId\",\"params\":[]}'\n# {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x9d0c\"} # 0x9d0c is 40204\n```\n\n## How to use it\n\nEach call is a single HTTP POST. A small shell helper keeps the examples short:\n\n```bash\nexport RPC=http://127.0.0.1:8545\n\nrpc () {\n curl -s \"$RPC\" -H 'content-type: application/json' \\\n -d \"{\\\"jsonrpc\\\":\\\"2.0\\\",\\\"id\\\":1,\\\"method\\\":\\\"$1\\\",\\\"params\\\":${2:-[]}}\"\n}\n```\n\nRead the latest height, then read the DAG:\n\n```bash\nrpc eth_blockNumber\n# {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x3039\"}\n\nrpc citrate_getDagStats\n# { \"tipsCount\": 3, \"maxBlueScore\": 11800, \"height\": 12345,\n# \"ghostdagParams\": { \"k\": 18, \"maxParents\": 10, \"finalityDepth\": 100 } }\n```\n\nRun a model call on the chain. Inference is a chain operation here, not an outside service:\n\n```bash\nrpc citrate_getTextEmbedding '[\"the quick brown fox\"]'\n# result: [[0.0123, -0.045, ...]] (a bge-m3 embedding vector)\n```\n\nA single embedding or search call accepts at most 256 inputs (`MAX_EMBEDDING_INPUTS`); a larger batch\nreturns invalid params. The consensus that orders all of these reads is GhostDAG, covered under\n[Citrate Network consensus](/chain/consensus), and the fee and supply numbers are in\n[economics](/chain/economics).\n\n## Reference\n\nMethods are grouped by what they touch. Each row cites the source file in `citrate-chain` it is registered\nin. Methods that do not appear in the source at this SHA are not listed here.\n\n### Ethereum-compatible (`eth_*`)\n\nRegistered in `core/api/src/eth_rpc.rs` and `core/api/src/server.rs`.\n\n| Method | Params | Returns |\n|---|---|---|\n| `eth_chainId` | none | chain id, hex (`0x9d0c` is 40204) |\n| `eth_blockNumber` | none | latest height, hex |\n| `eth_getBlockByNumber` | `[blockTag\\|hex, includeTxs]` | block object or `null` |\n| `eth_getTransactionByHash` | `[hash]` | transaction object or `null` |\n| `eth_getTransactionReceipt` | `[hash]` | receipt object or `null` |\n| `eth_getBalance` | `[address, blockTag]` | balance, hex |\n| `eth_getCode` | `[address, blockTag]` | contract code, hex |\n| `eth_getStorageAt` | `[address, slot, blockTag]` | storage word, hex |\n| `eth_getTransactionCount` | `[address, blockTag]` | nonce, hex |\n| `eth_accounts` | none | `[]`, the node holds no keys |\n| `eth_call` | `[txObject, blockTag]` | return data, hex |\n| `eth_estimateGas` | `[txObject]` | gas estimate, hex |\n| `eth_gasPrice` | none | gas price, hex |\n| `eth_feeHistory` | `[blockCount, newestBlock, percentiles]` | fee-history object |\n| `eth_getLogs` | `[filterObject]` | matching log entries |\n\nThe standard filter, send, and node-metadata methods are also present (`eth_sendRawTransaction`,\n`eth_newFilter`, `eth_syncing`, `net_version`, `web3_clientVersion`, and so on).\n\n### Chain and DAG (`chain_*`, `citrate_*`)\n\nThe `chain_*` namespace reads the BlockDAG directly; `citrate_getDagStats` summarizes the current tip set.\nRegistered in `core/api/src/server.rs` and `core/api/src/eth_rpc.rs`.\n\n| Method | Params | Returns |\n|---|---|---|\n| `chain_getHeight` | none | current height |\n| `chain_getTips` | none | the current tip hashes |\n| `chain_getBlock` | `[hashOrHeight]` | a block |\n| `chain_getTransaction` | `[hash]` | a transaction |\n| `citrate_getDagStats` | none | tips, blue score, height, GhostDAG params |\n\n### Economics (`citrate_*`)\n\nRegistered in `core/api/src/economics_rpc.rs`. These return method-not-found when the node runs without an\neconomics manager configured.\n\n| Method | Params | Returns |\n|---|---|---|\n| `citrate_getToken` | none | `{name, symbol, decimals, totalSupply, totalMinted}` |\n| `citrate_getEconomicState` | none | supply, gas price, staked amount, treasury, and related fields |\n| `citrate_gasPrice` | none | base gas price, hex |\n| `citrate_getStakedBalance` | `[address]` | staked balance, hex |\n| `citrate_getReputationScore` | `[address]` | reputation score, number |\n\n### AI (`citrate_*`)\n\nEmbedding, search, and chat are registered in `core/api/src/ai_rpc.rs`; model and inference lifecycle\nmethods in `core/api/src/server.rs`.\n\n| Method | Params | Returns |\n|---|---|---|\n| `citrate_getTextEmbedding` | `[text \\| string[]]`, at most 256 inputs | one embedding, or one per input |\n| `citrate_semanticSearch` | `[query, documents[], topK?]`, at most 256 documents | entries ranked by cosine similarity |\n| `citrate_chatCompletion` | `[{request}]` or `[prompt, maxTokens?, temperature?]` | a chat-completion response |\n| `citrate_getModels` | none | registered models |\n| `citrate_getModel` | `[modelId]` | model detail |\n| `citrate_getInferenceResult` | `[id]` | an inference result by id |\n| `citrate_createTrainingJob` | `[{job}]` | a training-job handle |\n| `citrate_getTrainingJob` | `[id]` | training-job status |\n| `citrate_deployModel` | operator-authenticated | registers a model |\n\nThe embedding model is `bge-m3`; the default chat model is `mistral-7b-instruct-v0.3`.\n\n### Network and operations (`citrate_*`)\n\nAggregate reads are open; the snapshot and emergency controls authenticate inside the handler against an\noperator-supplied token, never by seat.\n\n| Method | Params | Returns |\n|---|---|---|\n| `citrate_getMempoolStats` | none | aggregate mempool statistics |\n| `citrate_getMempoolSnapshot` | operator-authenticated | per-transaction mempool detail |\n| `citrate_emergencyStatus` | operator-authenticated | block-production pause state |\n| `citrate_emergencyPause` | operator-authenticated | halts block production |\n| `citrate_emergencyResume` | operator-authenticated | resumes block production |\n\n## Failure modes\n\nThe interface fails closed, and the errors are standard JSON-RPC:\n\n- `-32601`, method not found. A typo, or a method the node does not serve. The economics methods return this\n when no economics manager is configured; the chain, DAG, and core AI methods do not require one.\n- `-32602`, invalid params. The shape or count is wrong. An embedding or search batch over 256 inputs lands\n here.\n- `-32600`, invalid request. The envelope is not valid JSON-RPC 2.0.\n\nA chain id other than `0x9d0c` is not an error code; it means you are pointed at a different network. The\noperator-authenticated methods (`citrate_getMempoolSnapshot`, the `citrate_emergency*` controls,\n`citrate_deployModel`) reject a missing or wrong token rather than acting, so an unprivileged caller cannot\npause production or read per-transaction mempool detail.\n\n## Access and canon\n\nPublic. The `eth_*`, `chain_*`, DAG, and AI methods are the open surface a developer needs to build. No\nkeys, mnemonics, or operator tokens appear here; the operator token is supplied at runtime and is never\ndocumented. The economics reads are open on a configured node, though the deeper reward model narrative\nsits at the commercial tier. Identity on inference is a claim unless signed: anonymous callers reach public\nmodels only, and gated models require the signature binding.\n\n## Source and verification\n\nMethods verified against `citrate-chain` at `9d5959e` by enumerating every `add_sync_method(\"...\")`\nregistration in `core/api/src/eth_rpc.rs`, `server.rs`, `ai_rpc.rs`, `economics_rpc.rs`, and the\n`methods/` module. Chain id `0x9d0c` confirmed in `eth_rpc.rs`; the 256-input cap (`MAX_EMBEDDING_INPUTS`)\nin `ai_rpc.rs`. The network is live on testnet 40204. Status: Implemented (testnet, pre-audit).\n"},"/chain/sequencer":{"slug":"/chain/sequencer","title":"Citrate Sequencer, Mempool and Block Building","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/core/sequencer/src/mempool.rs, citrate-chain/core/sequencer/src/validator.rs, citrate-chain/core/sequencer/src/block_builder.rs","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"Mempool, `src/mempool.rs`","anchor":"mempool-srcmempoolrs"},{"depth":3,"text":"Transaction validator, `src/validator.rs`","anchor":"transaction-validator-srcvalidatorrs"},{"depth":3,"text":"Block builder, `src/block_builder.rs`","anchor":"block-builder-srcblock_builderrs"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The sequencer is the path a transaction takes between arriving at a node and landing in a block. It validates each transaction, holds the valid ones in a priority mempool, and assembles candidate blocks for proposers. This page is for developers who submit transactions and operators who want to understand how a block is put together.\n\n## What it is\n\nThink of the mempool as a sorting shed at harvest. Transactions come in, the ones that do not pass inspection are set aside, and the rest are sorted so the most important work is taken first. The sequencer (`core/sequencer/`) does this in three plain stages.\n\n1. Validate. An incoming transaction passes a validation pipeline: signature, balance, nonce, gas price and limit, data size, rate limit, and an address blacklist.\n2. Stage. A valid transaction enters the priority mempool, which holds up to ten thousand transactions by default, caps each sender, drops duplicates, and orders by priority.\n3. Build. The block builder takes the top-priority transactions, executes them, computes the state root and receipt root and an EIP-1559 base fee, and produces a signed candidate block.\n\nPriority is not gas price alone. Every transaction carries a `TxClass`, and each class has a multiplier so that system and compute traffic can be ordered ahead of ordinary transfers. The effective priority is the gas price multiplied by the class multiplier.\n\n| `TxClass` | Priority multiplier |\n|---|---|\n| `System` | 1000 |\n| `ModelUpdate` | 100 |\n| `Compute` | 80 |\n| `Training` | 50 |\n| `Inference` | 20 |\n| `Storage` | 10 |\n| `Standard` | 1 |\n\nSource: `core/sequencer/src/mempool.rs`, `TxClass::priority_multiplier()` (`src/mempool.rs:66`).\n\n## How to use it\n\nYou submit transactions to the mempool; proposers build from it. In Rust the flow is:\n\n1. Construct a mempool from a config (defaults: ten thousand capacity, one Gwei gas floor, one hundred transactions per sender).\n2. Validate a transaction against the current state before staging it.\n3. Add the validated transaction to the mempool; it is sorted into place by priority.\n4. When it is your turn to propose, build a candidate block from the top of the pool.\n\n```rust\nuse citrate_sequencer::*;\nuse std::sync::Arc;\n\nlet mempool = Arc::new(Mempool::new(MempoolConfig::default())); // 10k cap, 1 Gwei floor\n\n// Validate before staging.\nlet validator = TxValidator::new(ValidationRules::default(), state_provider);\nvalidator.validate(&tx).await?;\nmempool.add_transaction(tx).await?;\n\n// Build a candidate block from the pool.\nlet builder = BlockBuilder::new(builder_config, mempool.clone(), proposer_key).with_executor(executor);\nlet block = builder.build_block(selected_parent, merge_parents, parent_height, parent_blue_score, vrf_proof).await?;\n```\n\nTo watch pending transactions on a live node without Rust, use `mempool_getPending` and `citrate_getMempoolStats` over JSON-RPC; see [chain RPC](/chain/rpc).\n\n## Reference\n\n### Mempool, `src/mempool.rs`\n\n- `Mempool::new(config)`, build a mempool. `MempoolConfig::default()` is ten thousand capacity, one Gwei gas floor, one hundred per sender (`src/mempool.rs:181`).\n- `Mempool::add_transaction(tx)`, validate and insert with priority sorting.\n- `Mempool::get_transactions(limit)`, extract the top-priority batch for building.\n- `Mempool::pending_nonce_for(sender)`, the next pending nonce for a sender.\n- `Mempool::remove_transaction(hash)`, drop a transaction once it is in a block.\n- `Mempool::stats()`, size, gas statistics, and a per-class breakdown.\n\n### Transaction validator, `src/validator.rs`\n\n- `TxValidator::new(rules, state_provider)`, then `validate(tx)` or `validate_batch(txs)`.\n- `validate` runs, in order: address blacklist, rate limit, gas price and limit, data-size limit, signature (ed25519 and ECDSA), then balance and nonce (`src/validator.rs:188`).\n- `ValidationRules`, the configurable floor: minimum gas price, maximum gas limit, maximum data size, rate limits.\n- `ValidationPipeline`, splits a batch into valid and invalid.\n- `TxValidator::blacklist_address(addr)` and `unblacklist_address(addr)`.\n- `StateProvider` trait, async account lookups; `MockStateProvider` is test-only.\n\n### Block builder, `src/block_builder.rs`\n\nThe builder assembles the candidate block, plainly and in order: it takes the selected parent (the tip with the highest blue score) plus up to `max_parents - 1`, which is nine, merge parents from the consensus layer, pulls the top-priority transactions from the mempool, executes them, computes the state and receipt roots, sets the EIP-1559 base fee, and signs the result.\n\n- `BlockBuilder::new(config, mempool, proposer_key)`, with the proposer key held on the builder (`src/block_builder.rs:129`).\n- `BlockBuilder::with_executor(executor)`, attach the execution engine.\n- `BlockBuilder::build_block(selected_parent, merge_parents, parent_height, parent_blue_score, vrf_proof)`, produce the full candidate (`src/block_builder.rs:149`).\n- `BlockBuilderConfig`, maximum block size, gas limits, transaction bounds, and `block_time_target`, whose library default is two seconds (`src/block_builder.rs:77`). The testnet node configuration sets a one-second target, and the measured block interval on the testnet is about two seconds.\n\nParent selection itself belongs to the consensus layer: `ParentSelector` returns `(selected_parent, merge_parents)` for a new block. See [consensus](/chain/consensus) for how blue score picks the selected parent, and [the LVM](/chain/lvm) for how the transactions execute.\n\n## Design rationale\n\nTwo design choices stand out. The mempool sorts by class as well as price so the network does not let a fee war crowd out the work it exists to carry; compute and training traffic carry weight that an ordinary transfer does not. And the builder reuses the consensus layer's parent selection rather than inventing its own, so there is one definition of \"which parents\" across the codebase, not two that can drift apart. A block cadence of a few seconds keeps blocks frequent enough that priority work waits seconds, not minutes, while the BlockDAG absorbs the near-simultaneous blocks that a fast cadence produces.\n\n## Failure modes\n\n- A transaction that fails any validation check is set aside, not staged; an invalid signature, a stale nonce, a gas price below the floor, or a blacklisted sender each stop it at the door of this node's mempool. Signature and transaction hardening is in progress; see [SECURITY.md](https://github.com/CitrateNetwork/.github/blob/main/SECURITY.md).\n- The mempool is bounded. At capacity, low-priority transactions are evicted rather than allowed to exhaust memory, and per-sender caps stop one account from filling the pool. The system fails closed: it sheds the lowest-priority load rather than accepting unbounded work.\n- A built block carries computed state and receipt roots; a proposer cannot substitute arbitrary roots, because the receiving nodes recompute and reject a block whose roots do not match execution.\n\n## Access and canon\n\nPublic. Mempool behavior and validation rules are what a developer needs to submit transactions and reason about ordering and fees. No secrets appear here: proposer keys and VRF proofs are parameters, never documented values, and `MockStateProvider` is test scaffolding, not a production backend.\n\n## Source and verification\n\n- Source files: `core/sequencer/src/mempool.rs`, `src/validator.rs`, `src/block_builder.rs`.\n- Audited against SHA `9d5959e`.\n- Status: Implemented, pre external audit. The crate is internally tested, including property tests; it has not completed a third-party audit. The mempool denial-of-service surface (rate limiting, per-sender caps, eviction) is implemented but should be treated as pre-certification.\n"},"/chain/storage":{"slug":"/chain/storage","title":"Citrate Storage, State, RocksDB, Pruning, IPFS Pinning","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/core/storage/src/state_manager.rs, citrate-chain/core/storage/src/db/, citrate-chain/core/storage/src/chain/, citrate-chain/core/storage/src/state/, citrate-chain/core/storage/src/pruning/, citrate-chain/core/storage/src/ipfs/","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"RocksDB backend, `src/db/`","anchor":"rocksdb-backend-srcdb"},{"depth":3,"text":"Chain store, `src/chain/`","anchor":"chain-store-srcchain"},{"depth":3,"text":"State, `src/state/`, `src/state_manager.rs`","anchor":"state-srcstate-srcstate_managerrs"},{"depth":3,"text":"Pruning, `src/pruning/`","anchor":"pruning-srcpruning"},{"depth":3,"text":"IPFS model storage and pinning, `src/ipfs/`","anchor":"ipfs-model-storage-and-pinning-srcipfs"},{"depth":3,"text":"Caching, `src/cache/`","anchor":"caching-srccache"},{"depth":3,"text":"At-rest encryption","anchor":"at-rest-encryption"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Storage is where Citrate Network keeps what it has agreed on: the BlockDAG, account and AI state, and large model artifacts. This page gives developers and operators the mental model first, then the audited surface. It is the soil under the orchard; everything else grows on it.\n\n## What it is\n\nThe storage crate (`core/storage/`) is the persistence layer, and it coordinates three things. A RocksDB key-value backend is the durable store on disk. An in-process state model holds accounts, contract storage, code, and AI state, and computes a deterministic state root over them. An IPFS integration holds large off-chain model artifacts, with incentives for nodes that keep them pinned.\n\nState is flat, not a tree. Citrate Network does not use a Merkle-Patricia trie for the world state. State lives in flat RocksDB column families, and the state root is a hash over the sorted state. `StateManager::calculate_state_root()` computes an account root (accounts sorted by address), a storage root (storage sorted by address), and an AI root, then combines them as `SHA3-256(account_root || storage_root || ai_root)`. Because the inputs are sorted, every honest node with the same state computes the same root. This is a deliberate simplification, explained under design rationale below.\n\nThe top-level coordinator is `StorageManager` (`src/lib.rs`), built with `StorageManager::new(path, pruning_config)` for the default, no at-rest encryption, or `StorageManager::with_config(path, config)` for the full configuration including at-rest encryption. It owns the RocksDB handle, the block and transaction stores, the state store, the block and state caches, and the pruner. IPFS is a separate service, not a field of `StorageManager`.\n\n## How to use it\n\nFor an operator, storage is mostly configuration and then it runs itself.\n\n1. Choose a data path and a pruning policy, then construct a `StorageManager`.\n2. Start its background services; this spawns the auto-pruner on the configured interval.\n3. If you need at-rest encryption, supply an `EncryptionAtRestConfig` on the `StorageConfig` you pass to `with_config`; the key material is bound at construction, there is no separate runtime initialize step. Encryption is off by default.\n\n```rust\nuse std::sync::Arc;\nuse citrate_storage::{StorageManager, StorageConfig, PruningConfig};\nuse citrate_storage::crypto::at_rest::EncryptionAtRestConfig;\n\n// Default storage at a path, auto-pruning with defaults.\nlet storage = Arc::new(StorageManager::new(\"./data\", PruningConfig::default())?);\nstorage.clone().start_services().await; // spawns the auto-pruner (async, takes Arc)\n\n// Full config with at-rest encryption:\nlet cfg = StorageConfig::default()\n .with_encryption(EncryptionAtRestConfig::with_password(\"...operator-supplied passphrase...\"));\nlet storage = StorageManager::with_config(\"./data\", cfg)?;\n```\n\nFor storage paths, pruning, and the IPFS daemon in an operational setting, see [run a node](/operators/run-a-node).\n\n## Reference\n\n### RocksDB backend, `src/db/`\n\n- `RocksDB::open(path)` (`src/db/rocks_db.rs`) wraps `rocksdb::DB` and opens every column family with tuned options. Compression is LZ4 in production.\n- Read and write surface: `get_cf`, `put_cf`, `delete_cf`, `iter_cf`, `prefix_iter_cf`; batched writes commit with either `write_batch` (no fsync) or `write_batch_sync` (fsync). Atomic counters back a durability tripwire (`REM-2 / WP-H1.3`) so producer-path writes are verifiably synced.\n- Column families (`src/db/column_families.rs`) are grouped by role: blocks, headers, transactions, receipts; state, accounts, storage, code, account versions; AI models and training; the DAG; checkpoints; and metadata.\n\n### Chain store, `src/chain/`\n\n- `BlockStore` (`src/chain/block_store.rs`), `put_block` writes a block plus its parent-to-child DAG relations, height index, and blue set in one atomic batch. Latest height is cached for fast lookup; truncated or corrupt values are treated as missing (`SECREM-01 CONS-6`).\n- `TransactionStore` (`src/chain/transaction_store.rs`), batched, synced writes with a sender-nonce index, plus receipts.\n\n### State, `src/state/`, `src/state_manager.rs`\n\n- `StateStore` (`src/state/state_store.rs`) over RocksDB: `get/put_account`, `get/put_storage`, `delete_storage`, `get/put_code`, `get_all_accounts`, and `write_state_batch_sync` for finalized state. AI state is first-class: `get/put_model`, `get/put_training_job`.\n- `AIStateTree` (`src/state/ai_state.rs`), an in-memory map of models, training jobs, model-weight CIDs, an inference cache, and LoRA adapters, with `calculate_root()` over the AI sub-state.\n- `StateManager` (`src/state_manager.rs`) composes `StateStore` and `AIStateTree`, and `calculate_state_root()` derives the world-state root as `SHA3-256(account_root || storage_root || ai_root)` (`src/state_manager.rs:34`).\n\n### Pruning, `src/pruning/`\n\n`Pruner` (`src/pruning/pruner.rs`) with `PruningConfig` (`keep_blocks`, `keep_states`, `interval`, `batch_size`, `auto_prune`). `prune()` runs one cycle; `prune_blocks_before(height)` and `prune_states_before(height)` delete historical blocks and old state below a threshold; `compact()` triggers RocksDB compaction afterwards. `start_auto_pruning` runs the loop on the configured interval, spawned by `StorageManager::start_services()`.\n\n### IPFS model storage and pinning, `src/ipfs/`\n\n- `IPFSService` (`src/ipfs/mod.rs`), an HTTP client to an IPFS daemon: `store_model`, `retrieve_model`, `list_pinned_models`, `get_model_metadata`, `fetch_raw`. `IpfsDaemon` (`src/ipfs/daemon.rs`) manages a local kubo daemon.\n- Chunking (`src/ipfs/chunking.rs`): models above a size threshold are split into chunks, each addressed by a BLAKE3 hash; `ChunkManifest` records the per-chunk CIDs for reassembly.\n- Pinning incentives (`src/ipfs/pinning.rs`): `PinningManager` accounts for replica counts and accrued rewards per CID and per pinner; rewards scale by pinned bytes, model type, and duration. `PersistentPinRegistry` persists the registry.\n- `EncryptedIPFSStore` (`src/ipfs/encrypted_store.rs`), optional client-side encryption (AES-256-GCM, per-chunk nonce and tag) with an address-based access list; public metadata stays in the clear.\n\n### Caching, `src/cache/`\n\n`Cache` (`src/cache/lru_cache.rs`) is a thread-safe LRU. `StorageManager` keeps a hot block cache and state cache; `clear_caches()` flushes them.\n\n### At-rest encryption\n\nWhen enabled via `StorageConfig`, the storage layer applies a cipher-agile, post-quantum hybrid envelope (`src/crypto/`): a hybrid key exchange combining ML-KEM (CRYSTALS-Kyber) with X25519, feeding AES-256-GCM data encryption, to resist a harvest-now, decrypt-later attack. Per-column-family keys are derived from an Argon2id master key through HKDF-SHA3, with scheduled rotation. Key commitments, never key material, can be anchored on-chain for auditability (`src/crypto/key_commitment.rs`). It is off by default; enable it by setting `StorageConfig::with_encryption(EncryptionAtRestConfig::with_password(...))` before `StorageManager::with_config`.\n\n## Design rationale\n\nThe state root is computed by hashing sorted state with SHA3-256, not by a Merkle-Patricia trie, and that is a deliberate simplification. A trie buys you compact inclusion proofs, but it costs write concurrency: every write touches a path of internal nodes, and concurrent writers contend on them. Citrate Network carries AI workloads that write state in bulk, so we took the trade the other way. Flat column families let writes proceed in parallel and support multi-version concurrency, and a deterministic hash over sorted state still gives every node the same root to agree on. We do not advertise trie-proof structure, because we do not implement one; the page documents the mechanism that exists.\n\nDurability is the other deliberate choice. Producer-path writes commit with fsync and are checked by a tripwire counter, so a block a node claims to have stored is a block it has actually flushed to disk, not one sitting in a buffer that a crash could lose.\n\n## Failure modes\n\n- Truncated or corrupt stored values are treated as missing rather than parsed into bad state, so a damaged record fails closed at read time instead of poisoning consensus.\n- Producer-path writes are fsync'd and counted; the durability tripwire flags a path that writes without syncing, so a crash does not silently drop a block the node reported as stored.\n- At-rest encryption holds no key material on disk: the master key is operator-supplied at runtime, per-family keys are derived, key material is zeroized after use, and only key commitments, not keys, are ever anchored on-chain.\n\n## Access and canon\n\nPublic. The storage model, the state-root construction, and the reference are what a developer or operator needs to reason about persistence, pruning, and at-rest protection. No secrets appear here: no keys, passphrases, or private endpoints.\n\n## Source and verification\n\n- Source files: `core/storage/src/state_manager.rs`, `src/db/`, `src/chain/`, `src/state/`, `src/pruning/`, `src/ipfs/`, `src/crypto/`.\n- Audited against SHA `9d5959e`.\n- Status: Implemented, pre external audit. The crate is internally tested, including a durability and fsync suite; it has not completed a third-party audit. Several call sites carry remediation markers (for example `REM-2 / WP-H1.3` fsync enforcement, `SECREM-01 CONS-6` corrupt-data handling). Treat it as production-track but pre-certification.\n"},"/chain/tutorials/call-citrate-rpc":{"slug":"/chain/tutorials/call-citrate-rpc","title":"Call the Citrate RPC","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain core/api","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Step 1, confirm you are on Citrate","anchor":"step-1-confirm-you-are-on-citrate"},{"depth":3,"text":"Step 2, read the chain height","anchor":"step-2-read-the-chain-height"},{"depth":3,"text":"Step 3, read the BlockDAG","anchor":"step-3-read-the-blockdag"},{"depth":3,"text":"Step 4, read the network economics","anchor":"step-4-read-the-network-economics"},{"depth":3,"text":"Step 5, read an error on purpose","anchor":"step-5-read-an-error-on-purpose"},{"depth":3,"text":"Step 6, pick your next page","anchor":"step-6-pick-your-next-page"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"A short, copy-paste walkthrough of the JSON-RPC surface. You will confirm you are on Citrate, read the\nBlockDAG, and read the network economics, learning the request and response shape, the id field, and the two\nerror codes you will see most often. Every method here exists in `citrate-chain`, and every step is\nread-only, so it needs no account and no SALT.\n\n## What it is\n\nA tour of four real methods over plain HTTP. You learn the JSON-RPC envelope once, then reuse it for the\nrest of the [JSON-RPC reference](/chain/rpc). Nothing here writes state, so you can run it against any\nCitrate endpoint you can reach without risk.\n\n## How to use it\n\nYou will need a reachable Citrate JSON-RPC endpoint. A local node serves `http://127.0.0.1:8545`. If you do\nnot have one, use the [RPC sandbox](/sandboxes/rpc) instead: the same methods, in the browser. You will also\nwant `curl`, and `jq` for readable output.\n\nSet up a small helper so the steps stay short:\n\n```bash\nexport RPC=http://127.0.0.1:8545\n\nrpc () {\n curl -s \"$RPC\" -H 'content-type: application/json' \\\n -d \"{\\\"jsonrpc\\\":\\\"2.0\\\",\\\"id\\\":1,\\\"method\\\":\\\"$1\\\",\\\"params\\\":${2:-[]}}\"\n}\n```\n\nEvery request carries four fields: `jsonrpc` (always `\"2.0\"`), an `id` you choose to match the reply to the\nrequest, the `method` name, and a `params` array. The helper sets `id` to `1` and defaults `params` to an\nempty array. The reply echoes your `id` and returns either a `result` or an `error`.\n\n### Step 1, confirm you are on Citrate\n\n```bash\nrpc eth_chainId\n# {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x9d0c\"}\nprintf '%d\\n' 0x9d0c # 40204\n```\n\n`0x9d0c` is 40204, and 40204 is Citrate testnet. The reply echoes the `id` you sent and puts the chain id in\n`result` as a hex string. Anything other than `0x9d0c` means you are pointed at a different network.\n\n### Step 2, read the chain height\n\n```bash\nrpc chain_getHeight\n# {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":12345}\n```\n\n`chain_getHeight` returns a plain number, the current height of the selected chain. It takes no parameters,\nso the helper's empty array is correct. Height is one of two clocks on a BlockDAG; the other, blue score,\ncomes next.\n\n### Step 3, read the BlockDAG\n\n```bash\nrpc citrate_getDagStats | jq\n```\n\n```json\n{\n \"totalBlocks\": 12345,\n \"blueBlocks\": 11727,\n \"redBlocks\": 618,\n \"tipsCount\": 3,\n \"maxBlueScore\": 11800,\n \"currentTips\": [\"0x...\", \"0x...\", \"0x...\"],\n \"height\": 12345,\n \"ghostdagParams\": { \"k\": 18, \"maxParents\": 10, \"maxBlueScoreDiff\": 1000, \"pruningWindow\": 100000, \"finalityDepth\": 100 }\n}\n```\n\nTwo things to notice. `maxBlueScore` is the DAG's ordering clock, not `height`. And `tipsCount` above one is\nnormal: several tips can exist at once on a BlockDAG, and GhostDAG merges them into a single order. To go\ndeeper on tips and blocks, read [read the DAG](/chain/tutorials/read-the-dag); for the words themselves, the\n[primer](/start/primer). One honesty note: `currentTips`, `maxBlueScore`, `height`, and `ghostdagParams` are\nread from chain state, while `blueBlocks` and `redBlocks` are estimated from height, so treat the\nblue-and-red split as indicative.\n\n### Step 4, read the network economics\n\n```bash\nrpc citrate_getToken | jq\n# { \"name\": \"Citrate\", \"symbol\": \"SALT\", \"decimals\": 18, \"totalSupply\": \"0x...\", \"totalMinted\": \"0x...\" }\n```\n\nSALT has 18 decimals and a one-trillion cap. It is the unit fees and rewards are counted in, not a product. For\nthe live economic snapshot, blocks height, gas price, staked amount, and treasury, call\n`citrate_getEconomicState`:\n\n```bash\nrpc citrate_getEconomicState | jq\n# { \"blockHeight\": 12345, \"totalSupply\": \"0x...\", \"circulatingSupply\": \"0x...\", \"gasPrice\": \"0x...\",\n# \"stakedAmount\": \"0x...\", \"treasuryBalance\": \"0x...\", ... }\n```\n\n`citrate_getEconomicState` needs an economics manager configured on the node. Without one it returns\n`-32601 Method not found`, the same code you would see for a typo. Detail: [economics](/chain/economics).\n\n### Step 5, read an error on purpose\n\nSend a method that does not exist, then send a real method with the wrong parameter shape:\n\n```bash\nrpc citrate_thisIsNotAMethod\n# {\"jsonrpc\":\"2.0\",\"id\":1,\"error\":{\"code\":-32601,\"message\":\"Method not found\"}}\n\nrpc eth_getCode '[\"not-an-address\"]'\n# {\"jsonrpc\":\"2.0\",\"id\":1,\"error\":{\"code\":-32602,\"message\":\"Invalid params\"}}\n```\n\n`-32601` and `-32602` are the two errors you will meet most. The first says the node does not serve that\nmethod, the second says the method exists but your `params` were wrong. Both are standard JSON-RPC codes.\n\n### Step 6, pick your next page\n\n| If you want to | Go to |\n|---|---|\n| See every RPC method, with params and shapes | [JSON-RPC reference](/chain/rpc) |\n| Walk the tips and fetch a block | [read the DAG](/chain/tutorials/read-the-dag) |\n| Deploy a contract from the command line | [deploy with the CLI](/chain/tutorials/deploy-a-contract-with-the-cli) |\n| Understand the words you just saw | [the primer](/start/primer) |\n\n## Reference\n\nThe methods used above, with their source files in `citrate-chain`:\n\n| Method | What it returns | Source |\n|---|---|---|\n| `eth_chainId` | the chain id, `0x9d0c` | `core/api/src/eth_rpc.rs` |\n| `chain_getHeight` | the current height as a number | `core/api/src/server.rs` |\n| `citrate_getDagStats` | tips, blue score, GhostDAG params | `core/api/src/eth_rpc.rs` |\n| `citrate_getToken` | SALT name, decimals, supply | `core/api/src/economics_rpc.rs` |\n| `citrate_getEconomicState` | live economic snapshot | `core/api/src/economics_rpc.rs` |\n| `eth_getCode` | the code at an address | `core/api/src/eth_rpc.rs` |\n\n## Failure modes\n\n- **`Connection refused`** means no node is listening on `$RPC` (the default is `127.0.0.1:8545`). Use the\n [RPC sandbox](/sandboxes/rpc) instead.\n- **`-32601 Method not found`** is a typo, or a method the node does not serve. The economics methods,\n `citrate_getToken` and `citrate_getEconomicState`, need an economics manager configured; the chain and DAG\n methods do not.\n- **`-32602 Invalid params`** means the method exists but your `params` shape was wrong. Check it against the\n [reference](/chain/rpc).\n- **A chain id other than `0x9d0c`** means you are not on Citrate.\n\n## Access and canon\n\nPublic and read-only. No keys or credentials are needed, and nothing here writes state. Chain id 40204 is\ntestnet. The example outputs are illustrative; exact values depend on the node's current state.\n\n## Source and verification\n\nMethods verified against `citrate-chain` at `9d5959e`: `eth_chainId` and `eth_getCode` in\n`core/api/src/eth_rpc.rs`, `chain_getHeight` in `core/api/src/server.rs`, `citrate_getDagStats` in\n`core/api/src/eth_rpc.rs`, and `citrate_getToken` and `citrate_getEconomicState` in\n`core/api/src/economics_rpc.rs`. The full surface is on the [JSON-RPC reference](/chain/rpc). Status:\nImplemented (testnet 40204), pre external audit.\n"},"/chain/tutorials/deploy-a-contract-with-the-cli":{"slug":"/chain/tutorials/deploy-a-contract-with-the-cli","title":"Deploy a contract with the CLI","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain cli/src/commands/contract.rs","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Step 1, build the tool","anchor":"step-1-build-the-tool"},{"depth":3,"text":"Step 2, initialize the config","anchor":"step-2-initialize-the-config"},{"depth":3,"text":"Step 3, confirm the chain id","anchor":"step-3-confirm-the-chain-id"},{"depth":3,"text":"Step 4, prepare an account and the artifact","anchor":"step-4-prepare-an-account-and-the-artifact"},{"depth":3,"text":"Step 5, deploy the contract","anchor":"step-5-deploy-the-contract"},{"depth":3,"text":"Step 6, verify the deployment with an eth_ call","anchor":"step-6-verify-the-deployment-with-an-eth_-call"},{"depth":3,"text":"Step 7, read and call the contract","anchor":"step-7-read-and-call-the-contract"},{"depth":3,"text":"Step 8, pick your next page","anchor":"step-8-pick-your-next-page"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"A copy-paste walkthrough of deploying compiled bytecode with the `citrate` command-line tool. You will\nconfirm the chain id, point the tool at your endpoint, deploy a contract, then verify the deployment with a\nread-only `eth_` call. The deploy step writes state, so it needs an account with a little SALT; the verify\nstep does not.\n\n## What it is\n\n`citrate` is the command-line tool built from `citrate-chain`. Its contract operations live under the\n`contract` subcommand, which submits an `eth_sendTransaction` to your endpoint, waits for the receipt, and\nprints the new address. Under the surface it speaks the same JSON-RPC you met in\n[call the Citrate RPC](/chain/tutorials/call-citrate-rpc), so anything the tool does you could also do by\nhand. For the wider command set, see the [CLI reference](/chain/cli).\n\n## How to use it\n\nYou will need a clone of `citrate-chain` with a Rust toolchain, a reachable JSON-RPC endpoint (a local node\non `http://localhost:8545`, or a testnet endpoint), and a compiled contract artifact. This tutorial assumes a\nhex bytecode file named `MyToken.bin`; produce one with your usual compiler, for example `solc --bin`.\n\n### Step 1, build the tool\n\n```bash\ncargo build --release -p citrate-cli\n# binary at target/release/citrate\nexport PATH=\"$PWD/target/release:$PATH\"\n```\n\n### Step 2, initialize the config\n\n```bash\ncitrate init\n```\n\nThe defaults, from `cli/src/config.rs`, are RPC `http://localhost:8545`, chain id 40204, keystore\n`~/.citrate/keystore`, gas price 1 gwei, and gas limit 3,000,000. Override the endpoint for any command with\nthe global `--rpc ` flag or the `CITRATE_RPC` environment variable.\n\n### Step 3, confirm the chain id\n\nBefore you write anything, confirm the endpoint is Citrate testnet. The tool stores chain id 40204 by\ndefault; check the live endpoint agrees with the same RPC call as the other tutorials:\n\n```bash\ncurl -s http://localhost:8545 -H 'content-type: application/json' \\\n -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_chainId\",\"params\":[]}'\n# {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x9d0c\"}\nprintf '%d\\n' 0x9d0c # 40204\n```\n\n`0x9d0c` is 40204. Anything else means you are about to deploy to a different network.\n\n### Step 4, prepare an account and the artifact\n\nCreate a deployer account and note the address it prints:\n\n```bash\ncitrate account create\n# Enter a keystore password when prompted.\n# Prints: Address: 0x...\n\ncitrate account list\n```\n\nThe deploy transaction spends gas, so the account needs a little SALT. On a network with a faucet, fund the\naddress through that network's faucet, then confirm the balance:\n\n```bash\ncitrate account balance 0xYOUR_ADDRESS\n```\n\nThe faucet is a network service, not a `citrate` subcommand, so its exact address and request shape vary by\ndeployment; check the endpoint operator's notes. The artifact itself is the `MyToken.bin` hex file from your\ncompiler. A file that does not end in `.wasm` is read as hex bytecode; a `.wasm` file is read as raw bytes.\n\n### Step 5, deploy the contract\n\n```bash\ncitrate contract deploy ./MyToken.bin \\\n --account 0xYOUR_ADDRESS \\\n --value 0\n```\n\nThe tool submits `eth_sendTransaction`, waits for the receipt, and prints the new contract address. Save it:\n\n```bash\nexport CONTRACT=0xDEPLOYED_CONTRACT_ADDRESS\n```\n\nConstructor arguments, if your contract takes any, go in `--args ''`. The tool's encoder is a\nstatic-types subset: it handles `address`, `bool`, `bytes32`, and `uint8` through `uint256`. Dynamic `string`\nand `bytes` arguments are not yet supported (`encode_method_call` in `cli/src/commands/contract.rs`).\n\n### Step 6, verify the deployment with an eth_ call\n\nConfirm the code actually landed at the address. `contract code` wraps `eth_getCode`, which is read-only and\nneeds no account:\n\n```bash\ncitrate contract code \"$CONTRACT\"\n# Contract code retrieved\n# Size: ... bytes\n```\n\nYou can confirm the same thing directly over RPC, which is the call the tool makes:\n\n```bash\ncurl -s http://localhost:8545 -H 'content-type: application/json' \\\n -d \"{\\\"jsonrpc\\\":\\\"2.0\\\",\\\"id\\\":1,\\\"method\\\":\\\"eth_getCode\\\",\\\"params\\\":[\\\"$CONTRACT\\\",\\\"latest\\\"]}\"\n# a non-\"0x\" result means code is present\n```\n\nTo inspect the deploy transaction itself, fetch its receipt. The tool polls `eth_getTransactionReceipt`\ninternally while it waits; you can read the same receipt with `citrate network transaction 0xTX_HASH`.\n\n### Step 7, read and call the contract\n\n`contract read` uses `eth_call`, so it costs nothing and needs no account:\n\n```bash\ncitrate contract read \"$CONTRACT\" \"totalSupply()\"\n\ncitrate contract read \"$CONTRACT\" \"balanceOf(address)\" \\\n --args '[\"0xYOUR_ADDRESS\"]'\n```\n\n`contract call` sends a state-changing transaction, waits for the receipt, and reports gas used and any\nemitted event topics:\n\n```bash\ncitrate contract call \"$CONTRACT\" \"transfer(address,uint256)\" \\\n --args '[\"0x1111111111111111111111111111111111111111\", \"1000\"]' \\\n --account 0xYOUR_ADDRESS\n```\n\n### Step 8, pick your next page\n\n| If you want to | Go to |\n|---|---|\n| The full CLI command set | [CLI reference](/chain/cli) |\n| The RPC the tool speaks underneath | [call the Citrate RPC](/chain/tutorials/call-citrate-rpc) |\n| Every RPC method, with params and shapes | [JSON-RPC reference](/chain/rpc) |\n| The consensus your transaction commits to | [consensus, GhostDAG](/chain/consensus) |\n\n## Reference\n\nThe commands used above, with their source in `citrate-chain`:\n\n| Command | What it does | Source |\n|---|---|---|\n| `citrate init` | write the default config and keystore directory | `cli/src/config.rs` |\n| `citrate account create` / `list` / `balance` | manage and read deployer accounts | `cli/src/commands/account.rs` |\n| `citrate contract deploy --account --value --args` | submit `eth_sendTransaction`, await receipt, print address | `cli/src/commands/contract.rs` |\n| `citrate contract code
` | read code via `eth_getCode` | `cli/src/commands/contract.rs` |\n| `citrate contract read
--args` | read state via `eth_call` | `cli/src/commands/contract.rs` |\n| `citrate contract call
--account --args` | send a state-changing transaction | `cli/src/commands/contract.rs` |\n| `citrate network transaction ` | read a transaction receipt | `cli/src/commands/network.rs` |\n\nThe global `--rpc ` flag and the `CITRATE_RPC` environment variable override the endpoint for any\ncommand (`cli/src/main.rs`). Deploy and call both poll `eth_getTransactionReceipt` while they wait\n(`wait_for_receipt` in `cli/src/commands/contract.rs`).\n\n## Failure modes\n\n- **`No account specified and no default account configured`** means you passed no `--account` and set no\n default. Pass `--account 0x...`.\n- **`Transaction receipt not found after 60 seconds`** means the deploy or call transaction did not confirm\n in the poll window. Check the node is producing blocks and the account has SALT for gas.\n- **`Contract deployment failed`** with no contract address means the transaction reverted or ran out of gas;\n the default gas limit is 3,000,000, raised through the config.\n- **`dynamic types (string, bytes) are not supported`** comes from the CLI encoder. Use a contract method\n whose arguments are the supported static types, or encode the call data yourself and deploy raw.\n- **`Connection refused`** means no node is listening on the endpoint. Point `--rpc` at a reachable one.\n\n## Access and canon\n\nPublic. The read steps (`contract code`, `contract read`, `eth_getCode`) write nothing and need no account.\nDeploying and calling write state and spend gas, so they need an account with SALT. Chain id 40204 is\ntestnet; confirm the live endpoint reports `0x9d0c` before you deploy anything you care about. Keys live in\nthe local keystore at `~/.citrate/keystore`; the Citrate Keyring, not a hosted service, holds them.\n\n## Source and verification\n\nCommands verified against `citrate-chain` at `9d5959e`. Contract operations are in\n`cli/src/commands/contract.rs`, with the subcommands `deploy`, `call`, `read`, `code`, `verify`, `verify-get`,\nand `verify-list`. Config defaults are in `cli/src/config.rs`, the global `--rpc` flag and the top-level\nsubcommands are in `cli/src/main.rs`, account operations are in `cli/src/commands/account.rs`, and the\ntransaction lookup is in `cli/src/commands/network.rs`. The faucet is a network service and has no `citrate`\nsubcommand, so its address and request shape are described generically above. Status: Implemented (testnet\n40204), pre external audit. The CLI ABI encoder is a static-types subset.\n"},"/chain/tutorials/read-the-dag":{"slug":"/chain/tutorials/read-the-dag","title":"Read the DAG","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain core/api","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Step 1, read the whole DAG in one call","anchor":"step-1-read-the-whole-dag-in-one-call"},{"depth":3,"text":"Step 2, blue score versus height","anchor":"step-2-blue-score-versus-height"},{"depth":3,"text":"Step 3, list the tips, then fetch one","anchor":"step-3-list-the-tips-then-fetch-one"},{"depth":3,"text":"Step 4, pick your next page","anchor":"step-4-pick-your-next-page"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"A copy-paste walkthrough that lets you see the BlockDAG instead of reading about it. You will read the whole\nDAG in one call, list the current tips, fetch a single tip block, and learn why blue score, not height, is\nthe clock that orders the ledger. Every method here is read-only, so it needs no account and no SALT.\n\n## What it is\n\nA Citrate ledger is a DAG with more than one leaf, not a single chain. GhostDAG (k = 18, finality depth 100)\ntakes those leaves and merges them into one agreed order, and the number it counts by is the blue score. This\ntutorial reads that state live. If the words are new, read the [primer](/start/primer) first; for the\nprotocol behind the numbers, read [consensus, GhostDAG](/chain/consensus).\n\n## How to use it\n\nYou will need a reachable Citrate JSON-RPC endpoint. A local node serves `http://127.0.0.1:8545`. If you do\nnot have one, use the [RPC sandbox](/sandboxes/rpc) instead. You will also want `curl`, and `jq` for readable\noutput.\n\nSet up the same helper used in [call the Citrate RPC](/chain/tutorials/call-citrate-rpc):\n\n```bash\nexport RPC=http://127.0.0.1:8545\n\nrpc () {\n curl -s \"$RPC\" -H 'content-type: application/json' \\\n -d \"{\\\"jsonrpc\\\":\\\"2.0\\\",\\\"id\\\":1,\\\"method\\\":\\\"$1\\\",\\\"params\\\":${2:-[]}}\"\n}\n```\n\n### Step 1, read the whole DAG in one call\n\n`citrate_getDagStats` returns tips, height, the head's blue score, and the network's GhostDAG parameters in a\nsingle response:\n\n```bash\nrpc citrate_getDagStats | jq\n```\n\n```json\n{\n \"totalBlocks\": 12345,\n \"blueBlocks\": 11727,\n \"redBlocks\": 618,\n \"tipsCount\": 3,\n \"maxBlueScore\": 11800,\n \"currentTips\": [\"0x...\", \"0x...\", \"0x...\"],\n \"height\": 12345,\n \"ghostdagParams\": { \"k\": 18, \"maxParents\": 10, \"maxBlueScoreDiff\": 1000, \"pruningWindow\": 100000, \"finalityDepth\": 100 }\n}\n```\n\nWhat you are looking at:\n\n- `currentTips` and `tipsCount`, the DAG's current leaf blocks. More than one tip at once is normal on a\n BlockDAG; GhostDAG orders them deterministically.\n- `maxBlueScore`, the blue score of the highest tip, the head the network builds on. This is the ordering\n clock, not `height`.\n- `ghostdagParams`, the live consensus constants: `k` is 18, `maxParents` is 10, `finalityDepth` is 100. These\n come from `GhostDagParams::default()` in `core/consensus/src/types.rs`.\n\nOne honesty note: `currentTips`, `maxBlueScore`, `height`, and `ghostdagParams` are read from chain state,\nwhile `blueBlocks` and `redBlocks` are estimated from height (the handler says so in a comment). Treat the\nblue-and-red split as indicative, not exact.\n\n### Step 2, blue score versus height\n\nHeight counts how deep a block sits along the selected chain. Blue score counts how many blue (well-connected,\nhonest-looking) blocks precede a block across the whole DAG. Tip selection follows the highest blue score, so\nwhen you ask which tip the network is building on, `maxBlueScore` is the answer, and the block with the\nhighest blue score is the head. Two tips can sit at the same height yet have different blue scores; the one\nwith the larger blue score wins. That is why we call blue score the ordering clock.\n\n### Step 3, list the tips, then fetch one\n\nGet the tips on their own:\n\n```bash\nrpc chain_getTips | jq\n# [\"0x...\", \"0x...\", \"0x...\"]\n```\n\n`chain_getTips` returns an array of tip hashes. Take the first and fetch its block by hash with\n`chain_getBlock`, whose parameter is a `BlockId` (here the `Hash` form):\n\n```bash\nTIP=$(rpc chain_getTips | jq -r '.result[0]')\n\nrpc chain_getBlock \"[{\\\"Hash\\\":\\\"$TIP\\\"}]\" | jq\n```\n\nThe block's header carries `blue_score`, the same value GhostDAG uses for tip selection, alongside\n`selected_parent_hash` and `merge_parent_hashes`. Those merge parents are the other tips this block absorbed,\nthe act of merging that makes a DAG a DAG rather than a chain. You can also read the height alone:\n\n```bash\nrpc chain_getHeight\n# {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":12345}\n```\n\n### Step 4, pick your next page\n\n| If you want to | Go to |\n|---|---|\n| The protocol behind these numbers | [consensus, GhostDAG](/chain/consensus) |\n| Every RPC method, with params and shapes | [JSON-RPC reference](/chain/rpc) |\n| The request envelope and error codes | [call the Citrate RPC](/chain/tutorials/call-citrate-rpc) |\n| The words: tips, blue score, merge | [the primer](/start/primer) |\n\n## Reference\n\nThe methods used above, with their source files in `citrate-chain`:\n\n| Method | What it returns | Source |\n|---|---|---|\n| `citrate_getDagStats` | tips, blue score, GhostDAG params | `core/api/src/eth_rpc.rs` |\n| `chain_getTips` | an array of tip hashes | `core/api/src/server.rs` |\n| `chain_getBlock` | one block, by `BlockId` (`{\"Hash\":\"0x...\"}`) | `core/api/src/server.rs` |\n| `chain_getHeight` | the current height as a number | `core/api/src/server.rs` |\n\nGhostDAG constants (`k = 18`, `maxParents = 10`, `finalityDepth = 100`) come from `GhostDagParams::default()`\nin `core/consensus/src/types.rs`.\n\n## Failure modes\n\n- **`Connection refused`** means no node is listening on `$RPC` (the default is `127.0.0.1:8545`). Use the\n [RPC sandbox](/sandboxes/rpc) instead.\n- **`chain_getBlock` returns `null`** when no block matches the `BlockId`. Check the hash, and that the\n `params` shape is `[{\"Hash\":\"0x...\"}]` and not a bare string.\n- **`-32602 Invalid params`** means the `BlockId` shape was wrong. The form above is the one the handler\n parses.\n- **A blue-and-red split that looks off** is expected: those two fields are estimated from height, per the\n note in Step 1.\n\n## Access and canon\n\nPublic and read-only. No keys, no write methods, no private endpoints, and nothing here writes state. Chain id\n40204 is testnet, and `http://127.0.0.1:8545` is the conventional local address, not a live network endpoint.\n\n## Source and verification\n\nMethods verified against `citrate-chain` at `9d5959e`: `citrate_getDagStats` in `core/api/src/eth_rpc.rs`,\nand `chain_getTips`, `chain_getBlock`, and `chain_getHeight` in `core/api/src/server.rs`. The GhostDAG\nconstants are `GhostDagParams::default()` in `core/consensus/src/types.rs`. Status: Implemented (testnet\n40204), pre external audit. The `blueBlocks` and `redBlocks` fields of `citrate_getDagStats` are estimates,\nas flagged above.\n"},"/compute/agent-runtime":{"slug":"/compute/agent-runtime","title":"Citrate Agent Runtime","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-agent-runtime (agent/cli/, agent/core/, agent-cron/, agent-chain/)","syncedSha":"f161e69","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"The diagnostic, `citrate-agent doctor`","anchor":"the-diagnostic-citrate-agent-doctor"},{"depth":3,"text":"The recorder, `RecorderClient`","anchor":"the-recorder-recorderclient"},{"depth":3,"text":"The approval gate, `ApprovalQueue`","anchor":"the-approval-gate-approvalqueue"},{"depth":3,"text":"The cron daemon and tripwires","anchor":"the-cron-daemon-and-tripwires"},{"depth":3,"text":"The capsule loader","anchor":"the-capsule-loader"},{"depth":3,"text":"Environment variables","anchor":"environment-variables"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The agent runtime is how an agent runs on an operator's own machine without acting unsupervised.\nIt records every action in a tamper-evident log, holds risky tool calls behind a human approval\ngate, and ships a diagnostic that refuses to clear a node when those safeguards are missing. It\nis for researchers and operators studying how agents can act on the network while staying\naccountable.\n\n## What it is\n\nThe safety posture is the whole point, so it comes first. An agent here does not run free. Three\nthings hold at once. Every action it takes is written to an append-only audit chain, where each\nrecord carries the hash of the one before it, so a deleted or altered record breaks the chain\nand shows. Every tool call the agent wants to make passes through an approval queue, and the\ncalls that matter wait for a human, or a quorum of humans, to sign off before they run. And the\nwhole thing runs on the operator's own hardware, the on-premise default that holds across the\nnetwork; the only writes that reach the public ledger are the ones the runtime is told to anchor.\n\nThe runtime is several crates in the `citrate-agent-runtime` workspace. The pieces that carry\nthe safety story are these.\n\n- A diagnostic, run as `citrate-agent doctor`, that checks the safeguards are intact and emits a\n signed report.\n- A recorder, `RecorderClient`, the single surface that signs and writes to the chain. Nothing\n else holds the key.\n- An approval queue, `ApprovalQueue`, the HIC (Human In Control) gate that risky tool calls pass\n through.\n- A cron daemon that runs recurring checks, the tripwires and standing procedures, on a\n schedule, and records what they find.\n- A capsule loader that verifies an agent's signed package against its manifest before it runs,\n and grants it only the capabilities the manifest declares.\n\n## How to use it\n\n1. Configure the chain dependency before you build. The runtime pulls a chain crate over an SSH\n host alias, `github-citrate-chain`, that must resolve in your `~/.ssh/config`. The simplest\n path is to run `citrate-federation/scripts/bootstrap.sh`, which sets the alias up for you. A\n fresh machine without it fails the build with `Could not resolve hostname\n github-citrate-chain`.\n2. Build the workspace. `cargo build --release`, then `cargo run --release --bin\n citrate-agent -- --help` to confirm the CLI. The package is `citrate-agent-cli`; the binary it\n produces is named `citrate-agent`.\n3. Write a `doctor.toml` naming the agent and pointing at the audit log and the policy files you\n want checked.\n4. Run the diagnostic against a node before you trust it. `citrate-agent doctor` exits 0 on pass\n or warn, 1 on a blocker, and 2 on a configuration or IO error before it could run.\n5. For the recorder and the cron daemon, supply the signing key from your own secret store\n through the environment. The key is never a value on this page or in the tree.\n\n```bash\ncitrate-agent doctor --config doctor.toml --output report.toml\n```\n\n```toml\n# doctor.toml, minimal\n[doctor]\nagent_did = \"did:citrate:agent:0x...\"\n```\n\n## Reference\n\n### The diagnostic, `citrate-agent doctor`\n\nThe command lives in `agent/cli/src/doctor_cmd.rs`; its config schema is in\n`agent/cli/src/config.rs`. It runs eleven checks defined in `agent/core/src/doctor/checks.rs`,\neach returning Pass, Skipped, Warn, or Blocker (`agent/core/src/doctor/report.rs`); a skipped\nrequired check holds the overall result to at least Warn rather than a clean Pass. With a seed\nfile it signs the report. The flags are `--config`, `--output`, `--check ` to run a subset, and\n`--seed` for the signing key.\n\n| Check | Behavior |\n|---|---|\n| `audit-chain-integrity` | Walks the audit hash-chain. Blocker if a record was altered or deleted. |\n| `audit-file-permissions` | Warn if the audit file is not mode 0600 on Unix. |\n| `approval-queue-depth` | Warn when pending approvals pass a threshold, default 100. |\n| `pending-break-glass` | Blocker if a break-glass case is surfaced, Warn if it is unaffirmed. |\n| `runtime-presence` | Warn if no async runtime is present. |\n| `capsule-manifest-reverify` | Blocker if a capsule fails to load, Warn on a bad signature. |\n| `wasm-linker-recheck` | Blocker on a linker or interface mismatch. |\n| `policy-bundle-hash` | Blocker if a policy file's SHA-256 has drifted. |\n| `tla-spec-ci-status` | Blocker on failing specs, Warn if the status is stale past the window. |\n| `retention-age` | Warn if the audit file is older than the retention maximum, default 90 days. |\n| `anchor-reconciliation` | Reports on-chain roots that have not been anchored. |\n\n### The recorder, `RecorderClient`\n\nIn `agent/core/src/audit/recorder.rs`. It is the only surface that signs on-chain writes: it\nowns a secp256k1 key, an RPC client, and the chain id 40204. It loads its key with `from_env`,\nwhich reads `DEPLOYER_PRIVATE_KEY`, or, when that is unset, a key file named by\n`CITRATE_RECORDER_KEY_ENV_FILE`; that path must be absolute and pass a mode-0600 check. The\nearlier gitignored `.env.testnet` fallback was removed (AR-B-010). Its writes go through\n`send_tx` and `wait_for_receipt`. Every approve or reject the runtime makes is written\nas a decision record to the on-chain `AgentDecisionRegistryV2`. The audit chain itself is\n`AuditChain` in `agent/core/src/audit/chain.rs`, which mints a genesis record, appends each new\nrecord with a contiguity check against the previous hash, and can walk the whole chain to verify\nits integrity. Records are written through an `AuditSink`; the filesystem sink and the\nchain-anchor sink are present, the object-store and write-once sinks are not yet shipped.\n\n### The approval gate, `ApprovalQueue`\n\nIn `agent/core/src/hitl/mod.rs`. Tool calls enter the queue and wait. Low-risk calls can be\nauto-granted on a fast path with a time-to-live, default 30 minutes; a pending call that no one\nanswers times out, default 5 minutes. The risk tier of a call decides how many signatures it\nneeds (`agent/core/src/hitl/quorum.rs`): low auto-approves, medium needs one signer from the\nrequired set, high needs two, and critical needs a fixed set of officer roles. Separation of\nduties is enforced in `agent/core/src/hitl/roles.rs`: the Auditor role can never approve, the\nCompliance Officer and Security Officer roles cannot both stand for the same approval, and no\nsigner counts twice. Signatures are verified against a canonical payload and a known signer\nroster (`agent/core/src/hitl/signing.rs`); a production build fails closed if no roster is set.\n\nThere is a break-glass path in `agent/core/src/hitl/break_glass.rs` for emergencies, and it is\nbuilt to be hard to abuse: an invocation notifies all roles, must be affirmed by a quorum within\na 72-hour window, and is blocked outright for ITAR-classed actions. Its state machine and the\naudit chain's integrity property are both checked in TLA+.\n\n### The cron daemon and tripwires\n\nIn `agent-cron/`. A `CronScheduler` (`agent-cron/src/scheduler.rs`) runs recurring jobs on cron\nschedules, each carrying a snapshot of the capabilities it was granted. A standing-procedure\nengine (`agent-cron/src/sop.rs`) runs multi-step procedures on a trigger. The tripwire daemon\n(`agent-cron/src/bin/tripwire_daemon.rs`) runs a set of compliance tripwires that read metrics\nfrom Prometheus and events from the chain, and fire through the recorder when a threshold is\ncrossed. Its environment is below.\n\n### The capsule loader\n\nIn `agent/core/src/capsule/`. An agent ships as a capsule: a signed archive with a manifest. The\nloader (`mod.rs`) verifies the content hash, checks the publisher's signature against a key\nregistry keyed by signing tier (`tiers.rs`), cross-checks the declared capabilities against the\ncomponent's interface (`verify.rs`), and builds a linker that exposes only the capabilities the\nmanifest declares, failing closed when an undeclared import is needed. The manifest\n(`manifest.rs`) declares the capsule's data classes, its risk tier, the roles required to\napprove it, and whether it is break-glass eligible.\n\n### Environment variables\n\n| Variable | Default | Required | Purpose |\n|---|---|---|---|\n| `DEPLOYER_PRIVATE_KEY` | none | tripwire daemon: yes | secp256k1 key for `RecorderClient`. Never commit it. |\n| `CITRATE_RECORDER_KEY_ENV_FILE` | none | no | Absolute path to a mode-0600 key file, read when `DEPLOYER_PRIVATE_KEY` is unset. |\n| `CITRATE_TRIPWIRE_PROM_URL` | `http://127.0.0.1:9090` | no | Prometheus base for tripwire metrics. |\n| `CITRATE_TRIPWIRE_RPC_URL` | `https://rpc.citrate.ai` | no | JSON-RPC for chain queries. |\n| `CITRATE_TRIPWIRE_REGISTRY` and the `_TENANT`, `_ROLE_ESCALATION`, `_MULTISIG` addresses | contract defaults | no | Tripwire contract addresses. |\n| `CITRATE_TRIPWIRE_SCOPE` | `keccak256(\"defense_prime-root\")` | no | bytes32 scope. |\n| `CITRATE_CAPSULE_SIGNING_SEED` | none | no | ed25519 seed for capsule packing. Never commit it. |\n\n## Design rationale\n\nThe runtime puts the key in exactly one place on purpose. `RecorderClient` is the only thing\nthat can sign an on-chain write, so the audit chain and the approval gate cannot be bypassed by\nsome other code path signing its own transaction; if it went to the chain, it went through the\nrecorder, and it is in the log. The audit log is a hash-chain rather than a plain file so that\ntampering is detectable rather than silent, and the diagnostic treats a broken chain as a\nblocker, not a warning. The approval gate scales the number of human signatures to the risk of\nthe call rather than asking for approval on everything, which is what keeps the gate usable\ninstead of ignored. The cost of all this is that the agent is slower and more bounded than one\nthat simply acts; for actions that change state on a shared network, that is the trade we want.\n\n## Failure modes\n\nThe runtime is built to fail closed. A production build with no signer roster will not start the\nrole-aware approval path; it refuses rather than approving on trust. An undeclared capability in\na capsule fails instantiation rather than being granted quietly. A break-glass invocation for an\nITAR-classed action is blocked outright, and any break-glass case that is surfaced turns the\ndiagnostic into a blocker. The recorder's key custody is honest about its limits: loading the\nkey from an environment variable is pilot-grade, fit for testnet and controlled pilots, and is\nthe part most in need of hardening before a stable release. If you ever find a real key in the\ntree, flag it; do not transcribe it.\n\n## Access and canon\n\nTier academic: this surface is oriented to research and formal methods, the TLA+ checks on the\naudit chain and the break-glass machine, capsule capability verification, and the diagnostic\nitself. The runtime is classified for a full external audit before any v1.0.0 tag\n(`AUDIT_TIER.md`); there is no stable release without a written attestation against an exact\ncommit. Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity. No\nsecrets appear here: `DEPLOYER_PRIVATE_KEY` and `CITRATE_CAPSULE_SIGNING_SEED` are named only as\nvariables to set, and any key file named by `CITRATE_RECORDER_KEY_ENV_FILE` must be an absolute\npath at mode 0600, held in your own secret store.\n\nThis page connects to [run a node](/operators/run-a-node) for the operator who hosts the\nruntime, to [research](/research/learning) for the agent-safety work behind it, and to the\n[governance contracts](/contracts/governance), where the recorder writes its decisions to the\n`AgentDecisionRegistryV2`.\n\n## Source and verification\n\n- Source repo: `citrate-agent-runtime`.\n- Files: `agent/cli/src/doctor_cmd.rs`, `agent/cli/src/config.rs`,\n `agent/core/src/doctor/{checks.rs,report.rs}`, `agent/core/src/audit/{recorder.rs,chain.rs}`,\n `agent/core/src/hitl/{mod.rs,quorum.rs,roles.rs,signing.rs,break_glass.rs}`,\n `agent/core/src/capsule/{mod.rs,manifest.rs,tiers.rs,verify.rs}`,\n `agent-cron/src/{scheduler.rs,sop.rs,bin/tripwire_daemon.rs}`, `AUDIT_TIER.md`.\n- Audited against SHA: `f161e69`.\n- Status by component:\n - `doctor` and its eleven checks, `RecorderClient`, `AuditChain`, `ApprovalQueue`, the quorum\n and role rules, break-glass, the cron scheduler, the standing-procedure engine, the tripwire\n daemon, and the capsule loader: Implemented.\n - The audit-chain integrity property, the approval state machine, and the break-glass state\n machine: Verified in TLA+.\n - `RecorderClient` key custody (environment-loaded key, no nonce cache): Implemented but\n pilot-grade, flagged for hardening.\n - Object-store and write-once audit sinks, and hardware-backed signing surfaces: Specified,\n not yet shipped.\n - Full external audit before v1.0.0: required, not yet performed; pre-stable (v0.x).\n"},"/compute/gateway":{"slug":"/compute/gateway","title":"Deploy the inference gateway","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-inference-gateway/gateway/src/, citrate-inference-gateway/crates/x402-axum/src/, citrate-inference-gateway/gateway/RUNBOOK.md","syncedSha":"603fe92","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"Run modes and where they live","anchor":"run-modes-and-where-they-live"},{"depth":3,"text":"Routes the operator runs","anchor":"routes-the-operator-runs"},{"depth":3,"text":"Configuration","anchor":"configuration"},{"depth":3,"text":"The x402 settlement path, from the operator's side","anchor":"the-x402-settlement-path-from-the-operators-side"},{"depth":3,"text":"Pieces that are designed but not complete at this SHA","anchor":"pieces-that-are-designed-but-not-complete-at-this-sha"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This is the operator's side of the Citrate Inference Gateway: how you deploy it on your own hardware, how\nyou configure it, and how a paid request settles. If you are an application developer who wants to call the\ngateway rather than run one, read the [client reference](/sdks/inference-gateway) instead; this page does\nnot repeat the request and response shapes that live there.\n\n> **Status: paid routes are not deployed.** The released gateway binary starts with `build_router`, which\n> serves only the free read routes. The x402-paid and API-key routes below are mounted by\n> `build_router_with_auth`, which today is used only by tests, so no public gateway settles paid calls yet.\n> This page documents the design and the code so operators can review it; it is not a live service.\n\n## What it is\n\nThe gateway is an OpenAI-compatible HTTP service that fronts Citrate Market on the Citrate Network, chain\nid 40204. An operator runs it on their own machine, in front of either the marketplace or a resident\ninference server, and it turns an OpenAI-shaped request into a metered, paid job. The work it performs is\nsettled in SALT per request over the x402 handshake; SALT settles the work, it is not something the gateway\nholds for you.\n\nIt runs in one of two modes, chosen at start by `CITRATE_GATEWAY_MODE`.\n\n- **Marketplace mode**, the default. The gateway reads the on-chain `ModelRegistry`, `ComputePricingOracle`,\n and `InferenceRouter`, selects a provider for the requested model, and gates the paid routes behind x402.\n This is the mode an operator selling into the marketplace runs.\n- **Local-proxy mode.** A leaner build that fronts a resident inference server on a single machine, for\n example a llama-server behind your own reverse proxy. It is gated by a `cgk_` API key rather than x402 and\n makes no chain calls. It exists so a single on-premise node can serve the same OpenAI surface without\n joining the marketplace.\n\nThe gateway is the thin layer between an OpenAI request and the marketplace. It holds no model weights and\nruns no inference itself; the providers it dispatches to are the [operators selling compute](/compute/node-agent),\nand the price it quotes comes from the [x402 pricing contracts](/contracts/x402).\n\n## How to use it\n\nThe deployment shape is the same in both modes: build the binary, set the environment, bind to loopback,\nand front it with your own TLS terminator. The difference is which variables you set.\n\n1. Build the gateway from the `citrate-inference-gateway` workspace, then decide the mode. Marketplace mode\n needs an RPC endpoint and the three contract addresses; local-proxy mode needs an upstream URL.\n2. Set the listen address. The default is loopback, `127.0.0.1:9800`. Do not bind to `0.0.0.0` on a host\n reachable from the public network; terminate TLS and apply rate limits at your own reverse proxy and let\n that proxy reach the loopback port.\n3. In marketplace mode, provision the operator signer. Settlement is a real on-chain transaction, so the\n gateway needs an account to send it from. That material is loaded from a keystore file or a KMS key\n reference, never from a value in your config; see the signer family below.\n4. Start the gateway and confirm liveness with `GET /health`, then point Prometheus at `GET /metrics`.\n5. Confirm the read path. `GET /v1/models` is free and reads the on-chain `ModelRegistry`; if it returns\n your expected models, the chain reads are wired. Once the paid router is wired, a paid `POST /v1/chat/completions`\n without a payment header returns an HTTP `402` challenge. With the current binary the paid routes are not\n mounted: expect a `404`, unless you enabled the development-only open chat mode below, which serves chat\n and batch without any payment gate.\n\n## Reference\n\n### Run modes and where they live\n\nThe mode is read and dispatched at start in `gateway/src/main.rs` (the `CITRATE_GATEWAY_MODE` variable is\nread there); the configuration defaults live in `gateway/src/config.rs`. The full operator reference is\n`gateway/RUNBOOK.md` in the repository.\n\n| Mode | Value | Reads chain | Gating | Use |\n|---|---|---|---|---|\n| Marketplace | `marketplace` (default) | yes | x402 per request | selling into Citrate Market |\n| Local-proxy | `local-proxy` | no | `cgk_` API key | a single on-premise inference server |\n\n### Routes the operator runs\n\nHandlers live under `gateway/src/`. The request and response bodies are documented on the\n[client page](/sdks/inference-gateway); this table is the operator's view of what is exposed and how each\nroute is gated.\n\nThe paid routes and `/v1/usage` are mounted by the router that receives the operator signer and the x402\nsettlement configuration, `build_router_with_auth` in `gateway/src/lib.rs`, which is the intended\nmarketplace path. The gateway binary (`gateway/src/main.rs`) does not call it yet; it calls `build_router`. The default `build_router` without that configuration serves only the free read routes,\n`/health`, `/v1/models`, and `/metrics`. Only when both `CITRATE_GATEWAY_OPEN_CHAT` and\n`CITRATE_GATEWAY_DEV_MODE` are set does it also mount `/v1/chat/completions`, `/v1/batch`, `/v1/batch/{id}` and\n`/v1/batch/{id}/output`, with no x402 gate. That mode is for development only.\n\n| Route | Method | Handler | Gating |\n|---|---|---|---|\n| `/v1/chat/completions` | POST | `gateway/src/chat.rs` | x402, paid (not mounted by the released binary) |\n| `/v1/batch` | POST | `gateway/src/batch.rs` | x402, paid (not mounted by the released binary) |\n| `/v1/batch/{id}`, `/v1/batch/{id}/output` | GET | `gateway/src/batch.rs` | submitter-bound reads; mounted only with the paid router or in development open chat mode |\n| `/v1/models` | GET | `gateway/src/models.rs` | free |\n| `/v1/usage` | GET | `gateway/src/usage.rs` | API-key bearer (not mounted by the released binary) |\n| `/health` | GET | `gateway/src/health.rs` | free liveness |\n| `/metrics` | GET | `gateway/src/metrics.rs` | bearer token, disabled when unset |\n\n### Configuration\n\nThe gateway reads its configuration from environment variables, verified in `gateway/src/config.rs`,\n`gateway/src/main.rs`, and the signer, provider, metrics, and key-vault modules. Names and purposes follow;\nno values are shown, and account material is never set inline. Contract addresses are published on the\n[chain address book](/chain/addresses); do not transcribe them here.\n\n```bash\n# Mode and chain wiring\nCITRATE_GATEWAY_MODE=marketplace # or local-proxy; read in gateway/src/main.rs\nCITRATE_GATEWAY_CHAIN_ID=40204 # default 40204\nCITRATE_GATEWAY_RPC_URL=... # chain JSON-RPC endpoint, default http://127.0.0.1:8545\nCITRATE_GATEWAY_LISTEN_ADDR=127.0.0.1:9800 # default loopback; 0.0.0.0 only behind a proxy\n\n# Marketplace-mode contract addresses (see /chain/addresses)\nCITRATE_GATEWAY_MODEL_REGISTRY=0x...\nCITRATE_GATEWAY_PRICING_ORACLE=0x...\nCITRATE_GATEWAY_INFERENCE_ROUTER=0x...\n\n# Operator signer: keystore file or KMS reference, plus spend caps. Never an inline secret.\nCITRATE_GATEWAY_KEYSTORE_PATH=... # durable store, required in production\nCITRATE_GATEWAY_KMS_KEY_ID=... # KMS key reference, an alternative to the keystore\nCITRATE_GATEWAY_OPERATOR_KEYSTORE_PASSWORD=... # keystore passphrase; or _PASSWORD_FILE\nCITRATE_GATEWAY_OPERATOR_SPEND_CAP_WEI=... # per-epoch settlement spend cap\nCITRATE_GATEWAY_OPERATOR_EPOCH_BLOCKS=... # spend-cap epoch length in blocks\nCITRATE_GATEWAY_ALLOW_LOCAL_SIGNER=... # development only, permits an in-process signer\n\n# At-rest money-store master key (note: not CITRATE_GATEWAY_-prefixed)\nGATEWAY_STORE_KEY=... # or GATEWAY_STORE_KEY_FILE\n\n# Provider dispatch controls\nCITRATE_GATEWAY_REQUIRE_SIGNED_RESULTS=... # require providers to sign their results\nCITRATE_GATEWAY_ALLOW_PRIVATE_PROVIDER_ENDPOINTS=... # development only, permits private provider URLs\n\n# Request ceiling, metrics, dev gates, observability\nCITRATE_GATEWAY_MAX_TOKENS=8192 # ceiling; per-request default is 512\nCITRATE_GATEWAY_METRICS_TOKEN=... # bearer token for /metrics; the route is disabled when unset\nCITRATE_GATEWAY_DEV_MODE=... # development only\nCITRATE_GATEWAY_OPEN_CHAT=... # development only, refuses non-loopback binds\nLOG_FORMAT=... # default pretty\nRUST_LOG=...\n\n# Local-proxy mode upstream\nCITRATE_GATEWAY_UPSTREAM_URL=... # resident inference server, default http://127.0.0.1:8181\n```\n\n### The x402 settlement path, from the operator's side\n\nPaid routes sit behind the `X402Layer` middleware in `crates/x402-axum/src/layer.rs`. The handshake is\nimplemented end to end, and settlement is a real on-chain transaction, not a mock. From the operator's view,\na paid request moves through these steps.\n\n1. A request arrives without an `x-payment` header. The gateway mints a single-use nonce and returns HTTP\n `402` with a payment challenge bound to the configured treasury, the wSALT token, chain id 40204, an\n amount in wei, and a limited validity window.\n2. The client signs the EIP-712 `transferWithAuthorization` digest and resends with the `x-payment` header.\n3. The gateway verifies the signature and the nonce, confirms the recipient binds to your treasury, and\n calls `X402Facilitator.settlePayment` on chain. Settlement is a single transaction, not a retried one.\n4. After the receipt confirms, the gateway runs the handler and dispatches to a selected provider, retrying\n up to `MAX_PROVIDER_ATTEMPTS` (3) providers before it gives up and returns a `503`. When\n `CITRATE_GATEWAY_REQUIRE_SIGNED_RESULTS` is set, it also verifies the provider's signed result. It then\n returns the OpenAI-shaped response. The provider-retry count and the signed-result check are separate from\n the single settlement transaction in step 3.\n\nThe client codec for this handshake lives in the [Marketplace SDK](/sdks/marketplace#x402); an operator does\nnot reimplement it.\n\n### Pieces that are designed but not complete at this SHA\n\nThe gateway is honest about what is not finished. Treat these as the operator-relevant gaps.\n\n- **Pool dispatch returns `503`.** The scoring logic can select a compute pool, but when a pool wins the\n dispatch the gateway returns a `503`; the per-provider path is the one that runs today. Status: Specified.\n- **Usage accounting is in process memory.** `GET /v1/usage` totals are held in memory and do not survive a\n restart. Durable usage storage is a later slice. Status: Specified.\n- **Friendly model-name resolution is best effort.** A pinned 32-byte model hash always resolves; a friendly\n name is resolved by enumerating the registry, because `ModelRegistry` exposes no name-to-hash view. A\n dedicated view or an off-chain name registry is the intended fix. Status: Specified.\n\n## Design rationale\n\nThe gateway runs on the operator's own hardware and binds to loopback by default, so the network surface is\nsomething you place deliberately behind your own TLS and rate limits rather than something exposed by\naccident. Settlement happens per request rather than against a held balance, so the gateway never escrows\nmore than the single request or batch in flight, and an operator is paid for the work actually performed.\nThe OpenAI shape is kept intact so the payment difference sits behind one middleware and the rest reads as an\nordinary inference proxy. The cost of per-request settlement is an on-chain transaction in the hot path; the\nbenefit is that no balance is held on a caller's behalf.\n\n## Failure modes\n\nThe paid surface is where the gateway is security relevant, and it fails closed.\n\n- **No payment.** A paid route without a valid `x-payment` header returns `402`, never the work.\n- **Replayed or forged payment.** The nonce is single-use and minted by this gateway, the signature is\n verified, the window is checked, and the recipient is bound to your treasury, so a replayed payment is\n rejected before it touches the chain.\n- **Settlement revert.** If the on-chain settlement reverts, the gateway returns `402` with the transaction\n hash rather than running the handler.\n- **Oversized request.** `max_tokens` is clamped to the ceiling before pricing, a batch over 1000 requests\n is rejected, and a batch spanning more than 32 distinct models (`MAX_DISTINCT_BATCH_MODELS`) is rejected,\n so a caller cannot price small and demand large.\n- **Open chat in production.** The unauthenticated chat path is gated behind two development flags and the\n gateway refuses to enable it on a non-loopback bind, so it cannot be left exposed by accident.\n- **Pool dispatch.** A request that scores to a pool returns `503` today rather than failing silently; route\n such traffic to the per-provider path until pool dispatch ships.\n\n## Access and canon\n\nCommercial tier. This is operator and deployment depth, the configuration and settlement detail an operator\nneeds to stand up a gateway. The client-facing REST surface that calls it is public and lives on the\n[client page](/sdks/inference-gateway).\n\nThe gateway runs on hardware you control. Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity. No API keys, treasury addresses, or operator account material appear here; that material is loaded\nfrom a keystore or a KMS reference and is never documented. The repository contains no hardcoded credentials\nat the audited SHA.\n\n## Source and verification\n\n- Source: `citrate-inference-gateway`. Run modes and configuration in `gateway/src/main.rs` and\n `gateway/src/config.rs`; router assembly in `gateway/src/lib.rs`; route handlers in `gateway/src/`\n (`chat.rs`, `batch.rs`, `models.rs`, `usage.rs`, `health.rs`, `metrics.rs`); chain reads in\n `gateway/src/queries.rs` (the `ChainQueries` trait, not an HTTP route); x402 middleware in\n `crates/x402-axum/src/layer.rs`; operator reference in `gateway/RUNBOOK.md`.\n- Audited against SHA: `603fe92`.\n- Status: Implemented (pre-audit) for the free routes: the run modes, on-chain reads and per-provider\n dispatch with failover exist and run. The x402 settlement path and the API-key routes are built and tested\n but not mounted by the released binary, so paid calls are not live. This slice has not had an external audit. Specified: pool\n dispatch (returns `503` today), durable usage accounting, and a name-to-hash model view.\n"},"/compute/node-agent":{"slug":"/compute/node-agent","title":"Citrate Node","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-node-agent (crates/, README.md)","syncedSha":"0e63363","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"`compute.json`, `crates/config/src/lib.rs`","anchor":"computejson-cratesconfigsrclibrs"},{"depth":3,"text":"Environment variables","anchor":"environment-variables"},{"depth":3,"text":"Bidder, `crates/bidder/src/lib.rs`","anchor":"bidder-cratesbiddersrclibrs"},{"depth":3,"text":"Heartbeat, `crates/heartbeat/src/lib.rs`","anchor":"heartbeat-cratesheartbeatsrclibrs"},{"depth":3,"text":"Supervision HTTP, `crates/supervision/src/server.rs`","anchor":"supervision-http-cratessupervisionsrcserverrs"},{"depth":3,"text":"Marketplace calls, `crates/chainio/`","anchor":"marketplace-calls-crateschainio"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Citrate Node is the daemon an operator runs to sell compute on Citrate Market from their own hardware.\nIt reads a small settings file, watches the market over JSON-RPC, decides which jobs to bid on, proves\nliveness with a heartbeat, and drives a won job to payout. It holds no keys: every write it wants made is\nhanded, unsigned, to a separate signing surface. This page is for operators on Citrate Network, chain id 40204.\n\n## What it is\n\nCitrate Node is a Rust workspace of focused crates, built into one `node-agent` binary. The binary loads\n`compute.json`, samples the clock, reads chain state, runs the bidder, and exposes a loopback supervision\nAPI the operator's tools drive. Compute is sold on Citrate Market; the machine running this daemon stays\non your premises, and the work it performs settles in SALT.\n\nOne property shapes the whole design: the daemon holds no signing keys. Every on-chain write is produced\nas an unsigned `SignatureRequest` and queued for an external signing surface, the operator's Citrate\nKeyring or a signing relay, which signs and broadcasts it. The daemon only observes the resulting\ntransaction. This is why selling involves two roles, the agent that decides and the signer that holds\nkeys, and it is enforced in code at `crates/lifecycle/src/lib.rs` and `crates/node-agent/src/main.rs`\n(ADR-agent-signing / TD-17).\n\nThe crates, each citing the path it is audited against:\n\n| Crate | Path | Role |\n|---|---|---|\n| `config` | `crates/config/src/lib.rs` | Parse `compute.json` into `ComputeSettings`; schedule-window logic. |\n| `bidder` | `crates/bidder/src/lib.rs` | Pure `evaluate()` cost-plus bid decision. |\n| `heartbeat` | `crates/heartbeat/src/lib.rs` | 30-second liveness loop, `heartbeat()` calldata. |\n| `chainio` | `crates/chainio/` | Chain-40204 address book, JSON-RPC read client, ABI codec, outbound TLS gate. |\n| `supervision` | `crates/supervision/src/server.rs` | Loopback HTTP control surface, bearer-token gated. |\n| `node-agent` | `crates/node-agent/` | The binary that ties the crates together. |\n| `lifecycle` | `crates/lifecycle/src/lib.rs` | Job state machine, plans the next unsigned write. |\n| `executor` | `crates/executor/` | Model provisioning and inference adapter. |\n| `earnings` | `crates/earnings/` | Claimable poll and `claimRewards()` calldata. |\n| `pinning` | `crates/pinning/` | Replication-slot pinning sidecar. |\n\nThe settings, bidder, heartbeat, and supervision surfaces are the implemented selling path (SELL-S1). The\nexecution, model-provisioning, and earnings surfaces (SELL-S2) compile and are wired into the daemon loop\nbut several paths still report themselves as not yet production; treat them as experimental. The agent\nhas not had an external audit.\n\n## How to use it\n\n1. Build the binary from the workspace root with `cargo build --release`. The result is\n `target/release/node-agent`.\n2. Write `compute.json`, your participation policy. Start with `enabled: false` to dry-run the wiring,\n then flip it to `true`.\n3. Self-check offline by running `node-agent path/to/compute.json`. No RPC is contacted; the agent prints\n your policy, the clock, and the heartbeat calldata it would send.\n4. Run live as a daemon with `CITRATE_RPC_URL` and `CITRATE_PROVIDER_ADDRESS` set. The daemon brings up\n the loopback supervision API, reads chain state each tick, runs the bidder, and beats every 30 seconds.\n5. Wire up your signer. The signing surface pulls unsigned requests from `/signature-requests`, signs and\n broadcasts them, then reports each back to `/signature-requests/{id}/observed`.\n\nThe runnable, end-to-end version of this is [become a compute seller](/operators/tutorials/become-a-seller),\nand the full operating procedure is [sell compute](/operators/sell-compute).\n\n## Reference\n\n### `compute.json`, `crates/config/src/lib.rs`\n\nAudited against `ComputeSettings`. Unknown fields are ignored, so newer writers stay forward-compatible; a\nmissing or empty file falls back to disabled, the fail-safe default.\n\n| Field | Type | Default | Meaning |\n|---|---|---|---|\n| `enabled` | bool | `false` | Master participation switch. |\n| `allocation_percent` | u8, 0 to 100 | `0` | Fraction of the GPU you allot. Out of range is rejected. In S1 this is read and surfaced, not yet hardware-enforced. |\n| `schedule` | enum | `always` | `always`, `nights` (22:00 to 05:59 local), or `weekends` (Saturday and Sunday). |\n\n```json\n{\n \"enabled\": true,\n \"allocation_percent\": 50,\n \"schedule\": \"always\"\n}\n```\n\n### Environment variables\n\nAudited against the crates at this SHA. No secrets belong here. Every outbound URL is validated at client\nconstruction and fails closed on plaintext HTTP to a non-loopback host (`crates/chainio/src/outbound.rs`).\n\n| Variable | Default | Required | Purpose |\n|---|---|---|---|\n| `CITRATE_RPC_URL` | unset | No, S1 runs offline | Chain-40204 JSON-RPC endpoint, https or loopback http. |\n| `CITRATE_PROVIDER_ADDRESS` | unset | No | The operator's provider account address, 20-byte hex. |\n| `CITRATE_NODE_AGENT_DAEMON` | unset | No | Enable daemon mode (`1` or `true`), or pass `--daemon`. |\n| `CITRATE_NODE_AGENT_ADDR` | `127.0.0.1:19600` | No | Supervision bind address; must stay loopback or the daemon refuses to start. |\n| `CITRATE_NODE_AGENT_TOKEN_FILE` | `$HOME/.citrate/node-agent/supervision.token` | No | Bearer-token file, minted at startup, mode 0600. |\n| `CITRATE_NODE_PFLOPS_1E18` | `6e18`, 6 pflops | No | Node throughput as fixed-point times 1e18, feeds execution-time estimates. |\n| `CITRATE_CLAIM_THRESHOLD_WEI` | `1e18`, 1 SALT | No | Auto-claim earnings once claimable reaches this threshold (S2). |\n| `CITRATE_MODEL_CACHE_DIR` | `/var/lib/citrate-node-agent/models` | No | Model-weights cache (S2). |\n| `CITRATE_MODEL_SHA256` | unset | No | Trusted weights digest, recomputed locally for integrity (S2). |\n| `CITRATE_RESIDENT_MODEL_HASH` | unset | No | 64-hex model hash that binds the resident llama-server to one declared model; a job for any other model is refused (S2). |\n| `CITRATE_IPFS_GATEWAY` | unset | No | Gateway for model-CID weight fetch (S2). |\n| `CITRATE_LLAMA_URL` | unset | No | Resident llama-server inference endpoint (S2). |\n| `CITRATE_JOB_INPUT_DIR` | unset | No | Watched directory for off-chain job input (S2). |\n| `CITRATE_HTTP_TIMEOUT_SECS` | `30` | No | Total-request timeout for the outbound RPC and inference clients. |\n| `CITRATE_HTTP_CONNECT_TIMEOUT_SECS` | `10` | No | Connect timeout for the outbound clients. |\n| `CITRATE_HTTP_READ_TIMEOUT_SECS` | `60` | No | Per-read inactivity timeout, covers the streaming weight fetch. |\n\nThe execution path turns on only when `CITRATE_IPFS_GATEWAY`, `CITRATE_LLAMA_URL`, and\n`CITRATE_JOB_INPUT_DIR` are all set; otherwise the daemon bids and claims earnings but does not execute\nwon jobs (`crates/node-agent/src/main.rs`, `build_job_executor`).\n\nThere is also a dev-only escape hatch, `CITRATE_NODE_AGENT_ALLOW_INSECURE_OUTBOUND=1`. It bypasses the TLS\nrequirement and permits plaintext HTTP to non-loopback hosts, for LAN test rigs only. A network attacker\non a plaintext RPC link can feed the agent false chain state, false oracle prices, and false job state.\nThe agent logs loudly whenever it is set. Do not set it on any production node.\n\n### Bidder, `crates/bidder/src/lib.rs`\n\nA pure function, `evaluate(job, oracle, settings, caps) -> BidDecision`. The gates run cheapest first,\nthen pricing:\n\n1. `enabled == false`, skip (Disabled).\n2. Outside the schedule window, skip (OutsideSchedule).\n3. `maxPrice >= 10 SALT`, skip (ExceedsCommitmentCap); S1 stays below the threshold above which a job\n auto-upgrades to a verification tier whose precompile is stubbed.\n4. Tier other than Commitment, skip (UnsupportedTier).\n5. Active jobs at or above 80% of capacity, skip (AtCapacity).\n6. Time to deadline below twice the estimated execution seconds, skip (DeadlineInfeasible).\n7. Schedule window closes within twice the estimated execution seconds, skip (WindowClosingSoon).\n8. Oracle price stale, skip (OracleStale).\n9. Price is `cost x 1.15`, capped at `0.9 x maxPrice`; if that would fall below cost, skip (Unprofitable);\n otherwise bid.\n\n### Heartbeat, `crates/heartbeat/src/lib.rs`\n\nThe cadence is 30 seconds (`HEARTBEAT_INTERVAL`), kept under the on-chain heartbeat window so a single\nmissed beat never trips suspension. The calldata is the `HeartbeatMonitor.heartbeat()` selector alone, no\narguments. Send errors are logged but do not stop the loop.\n\n### Supervision HTTP, `crates/supervision/src/server.rs`\n\nLoopback only; a non-loopback bind is rejected at startup. A 256-bit bearer token is minted at startup,\npersisted at mode 0600, and required on every endpoint except `/health`.\n\n| Method | Route | Auth | Response |\n|---|---|---|---|\n| GET | `/health` | none | Health snapshot for liveness probes. |\n| GET | `/status` | bearer | `idle`, `bidding`, `executing`, or `paused`. |\n| POST | `/pause` | bearer | Stop new bids; in-flight jobs finish. |\n| POST | `/resume` | bearer | Resume bidding. |\n| GET | `/signature-requests` | bearer | The unsigned `SignatureRequest`s waiting for the signer. |\n| POST | `/signature-requests/{id}/observed` | bearer | Mark a request signed and broadcast; body `{\"tx_hash\":\"0x...\"}`. |\n\n### Marketplace calls, `crates/chainio/`\n\nThe read client and ABI codec work against the chain-40204 address book, mirrored from Citrate Network and\nguarded by a divergence test. On `ComputeMarketplace` the agent reads `getProvider` and `getJob` and builds\ncalldata for `registerProvider`, `bidOnJob`, `assignBestBid`, `startExecution`, `submitCommitment`,\n`submitResult`, and `completeJob`. It reads `saltPerPflopHour` and `isPriceStale` from `ComputePricingOracle`,\nsends `heartbeat()` to `HeartbeatMonitor`, claims from `ContributionAccounting`, and reads `getModel` from\n`ModelRegistry`. The marketplace functions are documented in full in [compute contracts](/contracts/compute).\n\n## Design rationale\n\nThe two-role split, an agent that never holds keys and a signer that does, is the load-bearing decision.\nAn unattended daemon that watches the market and reacts to prices is exactly the kind of process you do not\nwant holding a signing key on a production GPU host. By emitting unsigned requests and letting the operator's\nCitrate Keyring or a relay sign them, a compromise of the daemon cannot move funds or stake on its own. The\ncost is the extra signer hop, which the supervision API and the observe callback are built to make routine.\n\nThe bidder's gates lean conservative for the same reason. New providers start with no reputation, jobs that\nmiss a deadline are slashed, and the night and weekend schedules exist so an operator can sell idle hours\nwithout supervising the machine. The 80% capacity cap and the twice-execution-time margins all reserve\nheadroom so the daemon does not take on work it cannot safely finish.\n\n## Failure modes\n\n- A non-loopback `CITRATE_NODE_AGENT_ADDR` is refused at startup; the supervision surface cannot be widened\n to a routable interface.\n- Plaintext HTTP to a non-loopback RPC, IPFS, or inference endpoint fails closed at client construction, so\n a misconfigured daemon dies at startup rather than mid-job. The dev-only insecure-outbound flag is the\n only override, and it is forbidden in production.\n- The bearer token is minted locally at mode 0600. A web page the operator visits cannot read it, which\n also closes the cross-site request hole on `/pause` and `/resume`. `/health` is intentionally open and\n exposes no secret.\n- Past the execution deadline the lifecycle planner aborts rather than submit a late, slashable result.\n- A stale oracle stops the bidder from pricing off bad data.\n\n## Access and canon\n\nTier commercial.kyc. The agent is operator-depth implementation, the bid-pricing logic, the lifecycle, and\nthe capacity gates, that any contracted operator should have but whose anonymous theft would materially help\na competitor clone the selling side of the market. Access is gated on identity verification through Citrate's in-house verification (VERI),\nnot on a seat. Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity. Compute is sold from your own hardware, and\nSALT settles the work; it is the unit you count in, not a product to hold. No secrets appear on this page:\nthe supervision token is generated locally and never transcribed, and the agent holds no keys by design.\n\n## Source and verification\n\nVerified against `citrate-node-agent` at `38bc9d1`. `compute.json` against `crates/config/src/lib.rs`; the\nbidder gates and cost-plus pricing against `crates/bidder/src/lib.rs`; the 30-second cadence against\n`crates/heartbeat/src/lib.rs`; the loopback bind, bearer token, and routes against\n`crates/supervision/src/server.rs` and `crates/supervision/src/auth.rs`; the no-keys signing seam against\n`crates/lifecycle/src/lib.rs` and `crates/node-agent/src/main.rs`; the outbound TLS gate against\n`crates/chainio/src/outbound.rs`; the marketplace calls against `crates/chainio/src/marketplace.rs` and the\nchain-40204 address book in `crates/chainio/src/generated/addresses.json`. Status: SELL-S1 (settings,\nbidder, heartbeat, supervision, live reads) Implemented, pre-audit; SELL-S2 (execution, executor, earnings)\nSpecified and experimental.\n"},"/compute/pool":{"slug":"/compute/pool","title":"Citrate Compute Pool","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-compute-pool (pool-coordinator/, training-worker/)","syncedSha":"e5b7280","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"The two crates","anchor":"the-two-crates"},{"depth":3,"text":"How the coordinator decides","anchor":"how-the-coordinator-decides"},{"depth":3,"text":"Coordinator environment variables","anchor":"coordinator-environment-variables"},{"depth":3,"text":"Worker environment variables","anchor":"worker-environment-variables"},{"depth":3,"text":"What the worker library already contains","anchor":"what-the-worker-library-already-contains"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The compute pool is how several machines act as one provider on Citrate Market. It is two\ndaemons: a coordinator that spreads single-prompt inference work across a pool, and a worker\nthat takes part in distributed training and in multi-stage inference. It is for operators who\nrun more than one machine and want them to share work and share the pay.\n\n## What it is\n\nA pool is a group of machines, each one a member, that present themselves to the market as a\nsingle provider. The work and the settlement still happen on the public ledger, the Citrate\nNetwork; the pool only decides which member does a given job and divides the payment when the\njob is done.\n\nTwo daemons live in the `citrate-compute-pool` workspace, and they play different roles.\n\n- The coordinator, `citrate-pool-coordinator`, handles single-prompt inference. Every member\n of an on-chain `ComputePool` runs a copy. The copies watch the chain for a `ComputeRequested`\n event, and the one whose account is the elected coordinator for the current epoch picks a\n member, records the dispatch on-chain, sends the prompt to that member over HTTPS, and then\n submits the completion or the failure. An epoch is a fixed window of 100 blocks; the elected\n coordinator rotates with it.\n- The worker, `citrate-training-worker`, takes part in jobs that span machines. In `training`\n mode it is one node of a data-parallel training pool, the surface we call Citrate Orchard. In\n `pipeline` mode it owns one stage of a model too large for a single machine, and activations\n flow through the stages in sequence.\n\nBoth daemons keep their keys, their data, and their model weights on the operator's own\nhardware. Nothing about a job reaches the public ledger except the dispatch record, the\ncompletion, and the payment, which is the on-premise default that holds across the network.\n\n## How to use it\n\n1. Build the workspace. From the `citrate-compute-pool` root, `cargo build --release` produces\n both binaries. Neither one depends on a chain crate at the Rust level; they speak to the\n network over JSON-RPC at runtime, so no deploy key is needed to build.\n2. Give each daemon an account. Both load a secp256k1 key, either from a Secret Storage v3\n keystore file plus its passphrase, or from a raw private-key hex string for testnet only. The\n account address you configure must match the key.\n3. Point the daemon at the network and the contract. The coordinator needs the `ComputePool`\n address and the map of member endpoints; the worker needs its mode and its job contract.\n4. Run the coordinator on every member machine. Each copy decides for itself whether it is the\n elected coordinator for the current epoch and acts only when it is.\n5. Run the worker where the training or pipeline job lives, scoped to the job id you want it to\n watch.\n\nA coordinator and a worker, started from the environment up:\n\n```bash\n# Coordinator: one copy per pool member\nexport CITRATE_POOL_KEYSTORE_PATH=/secure/pool-keystore.json\nexport CITRATE_POOL_KEYSTORE_PASSPHRASE=\"$KEYSTORE_PASS\" # from your secret store\nexport CITRATE_POOL_WALLET_ADDRESS=0x\nexport CITRATE_POOL_CONTRACT=0x\nexport CITRATE_POOL_RPC_URL=https://\nexport CITRATE_POOL_MEMBER_ENDPOINTS=\"0xm1=https://m1/pool-infer,0xm2=https://m2/pool-infer\"\ncitrate-pool-coordinator\n\n# Worker: one per training or pipeline job\nexport CITRATE_WORKER_MODE=training\nexport CITRATE_TRAINING_KEYSTORE_PATH=/secure/worker-keystore.json\nexport CITRATE_TRAINING_KEYSTORE_PASSPHRASE=\"$KEYSTORE_PASS\"\nexport CITRATE_WORKER_CONTRACT=0x\nexport CITRATE_WORKER_JOB_ID=42\ncitrate-training-worker\n```\n\n## Reference\n\n### The two crates\n\n| Path | Crate | Role |\n|---|---|---|\n| `pool-coordinator/` | `citrate-pool-coordinator` | Watches `ComputeRequested`, decides the member, records dispatch, posts the prompt, completes or fails the job. |\n| `training-worker/` | `citrate-training-worker` | `training` mode is a data-parallel training node, `pipeline` mode is one stage of a multi-stage model. |\n\nThe workspace also builds two binaries this page does not cover: `citrate-coop-worker` (a member-facing\nregister-and-poll daemon in `training-worker/src/bin/coop_worker.rs`) and `citrate-training-coordinator` (a\nseparate HTTP coordinator service in the `training-coordinator/` crate).\n\n### How the coordinator decides\n\nThe decision loop is `handle_event` in `pool-coordinator/src/lib.rs`. Before it touches the chain, it runs\nan admission gate (`pool-coordinator/src/lib.rs`, CP-B-008): it rejects the event with `RejectedEvent` if\nthe payment is below `CITRATE_POOL_MIN_PAYMENT_GRAINS`, the prompt is larger than\n`CITRATE_POOL_MAX_PROMPT_BYTES`, or `max_tokens` is above `CITRATE_POOL_MAX_TOKENS`. For an admitted\n`ComputeRequested` event it then does five things, in order, and the order matters.\n\n1. It reads `coordinatorFor(poolId, epoch)`, with the epoch derived from the event's block\n number as `block / 100` (`pool-coordinator/src/chain.rs`, `epoch_of`). If the elected\n coordinator is not this daemon's own account, it stops and does nothing on-chain.\n2. It reads the pool's members, keeps only those that are active and have a GPU, and picks one.\n Selection is deterministic, not a rotating cursor: the member is\n `active[keccak256(job_id) mod active_count]`, where `select_member` takes the last eight bytes\n of the digest as a `u64` and reduces it modulo the active-member count\n (`pool-coordinator/src/dispatcher.rs`). The same job id always picks the same member, so a\n coordinator that restarts mid-job picks the same target, and two daemons that briefly believe\n they are coordinator pick the same target rather than two different ones.\n3. It resolves the chosen member to its HTTPS endpoint from the configured map, failing with\n `UnknownMemberEndpoint` if the member is not listed.\n4. It records the dispatch on-chain before making the HTTP call. Recording first means that if\n the call hangs and the daemon crashes, the chain already knows the dispatch happened and the\n timeout clock has started, so the job cannot sit pending forever.\n5. It POSTs the prompt to `member/pool-infer` and waits. On success it submits `completeJob`,\n which distributes payment across the members. On failure it submits `failJob`, which refunds\n the buyer.\n\nThe request body is `{model, prompt, max_tokens, job_id}` and the response is\n`{output, input_tokens?, output_tokens?}` (`pool-coordinator/src/provider.rs`). A 2xx response\nwith an empty or whitespace `output` is rejected as `ProviderFailed`, which routes the job down\nthe refund path. This is the pay-for-no-work guard; it does not yet check that the output is\ncorrect, only that there is one.\n\n### Coordinator environment variables\n\nRead from `pool-coordinator/src/config.rs` (chain, endpoints, timeouts, admission caps),\n`pool-coordinator/src/wallet.rs` (the account keys), and `pool-coordinator/src/main.rs` (contract address,\npoll cadence, metrics bind).\n\n| Variable | Default | Required | Purpose |\n|---|---|---|---|\n| `CITRATE_POOL_KEYSTORE_PATH` | none | one of | SSv3 keystore file path. |\n| `CITRATE_POOL_KEYSTORE_PASSPHRASE` | none | if keystore set | Keystore passphrase. |\n| `CITRATE_POOL_PRIVATE_KEY_HEX` | none | one of (testnet) | Raw 64-hex key. |\n| `CITRATE_POOL_WALLET_ADDRESS` | none | yes | Must match the derived key. |\n| `CITRATE_POOL_CONTRACT` | none | yes | `ComputePool` address. |\n| `CITRATE_POOL_MEMBER_ENDPOINTS` | `\"\"` | yes | `addr1=url1,addr2=url2` member endpoint map. |\n| `CITRATE_POOL_CHAIN_ID` | `40204` | no | Chain id, verified against the RPC at startup. |\n| `CITRATE_POOL_RPC_URL` | `http://127.0.0.1:8545` | no | JSON-RPC endpoint. |\n| `CITRATE_POOL_PROVIDER_TIMEOUT_SECS` | `30` | no | Per-request member timeout. |\n| `CITRATE_POOL_MIN_PAYMENT_GRAINS` | `1` | no | Admission gate: reject a request paying below this. |\n| `CITRATE_POOL_MAX_PROMPT_BYTES` | `131072` | no | Admission gate: reject a prompt larger than this (128 KiB). |\n| `CITRATE_POOL_MAX_TOKENS` | `8192` | no | Admission gate: reject a request asking for more tokens than this. |\n| `CITRATE_POOL_POLL_INTERVAL_SECS` | `3` | no | Event poll cadence. |\n| `CITRATE_POOL_CONFIRMATIONS_BUFFER` | `12` | no | Re-scan depth for reorg tolerance. |\n| `CITRATE_POOL_FROM_BLOCK` | `latest` | no | Event start block. |\n| `CITRATE_POOL_WS_URL` | none | no | Opt-in websocket endpoint for event subscription. |\n| `CITRATE_POOL_METRICS_ADDR` | none | no | Prometheus `/metrics` bind, warns if not loopback. |\n\n### Worker environment variables\n\nRead from `training-worker/src/bin/main.rs`.\n\n| Variable | Default | Required | Purpose |\n|---|---|---|---|\n| `CITRATE_WORKER_MODE` | none | yes | `training` or `pipeline`. |\n| `CITRATE_TRAINING_KEYSTORE_PATH` | none | one of | SSv3 keystore path. |\n| `CITRATE_TRAINING_KEYSTORE_PASSPHRASE` | none | if keystore set | Passphrase. |\n| `CITRATE_TRAINING_PRIVATE_KEY_HEX` | none | one of (testnet) | Raw key. |\n| `CITRATE_WORKER_CONTRACT` | none | yes | Training or pipeline contract address. |\n| `CITRATE_WORKER_JOB_ID` | none | recommended | The job to watch, by id. |\n| `CITRATE_WORKER_CHAIN_ID` | `40204` | no | Chain id. |\n| `CITRATE_WORKER_RPC_URL` | `https://rpc.citrate.ai` | no | JSON-RPC endpoint. |\n| `CITRATE_WORKER_POLL_INTERVAL_SECS` | `3` | no | Event poll cadence. |\n| `CITRATE_WORKER_CONFIRMATIONS_BUFFER` | `12` | no | Re-scan depth for reorg tolerance. |\n| `CITRATE_WORKER_FROM_BLOCK` | `latest` | no | Event start block. |\n| `CITRATE_WORKER_METRICS_ADDR` | none | no | Metrics bind, not yet wired for the worker. |\n\n### What the worker library already contains\n\nThe training state machine is written and tested, even where the production binary does not yet\ndrive it. In `training-worker/src/worker.rs` a `Worker` runs the full lifecycle, joining,\nloading shared starting weights, the per-step forward and backward pass, a quantized\nall-reduce of the gradient with peers, a per-step commitment, and, when a worker is the elected\ncoordinator for an epoch, aggregating the peers' commitments into a Merkle root and posting it\nwith `commitEpoch`. The `EpochAggregator` in that file enforces that only registered members\ncount toward a root and that each `(worker, step)` pair counts once, so a single flooding peer\ncannot finalize a root over forged work. The pipeline path in `training-worker/src/pipeline.rs`\nruns the same idea for one stage of a model. Today these run against a small deterministic model,\nan in-process transport, and a mock chain client, which is enough to check the state machine end\nto end.\n\n## Design rationale\n\nThe coordinator holds no shared state, and that is deliberate. Because the member is chosen by\nhashing the job id rather than advancing a cursor, every copy of the daemon reaches the same\nanswer without talking to the others, a restart does not lose its place, and the brief window\nwhere two copies both think they are coordinator resolves on-chain: the contract's \"job not\npending\" guard rejects the second `recordDispatch`. The cost is that selection is not perfectly\neven per member; over many jobs it is close enough, and the property we wanted was agreement\nwithout coordination. Recording the dispatch before the HTTP call, rather than after, trades a\nlittle latency for the guarantee that a crashed coordinator leaves a recoverable trail instead\nof a job stuck pending forever.\n\n## Failure modes\n\nThe surface is security relevant where money moves, so it fails toward the buyer. A member that\nreturns nothing, times out, or returns empty output is treated as work not done: the coordinator\nsubmits `failJob` and the buyer is refunded rather than paying for silence. The chain id is\nchecked against the RPC at startup, so a daemon pointed at the wrong network stops instead of\nsigning transactions there. Pool membership and stake are enforced by the on-chain contract, not\nby the daemons, so a machine cannot pay itself by lying to a coordinator. Bind any metrics\nendpoint to loopback unless you put authentication in front of it; the coordinator warns when the\nbind address is not loopback. The output guard closes the pay-for-empty-work path, not the\npay-for-wrong-work path: checking that an answer is correct, not merely present, is on-chain work\nthat is specified but not yet built.\n\n## Access and canon\n\nTier commercial: this is paid-seat marketplace operation, where operators sell pooled compute\nunder a service level. Every member account on the public network is identity-checked through\nVERI; Citrate keeps the verification result, not the personal data behind it. Keys, data, and\nmodel weights stay on the operator's hardware, and the only things the pool publishes are the\ndispatch record, the completion, and the payment. No secrets appear on this page: keystore\npassphrases and private keys come from your own secret store and are never printed.\n\nThis page connects to [federated learning](/research/learning) for the training surface\n(Citrate Orchard), to the [compute contracts](/contracts/compute) the daemons call, and to the\n[node agent](/compute/node-agent) that a single machine runs when it is not part of a pool.\n\n## Source and verification\n\n- Source repo: `citrate-compute-pool`.\n- Files: `pool-coordinator/src/lib.rs`, `dispatcher.rs`, `chain.rs`, `provider.rs`, `config.rs`;\n `training-worker/src/lib.rs`, `worker.rs`, `pipeline.rs`, `bin/main.rs`, `training-worker/src/wallet.rs`.\n- Audited against SHA: `e5b7280`.\n- Status by component:\n - Coordinator decision loop (`handle_event`, `select_member`, `epoch_of`, output guard):\n Implemented, with unit and smoke tests. The live JSON-RPC adapter (`HttpChainAdapter`) is the\n seam the loop runs against; the loop and its guards are tested through a mock chain.\n Pre-audit.\n - Coordinator binary: Implemented, pre-audit.\n - Worker binary (`bin/main.rs`): Implemented for event observation. It watches a job and logs\n the events that would drive the state machine; wiring those events to spawn a `Worker` is a\n follow-up. Pre-audit.\n - Training and pipeline state machines (`worker.rs`, `pipeline.rs`): Implemented against a\n deterministic model, in-process transport, and mock chain (S0). The real GPU backend,\n cross-machine libp2p transport, and live chain are Specified, not yet built (S1 and S2).\n - On-chain output correctness checking and challenge hooks: Specified, not yet built.\n"},"/contracts/compute":{"slug":"/contracts/compute","title":"Compute contracts","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/contracts/src (ComputeMarketplace.sol, ComputePool.sol, ComputePoolTraining.sol, ComputeVerifier.sol, BulkComputeGateway.sol, ComputePricingOracle.sol, interfaces/IComputePricingOracle.sol)","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"ComputeMarketplace","anchor":"computemarketplace"},{"depth":3,"text":"ComputePool","anchor":"computepool"},{"depth":3,"text":"ComputePoolTraining","anchor":"computepooltraining"},{"depth":3,"text":"ComputeVerifier","anchor":"computeverifier"},{"depth":3,"text":"ComputePricingOracle","anchor":"computepricingoracle"},{"depth":3,"text":"BulkComputeGateway","anchor":"bulkcomputegateway"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"These are the contracts that let a machine operator sell compute on Citrate Market and let a buyer pay\nfor verified work. Six contracts form one settlement loop: a buyer posts a job, an operator runs it,\nthe result is verified by tier, and payment settles on the public ledger. This page is for operators,\nbuyers, and anyone integrating against the marketplace.\n\n## What it is\n\nCitrate Market is the part of the public ledger where compute is bought and sold. An operator runs a\nCitrate Node off-chain to do the actual work, on hardware they control; the contracts here record the\nagreement, hold the escrow, decide whether the returned work was valid, and split the payment. The\nheavy data, the model weights and the inputs, never live on-chain. What the ledger keeps is a hash, a\nproof, and a receipt.\n\nThe loop has six parts:\n\n- **ComputeMarketplace** runs the single-provider job lifecycle: post, bid, assign, execute, verify,\n settle.\n- **ComputePool** and **ComputePoolTraining** are the multi-provider variants, one for pooled inference\n with a throughput guarantee, one for distributed training across many workers.\n- **ComputeVerifier** decides whether a returned result is valid, by one of three tiers.\n- **ComputePricingOracle** maps compute cost and SALT price so a job can be quoted.\n- **BulkComputeGateway** lets an institution pre-fund credits with a stablecoin and spend them later.\n\nAll six share one governance pattern. Each inherits a `Governable` mixin with a two-step transfer\n(`transferGovernance` then `acceptGovernance`), an `onlyGovernance` modifier, and a `governance()`\nreader. Five of the six also inherit `ReentrancyGuard`; the pricing oracle does not move value, so it\ndoes not need it.\n\nOperators sell into this surface through [Operators, sell compute](/operators/sell-compute) and run the\ndaemon described in [run a node](/operators/run-a-node). The on-chain verification leans on the compute\n[precompiles](/chain/precompiles), in particular the Halo2-KZG inference verifier at `0x0108`.\n\n## How to use it\n\nThe single-provider path, from a buyer's side, runs like this.\n\n1. Quote the work. Ask **ComputePricingOracle** what one PFLOP-hour costs in SALT with\n `saltPerPflopHour()`, or estimate a specific job with `estimateJobCost(modelHash, inputTokens,\n outputTokens, verificationTier)`.\n2. Post the job. Call `postJobWithMethod(...)` on **ComputeMarketplace** with the model hash, the input\n hash, a maximum price, a verification tier, a payment method, a bid window, and an execution window.\n The escrow is taken from `msg.value` in SALT, or debited from bulk credits if you chose that method.\n3. Wait for bids and assign. Operators call `bidOnJob`. Anyone can then call `assignBestBid`, which\n scores the bids on price, reputation, current load, and verification fit, and assigns the winner.\n4. The operator executes. They call `startExecution`, optionally post a Tier-1 commitment with\n `submitCommitment`, then return the result with `submitResult`. Submitting the result triggers\n verification inline.\n5. Settle. Once the verifier returns Valid and no dispute is open, call `completeJob`. Payment splits\n into the provider's share, a burn, and a treasury fee.\n\nTo sell instead of buy, register first: `registerProvider(supportedModels)` with a stake of at least\n`MIN_PROVIDER_STAKE`, then bid on jobs that match your models. To pool with other operators, see\n**ComputePool** below; to join a training run, see **ComputePoolTraining**.\n\n## Reference\n\nThe audited public surface of each contract, with the function names as they appear in source. The\ncanonical truth is the Solidity in `contracts/src`; treat ABIs as coming from the published package, not\nhand-copied from here.\n\n### ComputeMarketplace\n\n`contracts/src/ComputeMarketplace.sol`, `contract ComputeMarketplace is ReentrancyGuard, Governable`.\nThe single-provider job lifecycle, with escrow in SALT or bulk credits, a burn and a treasury fee on\nsettlement, provider staking and slashing, timeouts, and disputes. The constructor takes a verifier and\na treasury address and deploys a fresh `Burner` at construction.\n\n| Function | Notes |\n|---|---|\n| `postJob(bytes32 modelHash, bytes inputHash, uint256 maxPrice, ComputeVerifier.VerificationTier tier, uint256 bidWindow, uint256 execWindow) payable returns (uint256)` | Legacy poster; escrow defaults to SALT. |\n| `postJobWithMethod(bytes32 modelHash, bytes inputHash, uint256 maxPrice, ComputeVerifier.VerificationTier tier, PaymentMethod paymentMethod, uint256 bidWindow, uint256 execWindow) payable returns (uint256)` | Choose `SALT` or `BulkCredits` escrow. |\n| `autoAssignJob(bytes32 modelHash, bytes inputHash, ComputeVerifier.VerificationTier tier) payable returns (uint256)` | One-shot post and assign; requires at least `MIN_AUTO_ASSIGN_PAYMENT` (0.01 SALT). |\n| `bidOnJob(uint256 jobId, uint256 price, uint256 estimatedLatency)` | Operator bids during the bid window. |\n| `assignBestBid(uint256 jobId)` | Permissionless; scores bids by price, reputation, load, and verification fit. |\n| `startExecution(uint256 jobId)` / `submitCommitment(uint256 jobId, bytes32 commitment)` | Assigned provider begins work, or posts a Tier-1 commitment. |\n| `submitResult(uint256 jobId, bytes outputHash, bytes proof)` | Returns output and proof; runs verification inline. |\n| `completeJob(uint256 jobId)` | Settles: provider payment, burn, treasury fee. |\n| `expireJob(uint256 jobId)` | Refunds escrow if no assignment by the bid deadline. |\n| `timeoutJob(uint256 jobId)` | Slashes the assigned provider `TIMEOUT_SLASH_BPS` (5%) on a missed execution deadline. |\n| `failJob(uint256 jobId)` | Provider self-reports failure. |\n| `disputeResult(uint256 jobId) payable` | Files a dispute with a `DISPUTE_BOND` of 10 SALT. |\n| `resolveDispute(uint256 jobId, bool requesterWins)` | `onlyGovernance`. |\n| `registerProvider(bytes32[] supportedModels) payable` / `addStake() payable` | Onboarding; stake at least `MIN_PROVIDER_STAKE` (1000 SALT). |\n\nViews include `getJob`, `getJobBids`, `getProvider`, `getProviderCount`, and `providerSupportsModel`.\nGovernance setters are `setTreasury`, `setSlashingContract`, `setBurner`, `setBulkGateway`, and\n`setPricingOracle`.\n\nThe settlement receipt is one event:\n\n```solidity\nevent JobCompleted(\n uint256 indexed jobId,\n address indexed provider,\n uint256 providerPayment,\n uint256 burned,\n uint256 treasuryFee\n);\n```\n\nThe split is 95% to the provider, 2.5% burned, and 2.5% to the treasury. The burn and the fee each\nequal the price divided by `BME_BURN_DIVISOR` and `TREASURY_DIVISOR`, both 40. The lifecycle is captured\nin `enum JobState { Posted, Bidding, Assigned, Executing, Verifying, Completed, Expired, Timeout, Failed,\nDisputed }`, and the payment choice in `enum PaymentMethod { SALT, BulkCredits }`.\n\n### ComputePool\n\n`contracts/src/ComputePool.sol`, `contract ComputePool is ReentrancyGuard, Governable`. Multi-provider\nGPU pools with per-GPU staking, payment shared in proportion to GPU contribution, slashing on a missed\nthroughput guarantee, and a coordinator election. The constructor takes no arguments; the deployer\nbecomes governance.\n\n| Function | Notes |\n|---|---|\n| `createPool(string name, PoolMode mode, uint256 minProviders, uint256 guaranteedThroughput, uint256 pricePerUnit) returns (uint256 poolId)` | The creator does not auto-join. |\n| `joinPool(uint256 poolId, uint256 gpuCount) payable` | Stake `MIN_STAKE_PER_GPU` (10 SALT) per GPU. |\n| `leavePool(uint256 poolId)` / `dissolvePool(uint256 poolId)` | Leaving requires no active jobs; dissolve is creator-only. |\n| `pausePool(uint256 poolId)` / `resumePool(uint256 poolId)` | Creator only. |\n| `requestPoolCompute(uint256 poolId, bytes jobSpec, uint256 maxPrice) payable returns (uint256 jobId)` | Legacy bytes spec. |\n| `requestPoolComputeStruct(uint256 poolId, PoolJobSpec spec, uint256 maxPrice) payable returns (uint256 jobId)` | Typed spec; requires `spec.version == 1`. |\n| `completeJob(uint256 jobId)` / `failJob(uint256 jobId)` | Callable by governance, the pool creator, or the dispatcher. |\n| `reclaimExpiredJob(uint256 jobId)` | Requester refund after `JOB_DEADLINE` (600 blocks). |\n| `reportSLAViolation(uint256 poolId, uint256 actualThroughput)` | `onlyGovernance`; slashes `SLA_PENALTY_BPS` (10%). |\n| `recordDispatch(uint256 jobId)` / `reassignCoordinator(uint256 jobId)` | The elected coordinator records dispatch; a member can reassign after `COORDINATION_TIMEOUT` (20 blocks). |\n\nViews include `getPool`, `getPoolMembers`, `getPoolGPUCount`, `getMember`, `getJob`, `isPoolSolvent`,\n`coordinatorFor(poolId, epoch)`, and `decodePoolJobSpec`. The pool kinds are `enum PoolMode {\nInferencePool, DataParallel, PipelineParallel }`.\n\n### ComputePoolTraining\n\n`contracts/src/ComputePoolTraining.sol`, `contract ComputePoolTraining is ReentrancyGuard, Governable`.\nA distributed-training lifecycle: recruit workers, commit one Merkle root per epoch, challenge a step\nwith a fraud proof, finalize. Only the per-epoch roots are stored on-chain; the individual step\ncommitments live off-chain in the training mesh. The constructor takes a governance address.\n\n| Function | Notes |\n|---|---|\n| `requestTrainingJob(TrainingJobSpec spec) payable returns (uint256 jobId)` | `msg.value` must equal the per-epoch budget times the epoch count. |\n| `joinTrainingJob(uint256 jobId) payable` | A worker posts the per-worker stake. |\n| `closeRecruitment(uint256 jobId, address coordinator_)` | Permissionless once the minimum workers have joined. |\n| `commitEpoch(uint256 jobId, uint32 epoch, bytes32 root)` | Coordinator commits an epoch's Merkle root; pays that epoch's budget across the workers still in good standing. |\n| `challengeStep(uint256 jobId, uint32 epoch, uint32 step, address target, bytes32 leaf, bytes32[] merkleProof) payable` | Fraud proof; `CHALLENGE_BOND` is 1 SALT. |\n| `voteChallenge(uint256 jobId, uint32 epoch, uint32 step, address target, bool uphold)` | Committee vote; `COMMITTEE_QUORUM` is 2. |\n| `reassignCoordinator(uint256 jobId, address newCoordinator)` | After `COORDINATION_TIMEOUT` (100 blocks). |\n| `finalizeTrainingJob(uint256 jobId)` / `abortRecruiting(uint256 jobId)` | Finalize after the challenge window; abort returns stakes. |\n| `claimDeferredPayout(uint256 jobId)` | Pull-payment fallback when a push payout fails. |\n| `setCommittee(address member, bool active)` | `onlyGovernance`. |\n\nViews include `getJob`, `getWorker`, `getWorkerList`, `getEpochRoot`, `getChallenge`, and `heldStake`.\nThe job states are `enum JobState { Recruiting, Training, Awaiting, Finalized, Aborted }` and a challenge\nruns through `enum ChallengeState { None, Voting, ResolvedUphold, ResolvedReject }`.\n\n### ComputeVerifier\n\n`contracts/src/ComputeVerifier.sol`, `contract ComputeVerifier is ReentrancyGuard, Governable`. The\noutput-verification dispatcher. Every entry point is `onlyMarketplace`: the marketplace configures a\njob's tier, then asks the verifier to rule. There are three tiers, plus a bisection-bounded dispute\npath. The constructor takes the marketplace address.\n\n| Function | Notes |\n|---|---|\n| `configureJob(uint256 jobId, uint256 value, VerificationTier requestedTier)` | Marketplace registers a job's tier. |\n| `overrideTierToTEE(uint256 jobId)` | Forces the TEE tier. |\n| `submitCommitment(uint256 jobId, address provider, bytes32 commitment)` | Tier-1 commit. |\n| `verify(uint256 jobId, VerificationTier tier, bytes proofData) returns (VerificationResult)` | Dispatches to the tier handler. |\n| `verifyCommitment(uint256 jobId, bytes32 commitment, bytes output, bytes32 nonce) returns (bool)` | Tier 1. |\n| `verifyZKProof(uint256 jobId, bytes proof, bytes publicInputs) returns (bool)` | Tier 2; calls the precompile at `0x0108` by `staticcall`. |\n| `verifyTEEAttestation(uint256 jobId, bytes attestation, bytes signature) returns (bool)` | Tier 3; a 65-byte signature recovered against a registered TEE oracle. |\n| `initiateDispute(uint256 jobId)` / `performBisectionStep(uint256 jobId)` | `MAX_BISECTION_ROUNDS` is 10. |\n| `resolveDispute(uint256 jobId, VerificationResult outcome)` | Governance or marketplace. |\n| `addTEEOracle(address)` / `removeTEEOracle(address)` / `setMarketplace(address)` | `onlyGovernance`. |\n\nThe tiers are `enum VerificationTier { Commitment, ZKProof, TEE }` and the verdict is\n`enum VerificationResult { Pending, Valid, Invalid }`. A job above `VALUE_THRESHOLD` (10 SALT) cannot\nsettle on a bare commitment; the verifier upgrades it to the ZK tier. The constant\n`INFERENCE_PROOF_VERIFY = address(0x0108)` points at the Halo2-KZG verifier precompile, described in\n[precompiles](/chain/precompiles). The ZK tier is a research preview (the v1 circuit is small, not a\nfull-model proof), and hardening is in progress. The TEE tier is inert on chain 40204 today because no TEE\noracle is registered with `ComputeVerifier` (`teeOracleCount()` returns 0).\n\n### ComputePricingOracle\n\n`contracts/src/ComputePricingOracle.sol`, `contract ComputePricingOracle is IComputePricingOracle,\nGovernable` (interface at `contracts/src/interfaces/IComputePricingOracle.sol`). A quorum oracle that\nmaps compute cost in USD cents per PFLOP-hour and SALT price in USD cents, by a 67% committee vote, rate\nlimited to a 10% change per update, with staleness tracking. The constructor takes the two opening\nprices, both of which must be above zero.\n\n| Function | Notes |\n|---|---|\n| `addOracleMember(address)` / `removeOracleMember(address)` | `onlyGovernance`. |\n| `proposeComputePrice(uint256 newPrice)` / `proposeSaltPrice(uint256 newPrice)` | `onlyOracle`; the price updates once the vote reaches quorum. |\n| `saltPerPflopHour() view returns (uint256)` | Current SALT cost of one PFLOP-hour. |\n| `computeToSalt(uint256 pflopHours) view returns (uint256 saltCost)` | Converts PFLOP-hours to SALT. |\n| `estimateJobCost(bytes32 modelHash, uint256 inputTokens, uint256 outputTokens, uint8 verificationTier) view returns (uint256 saltCost)` | `modelHash` is reserved for future per-model overrides and is unused today. |\n| `isPriceStale() view returns (bool)` | True after `MAX_STALENESS` (7200 blocks). |\n\nKey constants: `QUORUM = 67`, `MAX_STALENESS = 7200`, `MAX_PRICE_CHANGE_BPS = 1000`,\n`TOKENS_TO_PFLOP_FACTOR = 1e12`, and verification multipliers of 1.0x for Commitment, 1.5x for ZK, and\n2.0x for TEE.\n\n### BulkComputeGateway\n\n`contracts/src/BulkComputeGateway.sol`, `contract BulkComputeGateway is ReentrancyGuard, Governable`.\nLets an institution buy compute credits in PFLOP-hours with a stablecoin, routed to a treasury and\npriced through the oracle; an authorized spender, typically the marketplace, debits those credits. The\nconstructor takes a treasury, an oracle, and a governance address.\n\n| Function | Notes |\n|---|---|\n| `purchaseComputeCredits(address stablecoin, uint256 amount) returns (uint256 creditsReceived)` | `MIN_PURCHASE_USD` is $10.00 at 6 decimals. |\n| `spendCredits(address institution, uint256 creditAmount) returns (bool success)` | `onlyAuthorizedSpender`. |\n| `getCreditBalance(address institution) view returns (uint256 credits)` | Current credit balance. |\n| `estimateCallsRemaining(address institution, uint256 avgTokensPerCall) view returns (uint256)` | Rough call budget. |\n| `currentCreditPriceUsd() view returns (uint256 priceUsd6)` | Credit price in USD at 6 decimals. |\n| `getPurchaseHistory(address institution) view returns (Purchase[])` | Plus `purchaseCount` and `institutionPurchaseCount`. |\n| `authorizeSpender` / `revokeSpender` / `setOracle` / `setTreasury` | `onlyGovernance`. |\n\nThe receipt is `event CreditsPurchased(address indexed institution, address indexed stablecoin, uint256\nusdAmount, uint256 creditsReceived, uint256 purchaseIndex)`.\n\n## Design rationale\n\nThe split between on-chain record and off-chain work is the whole point. A model run is large and\nprivate, so it happens on the operator's own hardware; the ledger keeps only the hash, the proof, and\nthe receipt. That is why ComputeVerifier offers three tiers rather than one. A small job can settle on a\ncheap commitment, a job above the value threshold must carry a zero-knowledge proof checked by the\n`0x0108` precompile (a research preview), and a job that needs hardware attestation is designed to\nrequire a TEE oracle signature (inert on 40204 until an oracle is registered). The buyer\nchooses how much assurance to pay for, and the contract enforces a floor for high-value work.\n\nThe training pool stores only one Merkle root per epoch. Putting every step commitment on-chain would be\nruinous, so the design keeps the steps in the off-chain mesh and lets any worker challenge a step with a\nfraud proof. The chain has to store little and still adjudicate honestly. Pricing sits behind a quorum\noracle rather than a single feed so that no one member can move the price more than 10% or push a stale\nnumber through.\n\n## Failure modes\n\nThese contracts hold escrow and stake, so the failure paths matter.\n\n- **A provider misses the execution deadline.** Anyone calls `timeoutJob`, which slashes 5% of the\n provider's stake. In this build the escrow is refunded to the requester rather than held for\n reassignment; there is no reassignment path yet.\n- **A returned result is wrong.** The verifier fails closed. A ZK proof that reverts, returns the wrong\n length, or returns anything other than the success word is treated as Invalid, so a bad proof never\n settles as valid. Above the value threshold a bare commitment is rejected and upgraded to the ZK tier.\n- **A buyer disputes a result.** They post the 10 SALT dispute bond and the marketplace runs a bounded\n bisection, at most 10 rounds, with governance or the marketplace resolving the outcome.\n- **A pool misses its throughput guarantee.** Governance calls `reportSLAViolation`, which slashes 10%\n of the pool's stake.\n- **A training worker commits a bad step.** A challenger posts the 1 SALT bond and submits a Merkle\n fraud proof; a committee vote at a quorum of 2 upholds or rejects it, and an upheld challenge slashes\n the cheating worker.\n\nThe pool coordinator election depends on an off-chain coordinator binary to record seeds, because the\non-chain `coordinatorFor` derives randomness from a recent block hash and that is only reliable for the\nmost recent few hundred blocks. Off-chain proof generators for both ZK verification and training Merkle\nproofs must match the on-chain wire format exactly, or valid-looking proofs will be rejected.\n\n## Access and canon\n\nTier: commercial for the marketplace, pools, gateway, and oracle, and academic for ComputeVerifier,\nwhich is the formal-methods and proof surface. These contracts are open on the public ledger and anyone\ncan read them; the full lifecycle, scoring, and settlement design is paid-seat depth.\n\nNo secrets appear on this page. There are no private keys, mnemonics, internal hostnames, or\ncredentials. The only hardcoded address is the public protocol precompile `0x0108`. Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity.\n\n## Source and verification\n\n- Source: `citrate-chain/contracts/src/`, in `ComputeMarketplace.sol`, `ComputePool.sol`,\n `ComputePoolTraining.sol`, `ComputeVerifier.sol`, `BulkComputeGateway.sol`, `ComputePricingOracle.sol`,\n and the interface `interfaces/IComputePricingOracle.sol`.\n- Audited against `citrate-chain` SHA `9d5959e`.\n- Status: Implemented, pre-audit, on testnet 40204. The timeout-reassignment gap, the off-chain\n coordinator-seed dependency, and the assumed 6-decimal stablecoins are open items noted above and not\n yet externally audited. Re-verify deployed bytecode with `eth_getCode` if the chain has been re-rolled.\n"},"/contracts/economics":{"slug":"/contracts/economics","title":"Economics contracts","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/contracts/src/{WrappedSALT,LiquidStakingPool,IPFSIncentives,ContributionAccounting,StablecoinTreasury,MarketMakerAllocation}.sol","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"WrappedSALT","anchor":"wrappedsalt"},{"depth":3,"text":"LiquidStakingPool","anchor":"liquidstakingpool"},{"depth":3,"text":"IPFSIncentives","anchor":"ipfsincentives"},{"depth":3,"text":"ContributionAccounting","anchor":"contributionaccounting"},{"depth":3,"text":"StablecoinTreasury","anchor":"stablecointreasury"},{"depth":3,"text":"MarketMakerAllocation","anchor":"marketmakerallocation"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"These are the on-premise economic primitives that settle work on the Citrate Network: a wrapped form of\nSALT that signed payments can move, a staking pool that lets staked SALT stay liquid, storage incentives\nfor content kept on IPFS, an accounting contract that tracks the work done so rewards follow it, a treasury\nfor stablecoin revenue, and an allocation that funds the network's market maker. This page is for the\ndevelopers and integrators building against those contracts on chain id 40204.\n\n## What it is\n\nSALT is the unit the Citrate Network counts in. It settles the work the network performs; it is not a\nproduct to hold. The contracts here are the on-chain machinery that records that work and pays it out, and\nnothing on this page treats SALT as an instrument to speculate on. For the base facts about SALT, supply,\nand gas, read [network economics](/chain/economics).\n\nEach contract does one job:\n\n| Surface | Contract | What it settles |\n|---|---|---|\n| SC-econ-wrappedSALT | WrappedSALT (wSALT) | native SALT wrapped as an ERC-20 so a signed authorization can move it |\n| SC-econ-staking | LiquidStakingPool (stSALT) | SALT staked into a pool, with a liquid share token in return |\n| SC-econ-ipfs | IPFSIncentives | rewards for operators who pin model and data content on IPFS |\n| SC-econ-contrib | ContributionAccounting | the record of seven kinds of work, weighted, with rewards split by score |\n| SC-econ-stable | StablecoinTreasury | stablecoin revenue from institutional compute purchases |\n| SC-econ-mm | MarketMakerAllocation | the gas-fee share that funds the network's market maker |\n\nThe first four are public; a builder needs them and their on-chain interfaces are public anyway.\nStablecoinTreasury and MarketMakerAllocation are tier commercial: the contract bytecode is public on chain,\nbut the narrative around the network's revenue and its market-maker arrangement is written for contracted\nprincipals. No keys or private endpoints appear on this page.\n\n## How to use it\n\nYou read and call these contracts the same way you would any contract on an EVM-compatible network.\n\n1. Point a client at the Citrate Network and confirm the chain id is 40204, as shown in\n [what Citrate is](/start/what-is-citrate).\n2. Resolve the address you want from the canonical registry, `contracts/addresses/40204.json`, rather than\n copying an address from prose. The keys there are the contract names used below.\n3. For a read, call a view function over `eth_call`. For a write, send a transaction signed by an account\n that holds the right role; the access column in each table below tells you which.\n4. Confirm an address holds the code you expect with `eth_getCode` before you send value to it.\n\nFor a network-wide snapshot of supply, gas price, staked amount, and treasury in a single call, use\n`citrate_getEconomicState`, documented in [chain RPC](/chain/rpc).\n\n## Reference\n\nEvery function below is read from the cited `.sol` file at the audited SHA. If a symbol is not listed here,\nit is not in the contract at this SHA.\n\n### WrappedSALT\n\n`contracts/src/WrappedSALT.sol`, `is IERC3009, ReentrancyGuard`. An ERC-20 wrapper around native SALT,\n`name=\"Wrapped SALT\"`, `symbol=\"wSALT\"`, `decimals=18`. It adds EIP-3009 authorized transfers so a holder\ncan sign a transfer that someone else submits, which is what the x402 payment flow relies on. The EIP-712\ndomain separator is rebuilt whenever `block.chainid` changes, so an authorization signed before a re-genesis\ncannot be replayed against the new chain.\n\n| Function | Purpose |\n|---|---|\n| `deposit()` / `receive()`, payable | wrap native SALT into wSALT |\n| `withdraw(uint256 amount)` | unwrap wSALT back into native SALT |\n| `transfer`, `approve`, `transferFrom` | standard ERC-20 |\n| `transferWithAuthorization(from, to, value, validAfter, validBefore, nonce, v, r, s)` | EIP-3009 signed transfer; the holder signs, anyone submits |\n| `transferWithFeeAuthorization(from, to, treasury, value, fee, validAfter, validBefore, nonce, v, r, s)` | one signed authorization for the gross `value`, split inside the contract into `value - fee` to `to` and `fee` to `treasury` |\n| `receiveWithAuthorization(...)` | pull-style EIP-3009 transfer; the caller must be the payee |\n| `cancelAuthorization(authorizer, nonce, v, r, s)` | cancel an unused authorization |\n| `DOMAIN_SEPARATOR()` | the current-chain EIP-712 domain separator |\n| `authorizationState(authorizer, nonce)` | whether a nonce has been used or cancelled |\n\nThe fee-bearing path binds `(treasury, fee)` into the signed digest through a distinct type hash,\n`TRANSFER_WITH_FEE_AUTHORIZATION_TYPEHASH`, so a submitter cannot redirect the fee leg to another address.\nEvents: `Transfer`, `Approval`, `Deposit`, `Withdrawal`, `AuthorizationUsed`, `AuthorizationCanceled`. The\nerror `InvalidFeeAuthorization` reverts a fee transfer whose signature does not recover to `from`.\n\n### LiquidStakingPool\n\n`contracts/src/LiquidStakingPool.sol`, `is ReentrancyGuard, Governable`. A shares-based staking pool,\n`name=\"Staked SALT\"`, `symbol=\"stSALT\"`, `decimals=18`. You deposit SALT and receive stSALT shares; rewards\nreported to the pool raise the value each share is worth, so the staked SALT stays usable as a share token\nwhile it earns. Withdrawals wait out `WITHDRAWAL_DELAY` blocks before they can be claimed.\n\n| Function | Access | Purpose |\n|---|---|---|\n| `deposit()`, payable | any | stake SALT, mint stSALT shares, returns `sharesOut` |\n| `requestWithdrawal(uint256 shareAmount)` | any | burn shares and queue the SALT release, returns `requestId` |\n| `claimWithdrawal(uint256 requestId)` | the requester | claim once `WITHDRAWAL_DELAY` has passed |\n| `reportRewards(uint256 rewards, uint256 slashed)` | an oracle | report rewards or slashing; applied at quorum |\n| `depositCollateral()` / `withdrawCollateral(uint256)` | a compute provider | post or remove provider collateral |\n| `slashProvider(address, uint256)` | governance | slash a provider's collateral back into the pool |\n| `donate()`, payable | any | add SALT without changing the share price |\n| `addOracle(address)` / `removeOracle(address)` | governance | manage the oracle committee |\n| `getSharePrice()`, `balanceOf(address)`, `previewDeposit(uint256)`, `previewWithdraw(uint256)` | view | share and value math |\n| `transferGovernance` / `acceptGovernance` | inherited | two-step governance handover |\n\nConstants: `WITHDRAWAL_DELAY = 50400` blocks, about seven days; `ORACLE_QUORUM = 67`, the percentage of the\ncommittee that must agree before a report applies; `MIN_COLLATERAL_BPS = 1000`; `MAX_REWARD_RATE_BPS =\n20000`; `MAX_SLASH_RATE_BPS = 1000`. Oracle reports carry a per-nonce replay guard, the committee must agree\non the same `(rewards, slashed)` values, and the caps bound how much any single report can move the pool.\nEvents: `Deposited`, `WithdrawalRequested`, `WithdrawalClaimed`, `RewardsReported`, `ProviderSlashed`,\n`OracleAdded`, `OracleRemoved`, `CollateralDeposited`, `CollateralWithdrawn`, `Donated`.\n\n### IPFSIncentives\n\n`contracts/src/IPFSIncentives.sol`, `is AccessControl, ReentrancyGuard`. This is the deployed storage\nincentive, a report-and-claim model. An authorized reporter attests that an operator pinned a content id of\na given size and model type, the contract accrues a reward for that pin, and the operator later withdraws\nthe accrued SALT.\n\n| Function | Access | Purpose |\n|---|---|---|\n| `reportPinning(string cid, uint256 sizePinned, ModelType modelType)` | `REPORTER_ROLE` | attest a pin and accrue its reward |\n| `claimRewards()` | any | withdraw accrued SALT |\n| `depositRewards()`, payable | `DEFAULT_ADMIN_ROLE` | fund the reward pool |\n| `updateBaseReward(uint256 newRate)` | `DEFAULT_ADMIN_ROLE` | set the base reward rate |\n| `calculateReward(uint256 sizePinned, ModelType)` | view | preview a reward |\n| `getModelPinners(string cid)` | view | the operators pinning a content id |\n\nEvents: `PinReported`, `RewardClaimed`, `BaseRewardUpdated`, `RewardsDeposited`. Two later designs exist in\nthe tree, a sealed proof-of-replication mechanism (`IPFSIncentivesV2.sol`) and a commit-reveal extension of\nit (`IPFSIncentivesV3.sol`) with a grief-slashable wrong-CommD challenge. Both are now listed in the\ncanonical registry (`contracts/addresses/40204.json`); this page documents version one's surface, and the\nsource and verification note points to the later versions.\n\n### ContributionAccounting\n\n`contracts/src/ContributionAccounting.sol`, `is Governable`. This is the record that lets rewards follow\nwork. It tracks contributions across seven types, weights each type, keeps a cached score per contributor,\nand splits a funded pool in proportion to those scores. The seven types are `Validation`, `ModelHosting`,\n`AdapterCreation`, `DataProvision`, `AppDevelopment`, `BridgeInfra`, and `Governance`. Weights are basis\npoints, where 10000 is a one-times multiplier.\n\n| Function | Access | Purpose |\n|---|---|---|\n| `recordContribution(address contributor, ContributionType ctype, uint256 amount)` | a recorder or governance | record work and refresh the contributor's score |\n| `recordDimensionContribution(address contributor, bytes32 dimension, uint256 amount)` | a recorder or governance | record a per-dimension running total |\n| `fundRewards()`, payable | any | add SALT to the reward pool |\n| `distributeRewards()` | governance | freeze each contributor's share of the pool by score |\n| `claimRewards()` | any | withdraw a claimable balance |\n| `updateWeight(ContributionType ctype, uint256 newWeight)` | governance | change a type's weight and rescore every contributor |\n| `addRecorder(address)` / `removeRecorder(address)` | governance | manage the recorder allowlist |\n| `getScore(address)`, `pendingReward(address)`, `contributorCount()`, `getContributorListPage(offset, limit)`, `getDimensionScore(address, bytes32)` | view | read scores, pending shares, and the contributor list |\n\nThe active contributor list is capped at `MAX_CONTRIBUTORS = 1024` so distribution and rescoring stay within\na bounded gas budget. `distributeRewards` allocates shares first and lets each contributor claim later, so\nclaim order does not change anyone's amount. Events: `ContributionRecorded`,\n`DimensionContributionRecorded`, `RewardsDistributed`, `RewardClaimed`, `WeightUpdated`, `RecorderAdded`,\n`RecorderRemoved`.\n\n### StablecoinTreasury\n\n`contracts/src/StablecoinTreasury.sol`, `is ReentrancyGuard, Governable`. Tier commercial. It accumulates\nstablecoins from institutional compute purchases across an allowlist of accepted tokens, tracks revenue and\nactivity per epoch, and distributes to recipients under governance control. No SALT is involved; it moves\nERC-20 stablecoins only, and assumes each accepted token is pegged one-to-one to the US dollar.\n\n| Function | Access | Purpose |\n|---|---|---|\n| `deposit(address stablecoin, uint256 amount)` | any | deposit an accepted stablecoin |\n| `distribute(address stablecoin, address[] recipients, uint256[] amounts)` | governance | distribute to a length-matched list of recipients |\n| `addStablecoin(address)` / `removeStablecoin(address)` | governance | manage the accepted-token allowlist |\n| `recordActivity(uint256 jobCount, uint256 inferenceCount)` | an authorized activity recorder | record per-epoch compute and inference counts |\n| `emergencyWithdraw(address stablecoin, address to)` | governance | move a stablecoin's full balance to a governance address |\n| `totalValueLocked()`, `stablecoinCount()`, `getAcceptedStablecoins()`, `getEpochRevenue(uint256)`, `getCurrentEpoch()` | view | read balances, the allowlist, and epoch data |\n\nAn epoch is `EPOCH_LENGTH = 1000` blocks; the allowlist is capped at `MAX_STABLECOINS = 20`. Events:\n`StablecoinAdded`, `StablecoinRemoved`, `Deposited`, `Distributed`, `EmergencyWithdrawal`, `EpochAdvanced`,\n`ActivityRecorderSet`, `ActivityRecorded`.\n\n### MarketMakerAllocation\n\n`contracts/src/MarketMakerAllocation.sol`, `is Governable`. Tier commercial. It receives a share of gas-pool\nfees, skimmed before the network's wider revenue split, to fund the market maker who provides liquidity and\nhandles listings. The market maker withdraws the accumulated SALT; governance can change the rate and the\nmarket-maker address.\n\n| Function | Access | Purpose |\n|---|---|---|\n| `receive()`, payable | the block producer or revenue distributor | take the gas-fee allocation |\n| `withdraw(uint256 amount)` | the market maker | withdraw part of the accrued SALT |\n| `withdrawAll()` | the market maker | withdraw the full balance |\n| `changeMarketMaker(address newMaker, string reason)` | governance | replace the market-maker address with a recorded reason |\n| `changeAllocationRate(uint256 newBps)` | governance | change the skim rate within its bounds |\n| `calculateAllocation(uint256 gasFees)` | view | preview the skim and the remainder |\n| `availableBalance()`, `changeHistoryCount()` | view | read the balance and the change history |\n| `transferGovernance` / `acceptGovernance` | inherited | two-step governance handover |\n\nThe default rate is `allocationBps = 1000`, ten percent. It is bounded between `MIN_ALLOCATION_BPS = 100` and\n`MAX_ALLOCATION_BPS = 1500`, and a rate change is held off until `RATE_CHANGE_COOLDOWN = 302400` blocks,\nabout seven days, have passed since the last change. Events: `AllocationReceived`, `Withdrawn`,\n`MarketMakerChanged`, `AllocationRateChanged`.\n\n## Design rationale\n\nThe shape of these contracts follows from one rule: pay for work that happened, and keep the accounting\nhonest while you do it. The staking pool uses a shares model so a staker's claim grows with reported rewards\nwithout a separate bookkeeping pass, and it holds the share price steady against a first-depositor\nmanipulation by computing shares against a virtual offset rather than the raw balance. The storage incentive\nkeeps the deployed version a simple report-and-claim, because the sealed-proof and commit-reveal designs add\nreal complexity and are not yet the live mechanism. ContributionAccounting freezes each round's shares at\ndistribution time and bounds its contributor set, so a reward split cannot be reordered for advantage or\npriced out by gas. The two commercial contracts are kept narrow and governance-gated because they touch the\nnetwork's revenue and a single strategic relationship, and the cooldown and rate bounds on the market-maker\nskim keep that relationship from quietly drifting.\n\n## Failure modes\n\nThese contracts move value, so the relevant question is how each one fails closed.\n\n- WrappedSALT will not unwrap more than an account holds, and the fee path reverts with\n `InvalidFeeAuthorization` rather than settling if the signed digest does not recover to `from`. The\n rebuilt domain separator means an authorization signed before a chain-id change cannot be replayed after\n one.\n- LiquidStakingPool applies a reward report only when a 67 percent quorum agrees on the same values, rejects\n a report that exceeds its caps, and rejects unsolicited SALT through `receive()` so that the only ways in\n are `deposit()`, which mints shares, and `donate()`, which does not move the share price.\n- ContributionAccounting refuses a new contributor once the cap is reached rather than running an unbounded\n loop, and recomputes scores on a weight change so the split stays consistent.\n- StablecoinTreasury distributes only up to the recorded balance and requires recipient and amount arrays of\n equal length; its emergency path moves funds to a governance address, not an arbitrary one.\n- MarketMakerAllocation caps the skim rate and enforces a cooldown between changes, so a single change cannot\n push the allocation past its bounds or be repeated rapidly.\n\n## Access and canon\n\nWrappedSALT, LiquidStakingPool, IPFSIncentives, and ContributionAccounting are public, the open primitives a\nbuilder needs, with on-chain interfaces that are public by nature. StablecoinTreasury and\nMarketMakerAllocation are tier commercial: the deployed bytecode and the interface are public on chain, but\nthe narrative around the network's revenue and its market-maker arrangement is written for contracted\nprincipals. No keys, mnemonics, private endpoints, or credentials appear on this page or are needed to read\nthese contracts. The deployed addresses are public testnet values.\n\n## Source and verification\n\n- Source repo: `citrate-chain` at SHA `9d5959e`.\n- Files: `contracts/src/WrappedSALT.sol`, `contracts/src/LiquidStakingPool.sol`,\n `contracts/src/IPFSIncentives.sol`, `contracts/src/ContributionAccounting.sol`,\n `contracts/src/StablecoinTreasury.sol`, `contracts/src/MarketMakerAllocation.sol`.\n- Addresses: the canonical registry is `contracts/addresses/40204.json` for chain 40204. At this SHA it\n lists all three, `IPFSIncentives` (version one), `IPFSIncentivesV2`, and `IPFSIncentivesV3`; the deploy\n script `contracts/script/DeployAll.s.sol` deploys version one, and V2 and V3 are deployed and registered\n separately. The superseded `contracts/DEPLOYED_ADDRESSES.md` is no longer the source of truth, so resolve\n from `addresses/40204.json` and confirm with `eth_getCode` before sending value.\n- Status: Implemented, pre-audit. The contracts run on testnet 40204 and carry remediations from the\n SECREM-01 and re-audit sprints in their source comments, with Foundry invariant suites in places, but no\n external third-party audit has been completed. Treat as experimental.\n"},"/contracts/governance":{"slug":"/contracts/governance","title":"Governance Contracts","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/contracts/src/{TreasuryGovernor,DisputeResolution,AgentDecisionRegistry,SpecRegistry}.sol","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"TreasuryGovernor","anchor":"treasurygovernor"},{"depth":3,"text":"DisputeResolution","anchor":"disputeresolution"},{"depth":3,"text":"AgentDecisionRegistry","anchor":"agentdecisionregistry"},{"depth":3,"text":"SpecRegistry","anchor":"specregistry"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The governance surface is four contracts that decide how the network spends its treasury, how it settles a\ndisputed compute result, and how it keeps an accountable record of what its agents do. They sit on the\npublic ledger, Citrate Network, and any contracted integrator should expect to read and call them.\n\n## What it is\n\nGovernance on Citrate is not one monolith. It is four small contracts, each with one job, so that a\ntreasury vote, a compute dispute, an agent audit record, and a formal-specification pin never share a\nfailure surface. Three of the four inherit a common ownership mixin, `Governable`, which hands authority\nover in two steps: the sitting governance account proposes a successor with `transferGovernance`, and the\nsuccessor must call `acceptGovernance` before it takes effect, with `cancelGovernanceTransfer` available\nbefore acceptance. A mistyped or unreachable successor therefore cannot lock governance, because the\nhandover does not complete until the new account acts.\n\n| Contract | Role | Status |\n|---|---|---|\n| `TreasuryGovernor` | On-chain governor for SALT and stSALT treasury spending | Implemented, pre-audit |\n| `DisputeResolution` | Bisection game that settles a challenged compute result | Implemented, TLA+ specified |\n| `AgentDecisionRegistry` | Audit trail of high-risk agent tool calls, with trust tiers | Implemented, pre-audit |\n| `SpecRegistry` | Domain to IPFS map of the behavioral specs agents check | Implemented, pre-audit |\n\nThe four are pre-audit. They carry inline remediation notes from internal review, for example the SOL-21\ngovernance follow-on, but they have not completed a final third-party audit. Treat every address and\nparameter as subject to change before mainnet.\n\n## How to use it\n\nMost readers will interact with one contract at a time. The two paths worth walking end to end are a\ntreasury spend and a compute dispute.\n\n1. **Move treasury funds.** Acquire the voting power, `PROPOSAL_THRESHOLD` is 10,000 SALT, then call\n `proposeTreasurySpend`. Voters call `castVote` over the voting window. After the window, call `queue`,\n wait out `EXECUTION_DELAY`, then call `execute`. Two proposal types execute on-chain: `TreasurySpend`\n calls `treasury.distribute`, and `Call` performs a generic `(target, value, calldata)` call so the\n governor can drive any governance function on a target it controls. `ParameterChange`, `OracleUpdate`,\n and `Emergency` emit an event for an off-chain multisig to act on.\n2. **Dispute a compute result.** As the challenger, call `initiateDispute` with the job and the step range,\n posting the bond. The defender calls `acknowledgeDispute` with a matching bond. The challenger narrows\n the range with `bisect` each round, the defender commits a step with `respond`, and the governance\n referee ends it with `resolve`. If either side stalls past the deadline, anyone calls `timeoutDispute`\n and the challenger wins.\n3. **Record an agent decision.** An authorized recorder calls `registerDecision` (or\n `registerDecisionWithTierCheck`) with the agent id, the tool name, and a hash of the parameters. An\n authorized disputer can file against it; governance resolves with `resolveDispute`.\n4. **Pin a behavioral spec.** Governance calls `registerSpec` with the operation domain and the IPFS CID of\n the Gherkin feature file, and `updateSpec` to bump the version. Agents read `getSpec` before a critical\n operation.\n\n## Reference\n\nThe audited surface, each item citing its source file under `contracts/src/`.\n\n### TreasuryGovernor\n\nSource: `contracts/src/TreasuryGovernor.sol`. A full on-chain governor for treasury operations. Voting\npower is the voter's native SALT balance plus their stSALT shares valued at the share price read from\n`LiquidStakingPool`. The lifecycle is propose, vote, queue behind a timelock, then execute. Parameters are\nfixed as constants and match `core/economics/src/governance.rs`.\n\n| Constant | Value | Meaning |\n|---|---|---|\n| `PROPOSAL_THRESHOLD` | 10,000 SALT | Minimum voting power to open a proposal |\n| `VOTING_PERIOD` | 50,400 blocks | Voting window, measured in blocks (wall-clock depends on block time) |\n| `EXECUTION_DELAY` | 7,200 blocks | Timelock before a queued proposal can execute |\n| `QUORUM_BPS` | 1,000 (10%) | Quorum as basis points of total supply |\n| `APPROVAL_BPS` | 6,000 (60%) | Approval threshold as basis points of votes cast |\n| `GRACE_PERIOD` | 50,400 blocks | Window to execute before a queued proposal expires |\n| `EMERGENCY_THRESHOLD_MULTIPLIER` | 3 | Emergency proposals need three times the threshold |\n\nProposal creation, each `payable`, each returning a `proposalId`:\n\n- `proposeTreasurySpend(title, description, stablecoin, recipients[], amounts[])`\n- `proposeParameterChange(title, description, parameterKey, parameterValue)`\n- `proposeOracleUpdate(title, description, target, newOracle)`\n- `proposeCall(title, description, target, value, data)`, generic on-chain execution against a governed target\n- `proposeEmergency(title, description)`, which requires three times the threshold\n\nVoting and lifecycle:\n\n- `castVote(proposalId, support)`, where `VoteType` is `For` (0), `Against` (1), `Abstain` (2)\n- `queue(proposalId)`, only from the `Succeeded` state\n- `execute(proposalId)`, guarded by `nonReentrant`, re-checks quorum and approval, runs the treasury call\n- `cancel(proposalId)`, by the proposer or the guardian\n- `acceptGovernanceOf(target)`, a permissionless completion of a `Governable` handover where this governor\n is the pending successor\n- `transferGuardian(newGuardian)`, `onlyGuardian`\n\nViews: `state`, `getVotingPower`, `getProposal`, `quorumThreshold`, `getSpendDetails`. Events:\n`ProposalCreated`, `VoteCast`, `ProposalQueued`, `ProposalExecuted`, `ProposalCanceled`,\n`GuardianTransferred`.\n\n### DisputeResolution\n\nSource: `contracts/src/DisputeResolution.sol`. A bisection game for challenged compute jobs, inheriting\n`ReentrancyGuard` and `Governable`. The constructor takes a dispute bond and a maximum bisection-round\ncount. The design is written down and checked: the contract header cites `DisputeResolution.tla` with nine\ninvariants and the `AdversarialCompute.tla` properties, including that griefing is never profitable.\n\nFlow:\n\n- `initiateDispute(jobId, defender, rangeStart, rangeEnd)`, `payable`, `nonReentrant`; the challenger posts\n the bond\n- `acknowledgeDispute(disputeId)`, `payable`; the defender posts a matching bond\n- `bisect(disputeId, claimFaulty)`, the challenger halves the range each round\n- `respond(disputeId, stepResultHash)`, the defender commits a step result\n- `resolve(disputeId, challengerWins)`, `onlyGovernance`; pays the winner and, if the challenger wins,\n slashes the defender through the `INematocystSlashing` interface at the `Inconsistency` tier\n- `timeoutDispute(disputeId)`, callable by anyone after the deadline; the challenger wins\n\nAdmin, all `onlyGovernance`: `setDisputeBond`, `setMaxBisectionRounds`, `setSlashingContract`,\n`setRoundDeadline`. The constant `DEFAULT_ROUND_DEADLINE` is 150 blocks. Views: `getDispute`,\n`isDisputeActive`, `getRangeSize`.\n\nThe slash call is wrapped in `try / catch`, so a missing or reverting slashing contract does not block the\ndispute from resolving and paying the winner. The slashing target itself is documented under\n[security contracts](/contracts/security).\n\n### AgentDecisionRegistry\n\nSource: `contracts/src/AgentDecisionRegistry.sol`. An on-chain audit trail of high-risk agent tool calls,\ninheriting `Governable`. Each record holds an agent id, the tool name, a parameters hash, the block and\ntimestamp, the executing address, a status, and any dispute evidence. From the record count and the dispute\ncount it derives a trust score, decisions minus twice disputes, and a tier: `Untrusted` below 100,\n`Standard` from 100 to under 500, `Trusted` at 500 or above.\n\nFunctions:\n\n- `registerDecision(agentId, toolName, paramsHash)`, `onlyAuthorizedRecorder`\n- `disputeDecision(decisionId, evidence)`, `onlyAuthorizedDisputer`\n- `resolveDispute(decisionId, upheld)`, `onlyGovernance`\n- `registerDecisionWithTierCheck` and `disputeDecisionWithTierCheck`, which do the same work and also emit\n `TrustTierChanged` when a tier boundary is crossed\n- `setAuthorizedRecorder(recorder, allowed)` and `setAuthorizedDisputer(disputer, allowed)`, `onlyGovernance`\n\nViews: `getDecisionHistory`, `getDecisionCount`, `getDisputeStatus`, `getTrustScore`, `getTrustTier`.\nEvents include `DecisionRecorded`, `DecisionDisputed`, `DisputeResolved`, and `TrustTierChanged`. The\ncontract reverts with `NotAuthorizedRecorder`, `NotAuthorizedDisputer`, or `ZeroAddress`.\n\nA second contract, `AgentDecisionRegistryV2`, lives at `contracts/src/rbac/AgentDecisionRegistryV2.sol`. It\nis a separate, newer design, not a drop-in replacement: it logs every signed action with a fuller shape\n(user, tenant, correlation id, event class, artifact root) and is append-only against the\n`AgentDecisionLog.tla` spec. It ships as a new contract with its own migration path; the V1 contract above\nis the one this page documents as the governance audit trail, and it remains current.\n\n### SpecRegistry\n\nSource: `contracts/src/SpecRegistry.sol`. A map from an operation domain, for example `\"contract_deploy\"`,\nto the IPFS CID of a Gherkin `.feature` file that agents must check before a critical operation. It inherits\n`Governable`; an earlier version had its own single-step `transferGovernor`, which was removed in the\nRM-L/WP-L1.1 migration in favor of the two-step mixin.\n\nMutators, all `onlyGovernance`: `registerSpec(domain, cid)`, `updateSpec(domain, newCid)` which bumps the\nversion, `deactivateSpec(domain)`, `reactivateSpec(domain)`. Views: `getSpec` returning cid, active flag,\nand version; `hasActiveSpec`; `getAllDomains`. Events: `SpecRegistered`, `SpecUpdated`, `SpecDeactivated`,\n`SpecReactivated`.\n\nA treasury spend, start to finish:\n\n```solidity\nuint256 id = governor.proposeTreasurySpend(\n \"Grant: Q3 dev fund\", \"...\", usdc, recipients, amounts\n);\ngovernor.castVote(id, TreasuryGovernor.VoteType.For);\n// voting period elapses\ngovernor.queue(id);\n// EXECUTION_DELAY blocks elapse\ngovernor.execute(id);\n```\n\n## Design rationale\n\nWe split governance into four contracts rather than one because the failure modes are different in kind. A\ntreasury vote is slow and deliberate and gated on capital. A compute dispute is fast and adversarial and\ngated on a bond. An agent audit record is high-volume and append-only. A spec pin is rare and governed.\nFolding them together would mean one upgrade, one bug, and one blast radius covering all four. Keeping them\napart costs a little duplication and buys independent review and independent upgrade.\n\nThe dispute game is bisection rather than full re-execution because re-running a long computation on chain\nis not affordable. Bisection narrows the disagreement to a single step in a logarithmic number of rounds,\nand only that step needs adjudication. The design is written in TLA+ first so the property that matters,\nthat an attacker who griefs always loses net SALT, is checked before the code is trusted. That formal work\nis described under [research](/research/learning).\n\n## Failure modes\n\n- **Governance handover.** A handover that names the wrong successor does not complete, because the\n successor must call `acceptGovernance`. The system stays under the current governance until a real\n successor accepts. This is the point of the two-step mixin.\n- **Stalled dispute.** If a party goes silent, the dispute does not hang. After the round deadline anyone\n calls `timeoutDispute` and the challenger wins, so a defender cannot escape a losing position by waiting.\n- **Slashing dependency.** `DisputeResolution` calls the slashing contract inside `try / catch`. If the\n slashing target is unset or reverts, the dispute still resolves and the winner is still paid; only the\n stake penalty is skipped, and it can be applied once the dependency is fixed.\n- **Treasury execution.** `execute` re-checks quorum and approval at execution time, not only at proposal\n time, and is `nonReentrant`. A proposal that lost or that has expired past the grace period cannot be\n forced through.\n\n## Access and canon\n\nTier: commercial. These are deep governance contracts. An anonymous copy would materially help a competitor\nclone the network's economic and dispute machinery, so the full detail is served to contracted builders,\nnot published openly. There are no secrets here, no keys and no private endpoints; addresses are not yet\nlisted because the contracts are pre-audit and pre-deployment.\n\nThe agent-safety and formal-methods pieces, `AgentDecisionRegistry` and `SpecRegistry`, are the on-chain\nedge of the research surface. The behavioral specs they pin and the TLA+ work behind `DisputeResolution`\nlive under [research](/research/learning). Slashing and finality are covered under\n[Citrate Network consensus](/chain/consensus).\n\n## Source and verification\n\n- Source repo: `citrate-chain`, files under `contracts/src/`: `TreasuryGovernor.sol`,\n `DisputeResolution.sol`, `AgentDecisionRegistry.sol`, `SpecRegistry.sol`, plus\n `contracts/src/rbac/AgentDecisionRegistryV2.sol` for the V2 note and `contracts/src/lib/Governable.sol`\n for the ownership mixin.\n- Audited against `citrate-chain` SHA `9d5959e`.\n- Status by contract: `TreasuryGovernor` Implemented, pre-audit; `DisputeResolution` Implemented and TLA+\n specified (`DisputeResolution.tla`, `AdversarialCompute.tla`), pre-audit; `AgentDecisionRegistry`\n Implemented, pre-audit, with `AgentDecisionRegistryV2` Implemented and TLA+ specified\n (`AgentDecisionLog.tla`); `SpecRegistry` Implemented, pre-audit. None has completed a final third-party\n audit. Verify deployed bytecode yourself with `eth_getCode` once addresses are published.\n"},"/contracts/models":{"slug":"/contracts/models","title":"Model contracts","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/contracts/src (ModelRegistry.sol, ModelMarketplace.sol, LoRAFactory.sol, ModelAccessControl.sol, InferenceRouter.sol, interfaces/IModelRegistry.sol, interfaces/IModelMarketplace.sol)","syncedSha":"fa7c913","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"ModelRegistry","anchor":"modelregistry"},{"depth":3,"text":"InferenceRouter","anchor":"inferencerouter"},{"depth":3,"text":"ModelMarketplace","anchor":"modelmarketplace"},{"depth":3,"text":"LoRAFactory","anchor":"lorafactory"},{"depth":3,"text":"ModelAccessControl","anchor":"modelaccesscontrol"},{"depth":2,"text":"Precompile calls","anchor":"precompile-calls"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"These are the contracts that register a model on the public ledger, sell access to it, route inference\nto the operators that serve it, and build adapters on top of it. A model is registered once and from\nthere it can be listed, gated, adapted, and called. This page is for model owners, inference consumers,\nand integrators.\n\n## What it is\n\nA model on Citrate is a record, not a file. The weights stay where the owner put them, usually behind an\nIPFS CID; the public ledger keeps the model's identity, its owner, its price, and a count of the work it\nhas done. Inference itself runs off chain on the operators that serve the model; the contracts here\ncharge for it, route it, and gate it. Where a contract asks a [precompile](/chain/precompiles) for an\nanswer, it does so through one library that refuses to treat \"no answer\" as an answer (see\n[Precompile calls](#precompile-calls) below).\n\nFive contracts cover the model economy:\n\n- **ModelRegistry** is the root record. Registering a model yields a `modelHash`; `requestInference`\n asks the model inference precompile at `0x0101`, which contract code cannot reach on 40204 today.\n- **InferenceRouter** load-balances inference across staked operators, with a cache and a refund path.\n- **ModelMarketplace** lets owners list a model and sell access, with bulk discounts and a treasury fee.\n- **LoRAFactory** builds, trains, merges, and cryptographically verifies low-rank adapters on top of a\n base model.\n- **ModelAccessControl** is a separate, tiered access registry with paid or approval-based grants,\n staking, revenue sharing, and an encrypted-inference path.\n\nA point worth holding onto: there are two access systems here, and they are not wired together.\nModelRegistry, the marketplace, the router, and the factory share one role-based registry. ModelAccessControl\nis a standalone contract with its own level-based grants. Pick the one your integration targets and stay\nin it.\n\nYou can register and call a model from the RPC surface with `citrate_deployModel` and read the catalog\nwith `citrate_getModels`; see [chain RPC](/chain/rpc). Federated training that produces models lives on\n[Citrate Orchard](/research/learning), which is a separate surface from the adapter factory described\nbelow.\n\n## How to use it\n\nThe shortest path from nothing to a paid inference call.\n\n1. Register the model. Call `registerModel(...)` on **ModelRegistry** with the name, framework, version,\n IPFS CID, size, an inference price, and the metadata struct. Send at least `REGISTRATION_FEE`\n (0.1 SALT). You get back the `modelHash`.\n2. Decide who can call it. For a simple owner-priced model, `requestInference` on the registry already\n charges and forwards payment to you. To sell in a catalog with reviews and discounts, list it on\n **ModelMarketplace**. For tiered or approval-gated access with staking, register it instead on\n **ModelAccessControl**.\n3. Serve it. Operators register on **InferenceRouter** with `registerProvider(endpoint, minPrice,\n supportedModels)`, staking at least the minimum, and the router scores and assigns requests across\n them.\n4. Call it. A consumer calls `requestInference(modelHash, inputData, maxPrice)` on the router. A cache\n hit returns the stored output with a partial refund; otherwise the assigned operator returns the\n result with `completeInference` and is paid, with any excess refunded.\n5. Adapt it, optionally. Build a LoRA adapter on the base model with `createLoRA(...)` on\n **LoRAFactory**, have an operator train it off chain and record the weights, and verify the adapter\n against the inference proof precompile before anyone relies on it.\n\n## Reference\n\nThe audited public surface of each contract, with function names as they appear in source. The\ncanonical truth is the Solidity in `contracts/src`; treat ABIs as coming from the published package, not\nhand-copied from here.\n\n### ModelRegistry\n\n`contracts/src/ModelRegistry.sol`, `contract ModelRegistry is IModelRegistry, AccessControl,\nReentrancyGuard` (the project-local `AccessControl` and `ReentrancyGuard`, not OpenZeppelin; interface at\n`contracts/src/interfaces/IModelRegistry.sol`). Stores model metadata, manages owner permissions, and\ncharges a fixed registration fee. Registration and updates are records only: no precompile is called, and\nthe weights stay at the IPFS CID. `requestInference` calls `0x0101` MODEL_INFERENCE through the\n`CitratePrecompiles` library. The constructor grants the deployer the admin and operator roles.\n\n| Function | Notes |\n|---|---|\n| `registerModel(string name, string framework, string version, string ipfsCID, uint256 sizeBytes, uint256 inferencePrice, IModelRegistry.ModelMetadata metadata) payable returns (bytes32)` | Requires `REGISTRATION_FEE` (0.1 SALT); returns the `modelHash`. |\n| `updateModel(bytes32 modelHash, string newVersion, string newIpfsCID)` | Owner only. |\n| `setInferencePrice(bytes32 modelHash, uint256 newPrice)` | Owner only. |\n| `deactivateModel(bytes32 modelHash)` / `activateModel(bytes32 modelHash)` | Owner or operator role. |\n| `grantPermission(bytes32 modelHash, address user)` / `revokePermission(bytes32 modelHash, address user)` | Owner only. |\n| `requestInference(bytes32 modelHash, bytes inputData) payable returns (bytes)` | Forwards the full `msg.value` to the model owner; no marketplace fee is retained. Calls `0x0101` with `modelHash || msg.sender || inputData`; reverts with `PrecompileUnavailable(0x0101)` on every 40204 node today, payment included. |\n| `withdrawFees()` | `onlyRole(DEFAULT_ADMIN_ROLE)`. |\n\nViews include `getModel` (an 8-tuple of owner, name, framework, version, IPFS CID, inference price, total\ninferences, and active flag), `getModelsByOwner`, `getModelRevenue`, `hasPermission`,\n`getAllModelHashes`, and `getModelsInfo`. Constant: `REGISTRATION_FEE = 0.1 ether` (the old\n`MODEL_PRECOMPILE = 0x1000` and `ARTIFACT_PRECOMPILE = 0x1002` constants are gone; nothing served those\naddresses). Note that `setRegistrationFee` is intentionally inert: it is a `view` that\nalways reverts with \"Registration fee is immutable\", so the fee cannot be changed.\n\n### InferenceRouter\n\n`contracts/src/InferenceRouter.sol`, `contract InferenceRouter is AccessControl, ReentrancyGuard`\n(project-local). Routes inference to registered operators by a load-balancing score, with response\ncaching, operator staking, and payment or refund distribution. The constructor takes the registry\naddress and grants the deployer admin and operator roles.\n\n| Function | Notes |\n|---|---|\n| `registerProvider(string endpoint, uint256 minPrice, bytes32[] supportedModels) payable` | Stake at least `minProviderStake` (100 SALT default). |\n| `requestInference(bytes32 modelHash, bytes inputData, uint256 maxPrice) payable returns (uint256)` | A cache hit returns the cached output with a partial refund. |\n| `completeInference(uint256 requestId, bytes outputData)` | Assigned operator only; pays the operator, refunds the excess, caches the output. |\n| `cancelRequest(uint256 requestId)` | Requester only, while pending; full refund. |\n| `updateProviderStatus(bool isActive)` / `addStake() payable` / `withdrawStake(uint256 amount)` | Operator stake management. |\n| `withdrawEarnings()` | Operator pulls accrued earnings. |\n| `setCaching(bytes32 modelHash, bool enabled)` | `onlyRole(OPERATOR_ROLE)`. |\n| `setPlatformFee(uint256 newFee)` / `setMinProviderStake(uint256 newStake)` / `withdrawPlatformFees()` | `onlyRole(DEFAULT_ADMIN_ROLE)`; the fee is capped at 1000 bps. |\n\nViews include `getRequest`, `getUserRequests`, `getProviders`, and `getProviderInfo`. Config:\n`minProviderStake = 100 ether`, `platformFee = 250` (2.5%), and `cacheReward = 100` (1%). Requests move\nthrough `enum RequestStatus { Pending, Processing, Completed, Failed, Cancelled }`.\n\n### ModelMarketplace\n\n`contracts/src/ModelMarketplace.sol`, `contract ModelMarketplace is IModelMarketplace, AccessControl,\nReentrancyGuard` (project-local; interface at `contracts/src/interfaces/IModelMarketplace.sol`). A\nmarketplace layered on the registry: owners list models, buyers purchase inference access with bulk\ndiscounts, and a fee splits to a treasury. Adds reviews, categories, and featuring. The constructor takes\nthe registry and treasury addresses, both non-zero; the registry is immutable.\n\n| Function | Notes |\n|---|---|\n| `listModel(bytes32 modelId, uint256 basePrice, uint256 discountPrice, uint256 minimumBulkSize, uint8 category, string metadataURI)` | Price in `[MIN_PRICE, MAX_PRICE]`; category at most 10. |\n| `purchaseAccess(bytes32 modelId, uint256 quantity) payable` | Bulk discount above `minimumBulkSize`; fee `MARKETPLACE_FEE_BASIS_POINTS` (2.5%); refunds the excess. |\n| `updatePricing(...)` / `updateCategory(...)` | Owner only. |\n| `deactivateListing(bytes32 modelId)` / `activateListing(bytes32 modelId)` | Owner only. |\n| `featureModel(bytes32 modelId) payable` | Admin free; owner pays `FEATURED_FEE` (1 SALT). |\n| `unfeatureModel(bytes32 modelId)` | `onlyRole(DEFAULT_ADMIN_ROLE)`. |\n| `addReview(bytes32 modelId, uint8 rating, string comment)` | Rating 1 to 5; comment at most 500 bytes. |\n| `updateTreasuryAddress(address newTreasury)` | `onlyRole(DEFAULT_ADMIN_ROLE)`. |\n\nViews include `getListing`, `getModelsByCategory`, `getFeaturedModels`, `getTopRatedModels`,\n`getModelsByOwner`, `getPurchaseHistory`, `getModelReviews`, and `getMarketplaceStats`. Constants:\n`MARKETPLACE_FEE_BASIS_POINTS = 250`, `MIN_PRICE = 0.001 ether`, `MAX_PRICE = 1000 ether`, and\n`FEATURED_FEE = 1 ether`. A review does not require a verified purchase; the review carries a `verified`\nflag set from the reviewer's purchase history, but an unverified review still counts toward the average\nrating.\n\n### LoRAFactory\n\n`contracts/src/LoRAFactory.sol`, `contract LoRAFactory is AccessControl` (project-local; it does not\ninherit `ReentrancyGuard`). A factory for creating, training, merging, and cryptographically verifying\nlow-rank adapters against base models in the registry. Training and merges run off chain (the compute\npool or an operator) and are recorded here: `createLoRA` emits `TrainingStarted`, `mergeLoRAs` emits\n`MergeRequested`, and the operator records the results with `completeTraining` and `completeMerge`.\nVerification goes through the Halo2-KZG inference-proof verifier at `0x0108`; adapter inference goes to\n`0x0101` with the adapter's id. The constructor takes the registry address and grants the deployer admin\nand operator roles.\n\n| Function | Notes |\n|---|---|\n| `createLoRA(bytes32 baseModelHash, string name, string description, uint256 rank, uint256 alpha, uint256 dropout, TrainingConfig config) payable returns (bytes32)` | Requires permission on the base model; fee is `trainingFeePerEpoch` times the epoch count. Emits `TrainingStarted`; no precompile call. |\n| `completeTraining(bytes32 loraHash, string ipfsCID)` | `onlyRole(OPERATOR_ROLE)`; records the trained-weights CID. |\n| `setAdapterModelCommitment(bytes32 loraHash, bytes32 commitment)` | `onlyRole(OPERATOR_ROLE)`; one-shot. |\n| `verifyAdapterAt(bytes32 loraHash, bytes32 inputCommitment, bytes32 outputCommitment, bytes proofBytes)` | Proof-backed verification through `0x0108`. |\n| `isAdapterVerified(bytes32 loraHash) view returns (bool)` | Whether the adapter has a verified proof. |\n| `mergeLoRAs(bytes32[] loraHashes, uint256[] weights, uint256 mergeType) payable returns (bytes32)` | Weights must sum to `1e18`. Emits `MergeRequested`; the merge itself runs off chain. |\n| `completeMerge(bytes32 requestHash, string resultCID)` | `onlyRole(OPERATOR_ROLE)`. |\n| `inferWithLoRA(bytes32 baseModelHash, bytes32 loraHash, bytes inputData) payable returns (bytes)` | Calls `0x0101` with `loraHash || msg.sender || inputData`, then splits 20% to the adapter creator and 80% through `modelRegistry.requestInference`. Reverts with `PrecompileUnavailable(0x0101)` on 40204 today, so nobody is paid. |\n| `setPublicStatus` / `grantPermission` / `revokePermission` | Creator only. |\n| `setTrainingFee` / `setMergeFee` / `withdrawFees` | `onlyRole(DEFAULT_ADMIN_ROLE)`. |\n\nViews include `getLoRA`, `getUserLoRAs`, `getModelLoRAs`, and `getMergeRequest`. Constants:\n`trainingFeePerEpoch = 0.01 ether`, `mergeFee = 0.05 ether`, `INFERENCE_PROOF_VERIFY = 0x0108`, and\n`INFERENCE_CIRCUIT_V1 = 1`. The old `LORA_PRECOMPILE = 0x1001` constant is gone: nothing served that\naddress, and its `startTraining`, `mergeLoras` and `applyAndInfer` calls never ran. On-chain LoRA\narithmetic is now the pair of precompiles at `0x0112` and `0x0113` described under\n[Precompile calls](#precompile-calls).\n\nAdapter provenance, as this contract implements it, ties to three things and no more: the base model\nhash, which must exist in the registry; the IPFS CIDs for the trained weights and for the dataset, the\nlatter carried as `datasetCID` inside the `TrainingConfig` struct; and a cryptographic commitment plus a\nproof. An operator sets a one-shot `adapterModelCommitment`, then `verifyAdapterAt` submits a Halo2-KZG\nproof to the `0x0108` precompile over the tuple of input commitment, model commitment, output commitment,\ncircuit version, and chain id. A passing proof flips `isAdapterVerified` to true. That proof shows an\nadapter produces a committed output for a committed input against the committed model. It does not, in\nthis build, link the adapter to a learning round, a contributor, or a contribution-accounting record;\nthere is no reference to those systems in the contract. Federated learning and contribution live on the\nseparate [Citrate Orchard](/research/learning) surface.\n\n### ModelAccessControl\n\n`contracts/src/ModelAccessControl.sol`, `contract ModelAccessControl is Ownable, ReentrancyGuard` (the\nOpenZeppelin versions, the one contract on this page that does). A standalone tiered access registry,\ndistinct from ModelRegistry: paid or approval-based grants, per-model staking, revenue sharing, and an\nencrypted-inference path through the runtime precompiles for model inference at `0x0101` and model\nencryption at `0x0106`, both called through the `CitratePrecompiles` library, so both fail closed on 40204\ntoday. The constructor makes the deployer the owner.\n\n| Function | Notes |\n|---|---|\n| `registerModel(bytes32 modelId, string ipfsCid, bool isEncrypted, uint256 accessPrice)` | Registrant gets `ACCESS_ADMIN` on the model. |\n| `updateModel(...)` / `setModelMetadata(...)` | Owner only. |\n| `grantAccess(bytes32 modelId, address user, uint8 level, uint256 expiresAt, uint256 usageLimit)` | Owner only. |\n| `revokeAccess(bytes32 modelId, address user)` | Owner only. |\n| `requestAccess(bytes32 modelId, uint8 level, string reason) payable returns (uint256 requestId)` | Requires at least the access price. |\n| `approveAccessRequest(uint256 requestId, uint256 expiresAt, uint256 usageLimit)` | Model owner. |\n| `executeInference(bytes32 modelId, bytes inputData) payable returns (bytes)` | Requires `ACCESS_INFERENCE`. |\n| `executeEncryptedInference(bytes32 modelId, bytes encryptedInput, bytes32 proofCommitment) payable returns (bytes)` | Through the encryption precompile. |\n| `setStakingRequirement` / `stakeForAccess` / `unstake` | Per-model staking. |\n| `withdrawRevenue()` / `emergencyWithdraw()` | `emergencyWithdraw` is `onlyOwner`. |\n\nViews include `getModelStats`, `hasAccessToModel`, `getUserAccessLevel`, and `getModel`. Access levels\nare `ACCESS_NONE = 0`, `ACCESS_INFERENCE = 1`, `ACCESS_FULL = 2`, and `ACCESS_ADMIN = 3`. Note that\n`getModelStats` returns `uniqueUsers` as a placeholder zero, and `updatePrecompileAddress` is a\nnon-functional placeholder.\n\n## Precompile calls\n\nEvery precompile call in these contracts goes through one library, `CitratePrecompiles`\n(`contracts/src/lib/CitratePrecompiles.sol`). It encodes each precompile's native input (the node does not\ndecode Solidity ABI selectors) and fails closed: a call that fails or returns nothing reverts with\n`PrecompileUnavailable(address)`, and a wrong-shaped answer reverts with\n`PrecompileBadOutput(address, length)`. This matters because a call to an address with no code succeeds\nwith empty data, so a contract that checks only the success flag would read \"no precompile here\" as a\nresult and pay for it.\n\n| Address | Name | Used by | On 40204 today |\n|---|---|---|---|\n| `0x0101` | MODEL_INFERENCE | ModelRegistry, LoRAFactory, ModelAccessControl | not served to contract code; the call reverts |\n| `0x0106` | MODEL_ENCRYPTION | ModelAccessControl | not served to contract code; the call reverts |\n| `0x0108` | INFERENCE_PROOF_VERIFY | LoRAFactory adapter verification | live where the node build carries the verifier |\n| `0x0112` | LORA_APPLY | library helper `loraApply` | active from genesis of the 2026-10-05 reroll |\n| `0x0113` | LORA_MERGE | library helper `loraMerge` | active from genesis, as above |\n| `0x0121` | MEMORY_ANCHOR_VERIFY | library helpers `memoryAnchorCommitment`, `AnchorProofs.isRecordAnchored` | active from genesis, as above |\n| `0x0122` | AGENT_OPS | library helpers `deviceLinkValid`, `deviceRevocationValid` | active from genesis, as above |\n\n`0x0112` applies one LoRA adapter to one tile of weights (`W + (alpha / r) (B . A)` in Q16.16 fixed\npoint) and `0x0113` merges up to 16 adapters on one tile, which is what lets a challenger recompute one\ndisputed tile of an aggregate instead of the whole tensor. `0x0121` checks a nightly decision-anchor\ninclusion proof and returns the day commitment to look up in `AnchorRegistry`. `0x0122` checks device link\nand revocation signatures. All four are pure byte functions that every node computes identically.\n\n**Activation.** The four agent precompiles sit behind a per-network activation height. On 40204 that\nheight is genesis: the release pins `(40204, Some(0))`, so after the 2026-10-05 reroll they are live from\nthe first block and no mid-chain activation is scheduled. The gas schedule is the owner-signed genesis\nschedule. On a network whose height is not reached (or not set) the addresses behave as if absent: every\nlibrary call to them reverts with `PrecompileUnavailable`, so no contract can mistake a missing precompile\nfor a \"valid\" or \"invalid\" verdict. Nodes on the rerolled 40204 must be built from the release commit that\ncarries this pin.\n\nThe byte layouts, gas formulas, activation rules and test evidence are specified once, in the chain\nrepository's\n[agent precompile specification](https://github.com/CitrateNetwork/citrate-chain/blob/main/docs/precompiles/AGENT_PRECOMPILES.md)\n(`docs/precompiles/AGENT_PRECOMPILES.md`). This page does not repeat them.\n\n## Design rationale\n\nThe registry keeps a model as a small record and pushes the work to the precompiles because the weights\nare large and often private; the ledger needs only the identity, the price, and the receipt. Charging\nthrough `requestInference` and forwarding the whole payment to the owner keeps the base case simple, and\nthe marketplace and access-control contracts layer richer policy on top when an owner wants it.\n\nThe two access systems exist for two audiences. A developer who wants a model in a public catalog with\nreviews and discounts uses the registry and the marketplace. An institution that wants approval-gated,\nstaked, or encrypted access uses ModelAccessControl. Keeping them separate avoids forcing one set of\nassumptions on the other, at the cost of an integrator having to choose.\n\nThe adapter factory proves what it can prove on-chain, which is a proof that a committed adapter produces\na committed output, and no more. Tying an adapter back to who contributed which gradient is a learning-\nround concern, and that belongs to Citrate Orchard, not to this factory. The honest framing is that the\nfactory verifies adapter behavior, not adapter origin.\n\n## Failure modes\n\nThese contracts move value and gate access, so the sharp edges are worth naming.\n\n- **A bad adapter is trusted.** Until `isAdapterVerified` returns true, an adapter has no on-chain proof\n behind it. Check it before relying on an adapter, because creation and training alone do not verify\n anything. The proof, when present, attests behavior, not origin.\n- **Reentrancy surface.** LoRAFactory does not inherit `ReentrancyGuard` even though `inferWithLoRA`,\n `mergeLoRAs`, and `withdrawFees` move value. On InferenceRouter only `requestInference` carries the\n guard; `completeInference`, `cancelRequest`, `withdrawEarnings`, `withdrawStake`, and\n `withdrawPlatformFees` rely on careful ordering rather than a guard. Both are flagged for external\n audit and should not be treated as verified.\n- **Unverified reviews.** A marketplace review does not require a verified purchase, so the average\n rating can be moved by addresses that never bought the model; the `verified` flag distinguishes them\n but does not exclude them from the average.\n- **Inference calls revert on 40204.** `requestInference` on the registry, `inferWithLoRA`, and both\n inference paths of ModelAccessControl revert with `PrecompileUnavailable(0x0101)` (or `0x0106`) on every\n 40204 node today, and any payment sent with them is returned by the revert. Earlier builds called\n addresses (`0x1000`, `0x1001`) that nothing served; those calls are gone.\n- **Agent precompiles where they are not active.** On 40204 after the 2026-10-05 reroll these are live\n from genesis. On any other network, code that uses `0x0112`, `0x0113`, `0x0121` or `0x0122` through the\n library reverts until that network's activation height is reached. Handle the revert; do not catch it\n and treat it as a negative answer.\n- **Inert and placeholder surfaces.** `setRegistrationFee` on the registry always reverts by design.\n On ModelAccessControl, `getModelStats` reports a placeholder zero for unique users and\n `updatePrecompileAddress` does nothing.\n\n## Access and canon\n\nTier: ModelRegistry and InferenceRouter are public developer reference, since a builder needs them to\nregister a model and consume inference. ModelMarketplace, LoRAFactory, and ModelAccessControl are\ncommercial, the paid-seat depth covering marketplace economics, the adapter pipeline, and the access and\nstaking design.\n\nNo secrets appear on this page. There are no private keys, mnemonics, internal hostnames, or\ncredentials. The only hardcoded addresses are public protocol precompiles: `0x0101`, `0x0106`, `0x0108`,\n`0x0112`, `0x0113`, `0x0121`, and `0x0122` (and the retired `0x1000`, `0x1001` and `0x1002`, named only to\nsay they are gone). Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity.\n\n## Source and verification\n\n- Source: `citrate-chain/contracts/src/`, in `ModelRegistry.sol`, `InferenceRouter.sol`,\n `ModelMarketplace.sol`, `LoRAFactory.sol`, and `ModelAccessControl.sol`, with interfaces\n `interfaces/IModelRegistry.sol` and `interfaces/IModelMarketplace.sol`.\n- Audited against `citrate-chain` SHA `fa7c913`, the head of the agent precompile fork stack (pull\n request 273 and the stack above it). Until that stack reaches `main`, the deployed contracts and `main`\n still carry the old constants; this page describes the code that ships with the fork.\n- Status: Implemented, pre-audit, on testnet 40204. The LoRAFactory reentrancy gap, the InferenceRouter\n guard mismatch, the unverified-review weighting, and the ModelAccessControl placeholders are open items\n noted above and not yet externally audited. Re-verify deployed bytecode with `eth_getCode` if the chain\n has been re-rolled.\n"},"/contracts/reference":{"slug":"/contracts/reference","title":"Contracts reference","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/contracts (addresses/40204.json, src/)","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The contracts that run on the Citrate Network, where to find their addresses, where to get their ABIs, and how to confirm an address is real before you call it. This page is for anyone integrating against the on-chain surface.\n\n## What it is\n\nThe Citrate Network is EVM-compatible, so the on-chain surface is a set of Solidity contracts you call with ordinary tooling. The address book lists 76 application and account-abstraction contracts for testnet beta, chain id 40204. At the last check 57 of them have code on chain; the other 19, the governance and cooperative set, are not deployed. The [chain addresses page](/chain/addresses) marks each one. They group into families: education, compute, models, network economics, governance, security, x402 payments, and account abstraction. The families are described on their own pages, linked below.\n\nTwo facts shape how you should treat this page. First, addresses drift: the chain can be re-rolled, and a contract you read about yesterday may sit at a new address today. The canonical record of what is deployed lives in the source repo, not here. Second, the ABIs are published as a package, so you never have to hand-copy them. This page tells you where both live and how to verify an address yourself.\n\n## How to use it\n\nYou need three things to call a Citrate contract: its address, its ABI, and the confidence that the address is what you think it is.\n\n1. Get the addresses. The canonical record of what is deployed is `contracts/addresses/40204.json` in the chain repo, the single table every consumer reads. The same set is published on the [chain addresses page](/chain/addresses) with a deployed or not-deployed status on each row. A `@citratelabs/chain-config` package is built in the chain repo (`packages/chain-config`) but is not published to npm yet, so read the JSON directly. Read from one of these rather than copying addresses into your code by hand; they update when the chain is re-rolled, and prose documentation may lag. (`contracts/DEPLOYED_ADDRESSES.md` is superseded and is no longer the source of truth.)\n\n```bash\n# The book is plain JSON; fetch it from the chain repo and read what you need.\ncurl -fsSL https://raw.githubusercontent.com/CitrateNetwork/citrate-chain/main/contracts/addresses/40204.json -o 40204.json\njq -r '.contracts.WrappedSALT' 40204.json\ncast code \"$(jq -r '.contracts.WrappedSALT' 40204.json)\" --rpc-url https://rpc.citrate.ai # \"0x\" means not deployed\n```\n\n2. Get the ABIs. Regenerate them from source with Foundry. After `forge build`, each contract's ABI is the `.abi` key of `out/.sol/.json`.\n\n```bash\ncd contracts\nforge build\njq '.abi' out/NematocystSlashing.sol/NematocystSlashing.json\n```\n\n3. Verify the address before you trust it. Ask the network for the code at the address with `eth_getCode`. A non-empty result means a contract is deployed there; `0x` means the address is empty or an account, and you should stop.\n\n```bash\ncurl -s https://rpc.citrate.ai \\\n -H 'content-type: application/json' \\\n -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_getCode\",\"params\":[\"
\",\"latest\"]}'\n```\n\nTo go further, compare the runtime bytecode the network returns against your local `forge build` output, the `.deployedBytecode` key of `out/.sol/.json`, to confirm the deployed contract matches the source at this SHA. The [read a contract](/contracts/tutorials/read-a-contract) tutorial walks through this step by step.\n\n## Reference\n\nThe contract families and where each is documented. Addresses for every contract are in `contracts/addresses/40204.json` and on the [chain addresses page](/chain/addresses), not transcribed here.\n\n| Family | What it covers | Page |\n|---|---|---|\n| Education | classrooms, mentorship, learning pools | [/contracts/edu](/contracts/edu) |\n| Compute | the compute marketplace, pools, pricing, verification | [/contracts/compute](/contracts/compute) |\n| Models | model registry, marketplace, access control, LoRA | [/contracts/models](/contracts/models) |\n| Network economics | wrapped SALT, liquid staking, treasury, cashout | [/contracts/economics](/contracts/economics) |\n| Governance | treasury governor, budget allocation, dispute resolution | [/contracts/governance](/contracts/governance) |\n| Security | slashing, heartbeat monitoring, TEE attestation, KYC | [/contracts/security](/contracts/security) |\n| x402 payments | the facilitator and paywall for HTTP 402 settlement | [/contracts/x402](/contracts/x402) |\n| Account abstraction | the ERC-4337 stack, validators, paymaster, factory | [/contracts/aa](/aa/contracts) |\n\nThe address source of truth and the ABI package, named once:\n\n| Resource | Where it lives |\n|---|---|\n| Canonical address table | `contracts/addresses/40204.json` (also on [/chain/addresses](/chain/addresses)) |\n| Chain config package | `@citratelabs/chain-config`, source in `citrate-chain/packages/chain-config`, not yet published to npm |\n| ABIs | regenerate with `forge build`, read the `.abi` key of `out/.sol/.json` |\n\nChain facts you will need when configuring a client:\n\n| Field | Value |\n|---|---|\n| Chain id | `40204` (`eth_chainId` returns `0x9d0c`) |\n| Block time | about 2 s measured on the testnet (the configured target is 1 s) |\n| Consensus | GhostDAG, k = 18 |\n| Public RPC (HTTP) | `https://rpc.citrate.ai` |\n| Public RPC (WebSocket) | `wss://rpc.citrate.ai` |\n\n## Access and canon\n\nPublic. Contract addresses are public on-chain data, and the ABIs and build commands are open developer reference. No private keys, no credentials, and no operational endpoints appear here. The public RPC hostname is the only network address you need; raw node addresses are not published, and you do not need them.\n\nOne pilot caveat carries from the source repo. The deploys are unsigned at this stage, so verification rests on `eth_getCode` cross-checks rather than cosign certificates. The account-abstraction stack (EntryPoint and the rest) is included in the canonical `contracts/addresses/40204.json` under `aaStack`.\n\n## Source and verification\n\nSource: `contracts/addresses/40204.json` (regenerated by `scripts/ops/emit-address-table.sh`), `contracts/README.md`, and the `packages/chain-config` source. Contracts compile under `pragma solidity ^0.8.26` (the 2026-09-07 re-roll built with solc 0.8.36). Chain id 40204 is confirmed in `node/config/testnet.toml` and `contracts/addresses/40204.json`; the testnet config sets a 1 s block target, and the measured interval is about 2 s. Audited against `citrate-chain` SHA `9d5959e`. Status: Implemented, testnet beta, pre-audit. Re-verify any address with `eth_getCode` if the chain has been re-rolled.\n\nSee also [chain RPC](/chain/rpc) and the [chain CLI](/chain/cli).\n"},"/contracts/security":{"slug":"/contracts/security","title":"Security & Slashing Contracts","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/contracts/src/{KYCRegistry,TEEAttestationRegistry,NematocystSlashing}.sol, contracts/src/interfaces/INematocystSlashing.sol","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"KYCRegistry","anchor":"kycregistry"},{"depth":3,"text":"TEEAttestationRegistry","anchor":"teeattestationregistry"},{"depth":3,"text":"NematocystSlashing","anchor":"nematocystslashing"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The security surface is where the network enforces who may participate and what happens when a participant\nmisbehaves. Three contracts carry it: a verification registry that records whether an account passed\nidentity review, an attestation registry that records whether a worker is running in a trusted execution\nenvironment, and a graduated slashing model that penalizes staked providers. This page is for protocol\nresearchers, node operators, and compute providers.\n\n## What it is\n\nTwo of these contracts gate participation, and one punishes it. `KYCRegistry` holds the result of an\nidentity check, a single boolean per account, mirrored from the off-chain identity authority. The personal\ndata behind the check never reaches the ledger; only the verification outcome does. `TEEAttestationRegistry`\nholds, per worker, whether that worker has a fresh attestation that its hardware is running in a trusted\nexecution environment. `NematocystSlashing` takes the staked SALT of a provider and reduces it when the\nprovider is shown to have misbehaved, with the penalty scaled by how severe the fault is and by how many\nproviders failed at once.\n\n| Contract | Role | Status |\n|---|---|---|\n| `KYCRegistry` | Mirror of a revocable, data-free identity claim, an anti-Sybil gate | Implemented, pre-audit |\n| `TEEAttestationRegistry` | Per-worker trusted-execution attestation state | Implemented, pre-audit |\n| `NematocystSlashing` | Graduated three-tier provider slashing with a correlation multiplier | Implemented, pre-audit |\n| `INematocystSlashing` | Minimal calling interface for the slashing contract | Implemented, interface only |\n\nThe slashing model is named for the nematocyst, the stinging cell of a cnidarian. The name is academic in\norigin, but the contract is real and runs; only the minimal `INematocystSlashing` interface is interface\nonly, and it exists so other contracts can call `slash` without importing the full implementation.\n\n## How to use it\n\n1. **Read an identity result.** Call `KYCRegistry.isVerified(account)`. A gating contract, for example the\n pinning incentive contract, calls this before it lets an account take a rewarded slot. To learn the\n stable identity an account resolves to, call `identityOf(account)`; two accounts that return the same\n non-zero value belong to the same person.\n2. **Check a worker's attestation.** Call `TEEAttestationRegistry.isAttested(worker, currentBlock)`. It\n returns true only when the worker has a fresh, non-slashed attestation that has not expired at that\n block. A pipeline contract calls this before it routes confidential inference to the worker.\n3. **Stake as a provider.** Call `NematocystSlashing.stake()` with at least `MIN_STAKE`, which is 100 SALT,\n to become a slashable provider. Call `unstake()` to withdraw the full stake and deregister, which is\n refused if the account has been banned.\n4. **Apply a slash.** Only governance calls `slash(provider, tier, evidence)`. The penalty is a fixed\n fraction of the stake for the tier, scaled up by the correlation multiplier when many providers are\n slashed in the same window.\n\n## Reference\n\nThe audited surface, each item citing its source file under `contracts/src/`.\n\n### KYCRegistry\n\nSource: `contracts/src/KYCRegistry.sol`. A deliberately small contract built on `AccessControl`. It mirrors\nthe off-chain identity authority's `kyc` claim by holding one boolean per account and, separately, the\nstable identity hash that account resolves to. It holds no value and no personal data; the off-chain claim\nis issued over the identity provider's userinfo endpoint, and only the outcome is mirrored here.\n\nTwo roles govern it: `DEFAULT_ADMIN_ROLE`, the deployer, manages updaters, and `KYC_UPDATER_ROLE`, held by\nthe identity authority key or a KYC oracle relay, sets and revokes verification.\n\n| Function | Access | What it does |\n|---|---|---|\n| `setVerified(account)` | `KYC_UPDATER_ROLE` | Marks an account verified; binds a self identity if none is set yet |\n| `setVerifiedWithIdentity(account, subHash)` | `KYC_UPDATER_ROLE` | Verifies and binds the account to a real identity hash |\n| `revoke(account)` | `KYC_UPDATER_ROLE` | Clears verification; leaves the identity binding intact |\n| `isVerified(account)` | view | Returns the current verification boolean |\n| `identityOf(account)` | view | Returns the bound identity hash, or zero if unbound |\n\nUntil the identity provider issues real subject and account claims, `setVerified` binds each account to its\nown identity, a hash of `\"PIN-self\"` and the address, so per-account verification is unchanged and the\nSybil binding is a safe no-op. When real claims arrive, the authority calls `setVerifiedWithIdentity` and\nthe binding becomes meaningful: two accounts under the same identity hash are the same person. Events:\n`KYCVerified`, `KYCRevoked`, `IdentityBound`.\n\n### TEEAttestationRegistry\n\nSource: `contracts/src/TEEAttestationRegistry.sol`. Stores, per worker, an attestation record proving the\nworker runs inside a trusted execution environment, for confidential pipeline-parallel inference. A complete\nattestation is an Azure MAA token at the VM level plus an NVIDIA NRAS claim at the GPU level. It inherits\n`ReentrancyGuard` and `Governable`, and its state machine mirrors the `PipelineParallelTEE.tla` spec:\nnot attested, attested, expired, then slashed as an absorbing terminal state.\n\nThere are two submission paths. The older governance-trusted path, `submitAttestation`, takes\nmeasurement hashes and trusts that they came from pre-approved signers. The cryptographic path,\n`submitAttestationStrictBound`, takes the raw MAA token, its signature, and the literal claim bytes; it\nverifies the RS256 signature on chain against a governance-published RSA key and verifies that the claim\nbytes appear literally inside the token payload, so the on-chain measurement is bound to real token content.\nThe NRAS side remains governance-trusted, pending a P-384 verification primitive.\n\n| Function | Access | What it does |\n|---|---|---|\n| `submitAttestation(...)` | open, gated by `strictCryptographicMode` | V1 path; reverts when strict mode is on |\n| `submitAttestationStrictBound(...)` | open | V2 path; on-chain RS256 verify plus literal claim binding |\n| `isAttested(worker, currentBlock)` | view | True only for a fresh, non-slashed, unexpired record |\n| `getAttestation(worker)` | view | Returns the full attestation record |\n| `reportExpiredServe(...)` | open, posts bond | Files a claim that a worker served past expiry |\n| `finalizeReport(reportId, uphold, stake)` | `onlyGovernance` | Adjudicates a report; slashes on uphold |\n| `proposeMaaRsaKey / finalizeMaaRsaKey` | `onlyGovernance`, then open | Timelocked install of an RSA key |\n| `setMaaRsaKeyActive(kidHash, active)` | `onlyGovernance` | Single-step activate or deactivate |\n| `setStrictCryptographicMode(enabled)` | `onlyGovernance` | Switches the V1 path on or off |\n\nKey constants: `ATTESTATION_LIFETIME_BLOCKS` is 28,800, roughly four hours; `SLASH_BPS` is 1,000, ten\npercent; `REPORT_BOND` is 1 SALT; `RSA_KEY_TIMELOCK_BLOCKS` is 3,600. `strictCryptographicMode` defaults to\ntrue, so a fresh deployment is in the cryptographic path unless governance explicitly opts out.\n\n### NematocystSlashing\n\nSource: `contracts/src/NematocystSlashing.sol`. The graduated slashing model, inheriting `ReentrancyGuard`\nand `Governable`. It maps three severities of provider misbehavior onto three tiers, each with a fixed\npenalty in basis points of the provider's stake.\n\n| Tier | `SlashTier` | Fault | Base penalty |\n|---|---|---|---|\n| 1 | `Latency` | Missed checkpoints or latency faults | `LATENCY_PENALTY_BPS` = 500 (5%) |\n| 2 | `Inconsistency` | Inconsistent results | `INCONSISTENCY_PENALTY_BPS` = 2000 (20%) |\n| 3 | `Byzantine` | Equivocation or double-signing | `BYZANTINE_PENALTY_BPS` = 10000 (100%) plus a permanent ban |\n\nA `Byzantine` slash sets `banned[provider]` to true, forfeits any remaining stake, and emits `Banned`. A\nbanned provider can never re-stake. The novel part of the design is the correlation multiplier, taken from\nEthereum slashing research: penalties scale up when many providers are slashed in the same window, so\ncoordinated failures cost far more than isolated ones.\n\n```text\nmultiplier = clamp( slashedInWindow * 30 / totalProviders, 1x, 3x )\n```\n\nIt is computed in `getCorrelationMultiplier`, with all arithmetic scaled by `1e18`. `CORRELATION_WINDOW`\nis 50 blocks; the per-block slash counter is summed across the window. The multiplier floors at 1x, so a\npenalty is never reduced below its base, and caps at 3x. When there are no providers it is 1x.\n\nA slash, step by step, in `slash(provider, tier, evidence)`, which is `onlyGovernance`:\n\n1. Require non-empty evidence and an un-banned, currently staked provider.\n2. Record the slash for correlation tracking.\n3. Compute the raw penalty as stake times the tier basis points, then multiply by the correlation\n multiplier, capped at the full stake.\n4. Deduct the penalty; for `Byzantine`, also forfeit the remainder and ban.\n5. Emit `Slashed` with the provider, tier, amount, and multiplier.\n\nStaking surface: `stake()`, `payable` and `nonReentrant`; `unstake()`, `nonReentrant`; and the views\n`isSlashable`, `slashesInWindow`, `getCorrelationMultiplier`, plus the public mappings `stakes` and\n`banned` and the counter `totalProviders`. Events: `Staked`, `Unstaked`, `Slashed`, `Banned`.\n\n`INematocystSlashing`, at `contracts/src/interfaces/INematocystSlashing.sol`, is the minimal interface that\n`DisputeResolution` and other callers use. It declares one method, `slash(address, uint8, bytes)`, so a\ncaller does not need the full implementation to trigger a penalty.\n\n## Design rationale\n\nSlashing is graduated rather than flat because the faults are not equal. A slow provider is an annoyance; a\nprovider serving inconsistent results is a correctness problem; a provider double-signing is an attack. A\nsingle penalty for all three would either be too soft for the attack or too harsh for the slowness, so the\npenalty tracks the severity. The correlation multiplier exists because the dangerous failure is the\ncorrelated one: a single bad node is noise, but many nodes failing in one window suggests a coordinated\nproblem, and the cost should rise to match. The floor at 1x means an isolated fault is never discounted.\n\nAttestation defaults to the cryptographic path because the safe default is the one that does not trust the\ncaller's word. The earlier governance-trusted path is kept only as an explicit, reversible opt-out for a\nstaged cutover. The RSA key install is timelocked so that even a one-block compromise of governance cannot\ninstall a malicious signing key, while deactivating a key stays single-step so a compromised key can be\nkilled at once.\n\n## Failure modes\n\n- **Expired attestation.** A worker that serves past its attestation expiry can be reported with\n `reportExpiredServe`, and governance slashes it on uphold. The reporter's bond is returned whether the\n report is upheld or rejected, because under-reporting is the greater risk here.\n- **Token replay.** Each MAA token signature is consumed once, tracked by its hash. Re-submitting the same\n token reverts, so a valid token cannot be replayed by the same or another worker.\n- **Governance compromise on keys.** Installing an RSA signing key is timelocked, giving off-chain monitors\n a window to observe and abort, so a brief governance hijack cannot immediately accept forged attestations.\n- **Banned provider re-entry.** A `Byzantine` slash bans the provider permanently; `stake` refuses a banned\n sender, so a fully slashed attacker cannot quietly rejoin.\n- **Identity revocation.** A revoked KYC claim flips `isVerified` to false at once while leaving the\n identity binding intact, so a gating contract sees the revocation immediately and re-verification keeps\n the same person identity.\n\n## Access and canon\n\nTier: commercial. These contracts carry the network's identity and attestation machinery, whose internals\nwould help a competitor, so the full detail is served to contracted and verified principals rather than\npublished openly. There are no secrets here, no keys, no credentials, and no private endpoints. Any deployed\naddresses are public on-chain data and are verifiable with `eth_getCode`.\n\nThis surface ties to the rest of Almanac at three points. The identity result `KYCRegistry` holds comes from\nthe VERI identity check that gates every account on the public network, covered under [accounts and\nidentity](/aa/identity); Citrate keeps the verification result, not the personal data. The compliance posture these\ngates serve, FERPA, HIPAA, SOC 2, and the rest by deployment context, is covered under\n[enterprise](/enterprise/compliance). Slashing and finality as consensus concerns are covered under [Citrate Network\nconsensus](/chain/consensus).\n\n## Source and verification\n\n- Source repo: `citrate-chain`, files under `contracts/src/`: `KYCRegistry.sol`,\n `TEEAttestationRegistry.sol`, `NematocystSlashing.sol`, and `interfaces/INematocystSlashing.sol`, plus\n `contracts/src/lib/Governable.sol` for the ownership mixin.\n- Audited against `citrate-chain` SHA `9d5959e`.\n- Status by contract: `KYCRegistry` Implemented, pre-audit; `TEEAttestationRegistry` Implemented, pre-audit,\n state machine specified against `PipelineParallelTEE.tla`; `NematocystSlashing` Implemented, pre-audit;\n `INematocystSlashing` Implemented as an interface only. None has completed a final third-party audit.\n Verify deployed bytecode yourself with `eth_getCode` once addresses are published.\n"},"/contracts/tutorials/interact-read-only":{"slug":"/contracts/tutorials/interact-read-only","title":"Interact read-only","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/contracts/src/NematocystSlashing.sol","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Step 1, confirm the chain and the contract","anchor":"step-1-confirm-the-chain-and-the-contract"},{"depth":3,"text":"Step 2, read view functions","anchor":"step-2-read-view-functions"},{"depth":3,"text":"Step 3, decode return data by hand","anchor":"step-3-decode-return-data-by-hand"},{"depth":3,"text":"Step 4, read events with eth_getLogs","anchor":"step-4-read-events-with-eth_getlogs"},{"depth":3,"text":"Step 5, the same from JavaScript","anchor":"step-5-the-same-from-javascript"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Everything you can learn from a Citrate contract without sending a transaction: calling view functions, decoding what they return, and reading the events a contract has emitted. No account, no gas, no signature. About ten minutes.\n\n## What it is\n\nTwo read-only patterns cover most of what you need from a deployed contract. The first is `eth_call`, which runs a view or pure function against current state and returns its value; the [read a contract](/contracts/tutorials/read-a-contract) tutorial covers it in detail. The second is `eth_getLogs`, which returns the events a contract has emitted over a range of blocks, so you can read its history rather than just its present state. Neither writes anything, so neither needs a key.\n\nThe examples read the slashing contract in the security family, which records how the network penalizes node operators that misbehave. Its parameters and live state are fully public; what they mean is described under [security contracts](/contracts/security).\n\n## How to use it\n\nYou need a reachable Citrate JSON-RPC endpoint. The public one is `https://rpc.citrate.ai`. You will want `curl`, and the `cast` command from [Foundry](https://book.getfoundry.sh/) for the encode-free path. Read the slashing contract's address from `contracts/addresses/40204.json`, the canonical record described on the [contracts reference](/contracts/reference), and confirm it with step 1 before trusting it.\n\nSet up the helper from [your first 10 minutes](/start/tutorials/your-first-10-minutes):\n\n```bash\nexport RPC=https://rpc.citrate.ai\nexport ADDR=\n\nrpc () {\n curl -s \"$RPC\" -H 'content-type: application/json' \\\n -d \"{\\\"jsonrpc\\\":\\\"2.0\\\",\\\"id\\\":1,\\\"method\\\":\\\"$1\\\",\\\"params\\\":${2:-[]}}\"\n}\n```\n\n### Step 1, confirm the chain and the contract\n\nCheck you are on chain 40204, then confirm code lives at the address.\n\n```bash\nrpc eth_chainId\n# {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x9d0c\"} # 0x9d0c is 40204\nrpc eth_getCode \"[\\\"$ADDR\\\", \\\"latest\\\"]\"\n# a long hex string means a contract is deployed there\n```\n\n### Step 2, read view functions\n\nThese are all `public` or `view` on the slashing contract. The fixed penalties are constants, expressed in basis points; the rest are read from live state.\n\n```bash\n# Number of registered providers (a public state variable)\ncast call $ADDR \"totalProviders()(uint256)\" --rpc-url $RPC\n\n# Correlation multiplier, scaled by 1e18; 1e18 is 1x\ncast call $ADDR \"getCorrelationMultiplier()(uint256)\" --rpc-url $RPC\n\n# Slash events recorded in the current correlation window\ncast call $ADDR \"slashesInWindow()(uint256)\" --rpc-url $RPC\n\n# Per-address views; substitute any address you want to inspect\ncast call $ADDR \"stakes(address)(uint256)\" 0x0000000000000000000000000000000000000000 --rpc-url $RPC\ncast call $ADDR \"banned(address)(bool)\" 0x0000000000000000000000000000000000000000 --rpc-url $RPC\ncast call $ADDR \"isSlashable(address)(bool)\" 0x0000000000000000000000000000000000000000 --rpc-url $RPC\n\n# Fixed tier penalties, in basis points\ncast call $ADDR \"LATENCY_PENALTY_BPS()(uint256)\" --rpc-url $RPC # 500 (5%)\ncast call $ADDR \"INCONSISTENCY_PENALTY_BPS()(uint256)\" --rpc-url $RPC # 2000 (20%)\ncast call $ADDR \"BYZANTINE_PENALTY_BPS()(uint256)\" --rpc-url $RPC # 10000 (100%)\n```\n\n### Step 3, decode return data by hand\n\nWhen you call without `cast`, the result is raw ABI-encoded hex and you decode it yourself. A `uint256` comes back as one 32-byte word.\n\n```bash\nSEL=$(cast sig \"getCorrelationMultiplier()\") # 0x...\nrpc eth_call \"[{\\\"to\\\":\\\"$ADDR\\\",\\\"data\\\":\\\"$SEL\\\"},\\\"latest\\\"]\"\n# {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x0000...0de0b6b3a7640000\"}\n# decode the 32-byte word to a number:\ncast --to-dec 0x0000000000000000000000000000000000000000000000000de0b6b3a7640000\n# 1000000000000000000 (1e18, a 1x multiplier)\n```\n\n### Step 4, read events with eth_getLogs\n\nA contract's history is in its events. `eth_getLogs` returns the logs matching a filter: the contract address, a block range, and optional topics. The first topic is the keccak256 hash of the event signature, so you can ask for one kind of event. The slashing contract emits `Staked(address,uint256)`, `Unstaked(address,uint256)`, `Slashed(...)`, and `Banned(address)`.\n\n```bash\n# Topic for the Staked event\ncast keccak \"Staked(address,uint256)\"\n# 0x... (use this as topics[0])\n\n# All Staked events from this contract, over a block range\nrpc eth_getLogs \"[{\\\"address\\\":\\\"$ADDR\\\",\\\"fromBlock\\\":\\\"0x0\\\",\\\"toBlock\\\":\\\"latest\\\",\\\"topics\\\":[\\\"\\\"]}]\" \n```\n\nEach log carries `topics` and `data`. Indexed event parameters land in `topics` after the signature hash; non-indexed parameters are ABI-encoded in `data`. For `Staked(address indexed provider, uint256 amount)`, the provider address is `topics[1]` and the amount is in `data`. Keep block ranges narrow on a busy contract, since a wide range returns a large response.\n\n### Step 5, the same from JavaScript\n\nThe same reads in viem, using the published ABI so you work in function names rather than selectors. This is read-only: a public client only, no key and no signer.\n\n```ts\nimport { createPublicClient, http, defineChain } from \"viem\";\nimport NematocystSlashing from \"./abi/NematocystSlashing.json\"; // regenerated via `forge build`\n\nconst citrate = defineChain({\n id: 40204,\n name: \"Citrate Network\",\n nativeCurrency: { name: \"SALT\", symbol: \"SALT\", decimals: 18 },\n rpcUrls: { default: { http: [\"https://rpc.citrate.ai\"] } },\n});\n\nconst client = createPublicClient({ chain: citrate, transport: http() });\nconst address = \"\" as const;\n\nconst providers = await client.readContract({\n address, abi: NematocystSlashing.abi, functionName: \"totalProviders\",\n});\nconst multiplier = await client.readContract({\n address, abi: NematocystSlashing.abi, functionName: \"getCorrelationMultiplier\",\n});\n\nconsole.log({ providers, multiplier }); // multiplier is 1e18-scaled\n```\n\n`readContract` issues an `eth_call` under the hood, exactly what you did by hand in step 3, but decoded for you.\n\n## Reference\n\nThe read surface used above, with sources.\n\n| Kind | Item | Source |\n|---|---|---|\n| View | `totalProviders()`, `slashesInWindow()`, `getCorrelationMultiplier()` | `contracts/src/NematocystSlashing.sol` |\n| View | `stakes(address)`, `banned(address)`, `isSlashable(address)` | `contracts/src/NematocystSlashing.sol` |\n| Constant | `LATENCY_PENALTY_BPS`, `INCONSISTENCY_PENALTY_BPS`, `BYZANTINE_PENALTY_BPS` | `contracts/src/NematocystSlashing.sol` |\n| Event | `Staked`, `Unstaked`, `Slashed`, `Banned` | `contracts/src/NematocystSlashing.sol` |\n\nFor encoding a single call step by step, see [read a contract](/contracts/tutorials/read-a-contract). For addresses and the ABI package, see the [contracts reference](/contracts/reference).\n\n## Failure modes\n\n- A `0x` result from `eth_getCode` means no contract is at the address. Re-read it from `contracts/addresses/40204.json`; the chain may have been re-rolled.\n- `-32601 Method not found` means the RPC method is misspelled or not served by the node.\n- An empty `eth_getLogs` response can mean the block range is wrong, the topic hash is wrong, or no such event has been emitted. Widen the range to test, then narrow it back.\n- A very wide `eth_getLogs` range can be refused or return a large payload. Page through narrow ranges instead.\n- A decoded value that looks wrong is usually a return-type mismatch. Confirm the type against the source.\n\n## Access and canon\n\nPublic and read-only. Every step here is a read against public on-chain data; no keys, no credentials, no gas. The public RPC hostname and the contract addresses are public values. Re-verify any address with step 1 before trusting it.\n\n## Source and verification\n\nFunctions and events verified against `citrate-chain` at SHA `9d5959e`: `contracts/src/NematocystSlashing.sol`. Regenerate the ABI with `forge build` (the `.abi` key of `out/NematocystSlashing.sol/NematocystSlashing.json`). Addresses live in `contracts/addresses/40204.json`, chain 40204, testnet beta. Status: Implemented, testnet beta, pre-audit.\n\nSee also [chain RPC](/chain/rpc) and the [chain CLI](/chain/cli).\n"},"/contracts/tutorials/read-a-contract":{"slug":"/contracts/tutorials/read-a-contract","title":"Read a contract","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/contracts/src (WrappedSALT.sol, LiquidStakingPool.sol)","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Step 1, confirm the contract exists","anchor":"step-1-confirm-the-contract-exists"},{"depth":3,"text":"Step 2, call a view function with raw eth_call","anchor":"step-2-call-a-view-function-with-raw-eth_call"},{"depth":3,"text":"Step 3, let cast encode and decode for you","anchor":"step-3-let-cast-encode-and-decode-for-you"},{"depth":3,"text":"Step 4, read a value that is computed live","anchor":"step-4-read-a-value-that-is-computed-live"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Read a deployed contract's state on the Citrate Network without sending a transaction. You will confirm the contract exists with `eth_getCode`, then call a view function with `eth_call`, first by encoding the selector yourself and then with tooling that does it for you. Read-only, no account, no gas, about ten minutes.\n\n## What it is\n\nReading a contract is a request, not a transaction. `eth_call` runs a function against the network's current state and hands back the return value; nothing is written, no fee is charged, and no signature is needed. The only inputs are the contract address and the function call data: a four-byte selector, the first four bytes of the keccak256 hash of the function signature, followed by any ABI-encoded arguments.\n\nThe examples below read two contracts in the network economics family: Wrapped SALT, an ERC-20 wrapper whose `symbol` and `decimals` are constants, and the liquid staking pool, whose share price is computed live. Both are described under [economics contracts](/contracts/economics).\n\n## How to use it\n\nYou need a reachable Citrate JSON-RPC endpoint. The public one is `https://rpc.citrate.ai`. A local node serves `http://127.0.0.1:8545`. You will also want `curl`, and the `cast` command from [Foundry](https://book.getfoundry.sh/cast/) for the encode-free path.\n\nSet up the same small helper used in [your first 10 minutes](/start/tutorials/your-first-10-minutes), so the steps stay short:\n\n```bash\nexport RPC=https://rpc.citrate.ai\n\nrpc () {\n curl -s \"$RPC\" -H 'content-type: application/json' \\\n -d \"{\\\"jsonrpc\\\":\\\"2.0\\\",\\\"id\\\":1,\\\"method\\\":\\\"$1\\\",\\\"params\\\":${2:-[]}}\"\n}\n```\n\nRead the address you want from `contracts/addresses/40204.json`, the canonical record described on the [contracts reference](/contracts/reference). The examples here use the Wrapped SALT and liquid staking pool addresses from that file; always confirm an address with step 1 before trusting it, because the chain can be re-rolled.\n\n### Step 1, confirm the contract exists\n\nAsk the network for the code at the address. A non-empty result means a contract is deployed there. A bare `0x` means the address is empty or an account, in which case stop, you have the wrong address.\n\n```bash\nrpc eth_getCode '[\"\", \"latest\"]'\n# {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x6080604052...\"} # long hex, a contract is here\n```\n\n### Step 2, call a view function with raw eth_call\n\n`WrappedSALT.symbol()` takes no arguments, so the call data is just its selector. The selector for `symbol()` is `0x95d89b41`. Put the address and the selector in an `eth_call`.\n\n```bash\nrpc eth_call '[{\"to\":\"\",\"data\":\"0x95d89b41\"},\"latest\"]'\n```\n\nThe `result` is an ABI-encoded string. Decoding it gives `wSALT`, the symbol declared as a constant in the source. You can compute any selector yourself with `cast sig \"symbol()\"`, which returns `0x95d89b41`.\n\n### Step 3, let cast encode and decode for you\n\nHand-encoding selectors is error-prone, so for everyday work let `cast` do it. You give it the human-readable signature and it handles both the encoding and the decoding.\n\n```bash\ncast call \"symbol()(string)\" --rpc-url $RPC # wSALT\ncast call \"decimals()(uint8)\" --rpc-url $RPC # 18\n```\n\n### Step 4, read a value that is computed live\n\n`LiquidStakingPool.getSharePrice()` returns SALT per staked-SALT share, scaled by 1e18, and returns exactly 1e18 when the pool is empty. It takes no arguments and reads live state.\n\n```bash\ncast call \"getSharePrice()(uint256)\" --rpc-url $RPC\n```\n\nA function that takes an argument encodes that argument into the call data after the selector. `cast` does the encoding from the signature.\n\n```bash\ncast call \\\n \"balanceOf(address)(uint256)\" \\\n 0x0000000000000000000000000000000000000000 \\\n --rpc-url $RPC\n# 0 (the zero address holds no shares)\n```\n\nOne caution. `LiquidStakingPool.balanceOf(address)` returns the SALT value of a staker's shares, not a raw token count, so the signature alone does not tell you the meaning. Read the contract's NatSpec before assuming what a function returns.\n\n## Reference\n\nThe functions read above, with their source files.\n\n| Contract | Function | Returns | Source |\n|---|---|---|---|\n| WrappedSALT | `symbol()` | `wSALT`, a constant | `contracts/src/WrappedSALT.sol` |\n| WrappedSALT | `decimals()` | `18`, a constant | `contracts/src/WrappedSALT.sol` |\n| LiquidStakingPool | `getSharePrice()` | SALT per share, 1e18-scaled | `contracts/src/LiquidStakingPool.sol` |\n| LiquidStakingPool | `balanceOf(address)` | SALT value of a staker's shares | `contracts/src/LiquidStakingPool.sol` |\n\nFor the full read surface and patterns like reading events, see [interact read-only](/contracts/tutorials/interact-read-only). For the address and ABI sources, see the [contracts reference](/contracts/reference).\n\n## Failure modes\n\n- A `0x` result from `eth_getCode` means no contract is at the address. Re-read it from `contracts/addresses/40204.json`; the chain may have been re-rolled since you copied it.\n- `-32601 Method not found` means the RPC method is misspelled or not served by the node.\n- A call that reverts comes back as an error, not a value. Check that the function exists in the ABI and that any arguments are well-formed.\n- A decoded value that looks wrong is often a signature mismatch. Confirm the return type against the source before reading meaning into it, as with `balanceOf` above.\n\n## Access and canon\n\nPublic and read-only. Nothing here writes state, so you can run it against any Citrate endpoint you can reach without risk. No keys, no credentials, no gas. The public RPC hostname and the contract addresses are public values; re-verify any address with step 1 before trusting it.\n\n## Source and verification\n\nFunctions verified against `citrate-chain` at SHA `9d5959e`: `contracts/src/WrappedSALT.sol` (`symbol`, `decimals`), `contracts/src/LiquidStakingPool.sol` (`getSharePrice`, `balanceOf`). Addresses live in `contracts/addresses/40204.json`, chain 40204, testnet beta. Status: Implemented, testnet beta, pre-audit.\n\nSee also [chain RPC](/chain/rpc) and the [chain CLI](/chain/cli).\n"},"/contracts/x402":{"slug":"/contracts/x402","title":"x402 payment contracts","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/contracts/src/{X402Facilitator,X402Paywall}.sol","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"X402Facilitator","anchor":"x402facilitator"},{"depth":3,"text":"X402Paywall","anchor":"x402paywall"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"These two contracts let a client pay for a single HTTP request rather than holding an account or pre-funding\nan allowance, following the x402 pattern that revives the HTTP 402 Payment Required response. A paywall\nverifies a signed payment and grants time-bound access; a facilitator settles such payments for a provider\nand takes a fee. This page is for contracted integrators building pay-per-request services on the Citrate\nNetwork.\n\n## What it is\n\nIn the x402 pattern a server answers a request with 402 Payment Required, the client signs an authorization\nto move payment, and the server grants access once that authorization settles. On the Citrate Network the\npayment is carried by Wrapped SALT, the EIP-3009 wrapper documented in\n[economics contracts](/contracts/economics): the client signs a `transferWithAuthorization` over wSALT, and\nthe server submits it. The client never needs to pre-approve an allowance, so the request can be paid for\nwith a single signature.\n\n| Surface | Contract | What it does |\n|---|---|---|\n| SC-x402-paywall | X402Paywall | verify a signed payment for a resource and grant time-bound access |\n| SC-x402-facilitator | X402Facilitator | settle signed payments on a provider's behalf, single or batched, taking a fee |\n\nBoth contracts are tier commercial. Their bytecode and interfaces are public on chain, but the settlement\nand fee logic is written for contracted builders. The paywall is explicitly a reference: a production\nprovider should review the access and replay properties against its own needs before relying on it.\n\nThis is the contract layer. The Citrate Network also accelerates x402 signature checks below the contract\nlayer, in the node's x402 payment precompiles at addresses `0x0200` to `0x0209`, which verify EIP-712 and\nEIP-3009 payloads about nine times more cheaply than the equivalent Solidity and fail closed by returning\nthe zero address on a bad signature. The contracts here do not call those precompiles directly; they settle\nthrough wSALT's EIP-3009 functions, and the precompiles are the faster path for a provider that verifies a\npayload off the contract path. For the precompile surface see [chain precompiles](/chain/precompiles), and\nfor how pay-per-request fits the compute marketplace see [Citrate Market](/compute/pool).\n\n## How to use it\n\nThe direct paywall path needs no facilitator. A provider runs it like this.\n\n1. Answer the client's request with 402 Payment Required, naming the resource and its price.\n2. The client signs a wSALT `transferWithAuthorization` to the provider's address for at least the price,\n with a fresh nonce and a validity window.\n3. The provider calls `X402Paywall.verifyAndGrant(resourceId, from, value, validAfter, validBefore, nonce,\n salt, v, r, s)`, where the `nonce` must equal `keccak256(abi.encode(resourceId, salt))` (the C047\n nonce-binding check). On success the payment settles to the provider and access is granted until `now + accessTTL`.\n4. The provider serves the resource. For later requests it checks `hasAccess(payer, resourceId)` and serves\n without a new payment until the grant expires.\n\nTo collect through a facilitator that takes a fee instead, the facilitator holds `FACILITATOR_ROLE` and\ncalls `X402Facilitator.settlePayment(...)` with the client's signed authorization; the gross value is split\ninto the net amount to the provider and the fee to the treasury inside wSALT, in one authorization.\n\n## Reference\n\nEvery function below is read from the cited `.sol` file at the audited SHA.\n\n### X402Facilitator\n\n`contracts/src/X402Facilitator.sol`, `is AccessControl, ReentrancyGuard`. Constructor\n`(wSALT, treasury, feeBps)`, with `feeBps` capped at 1000, ten percent. It settles signed payments and\nroutes a fee to the treasury. The role `FACILITATOR_ROLE` may settle; `DEFAULT_ADMIN_ROLE` may change the\nfee and treasury.\n\n| Function | Access | Purpose |\n|---|---|---|\n| `settlePayment(from, to, value, validAfter, validBefore, nonce, v, r, s)` | `FACILITATOR_ROLE` | settle one payment; consumes a single signed authorization for the gross `value` and splits net plus fee inside wSALT |\n| `batchSettle(PaymentAuthorization[] payments)` | `FACILITATOR_ROLE` | settle a batch, one authorization each, emitting a `BatchSettled` summary |\n| `setFacilitatorFee(uint256 newFeeBps)` | `DEFAULT_ADMIN_ROLE` | change the fee, capped at 1000 bps |\n| `setTreasury(address newTreasury)` | `DEFAULT_ADMIN_ROLE` | change the fee recipient |\n\nSettlement calls `wSALT.transferWithFeeAuthorization`, which consumes one EIP-3009 authorization for the\ngross `value` and splits it internally, so no separate ERC-20 approval is needed and the single-signature\nproperty holds. Public state: `wSALT`, immutable, `treasury`, `feeBps`, and the `FACILITATOR_ROLE`\nidentifier. The `PaymentAuthorization` struct is `(from, to, value, validAfter, validBefore, nonce, v, r,\ns)`. Events: `PaymentSettled(from, to, value, fee, nonce)`, `BatchSettled(count, totalValue, totalFees)`,\n`FeeUpdated`, `TreasuryUpdated`.\n\n### X402Paywall\n\n`contracts/src/X402Paywall.sol`. A reference resource gate. Constructor `(wSALT, resourcePrice)`; the\ndeployer becomes the `provider`. Access is time-bound: `accessGrantedUntil[key]` holds the expiry timestamp\nand `accessTTL` is applied at grant time, defaulting to `DEFAULT_ACCESS_TTL = 1 days`.\n\n| Function | Access | Purpose |\n|---|---|---|\n| `verifyAndGrant(resourceId, from, value, validAfter, validBefore, nonce, salt, v, r, s)` | any caller | require `value >= resourcePrice`, `nonce == keccak256(abi.encode(resourceId, salt))`, and that current access has expired, run the wSALT transfer to the provider, and grant access for `now + accessTTL` |\n| `hasAccess(address payer, bytes32 resourceId)` | view | true while the grant's expiry is in the future |\n| `setPrice(uint256 newPrice)` | the provider | change the resource price |\n| `setAccessTTL(uint256 newTTL)` | the provider | change the access window; must be greater than zero |\n\nPublic state: `wSALT` and `provider`, both immutable, `resourcePrice`, `accessGrantedUntil`, `accessTTL`,\nand `DEFAULT_ACCESS_TTL`. The access key is `keccak256(abi.encodePacked(from, resourceId))`. Events:\n`AccessGranted(payer, resourceId, amount, expiresAt)`, `PriceUpdated`, `AccessTtlUpdated`.\n\nA direct paywall purchase reads as follows.\n\n```text\n1. Client GET /resource -> 402 Payment Required (price, resourceId)\n2. Client signs wSALT transferWithAuthorization to the provider\n3. Provider calls X402Paywall.verifyAndGrant(resourceId, from, value, ...)\n4. On success the payment settles; access is valid for accessTTL\n```\n\n## Design rationale\n\nThe split between a paywall and a facilitator follows the two ways a provider actually collects. A small\nprovider gating its own endpoint wants no intermediary, so the paywall settles straight to the provider and\nrecords nothing but an expiry. A provider that wants someone else to handle settlement, or that batches many\npayments, uses the facilitator, which is why the fee leg lives there and not in the paywall. Both lean on\nwSALT's fee-bearing authorization so the client signs once for the gross amount and the contract does the\nrouting; the alternative, a separate allowance and a second transfer, would have meant either an unbounded\napproval or a settlement that could half-complete. Access is time-bound rather than permanent so a paid\ngrant expires and the same resource can be sold again, which is the normal shape of a metered service.\n\n## Failure modes\n\nThese contracts collect payment, so they are written to fail closed.\n\n- `verifyAndGrant` reverts if the payment is below the price, and refuses to grant while an existing grant is\n still active, so a single signed authorization cannot be replayed into a second window. The wSALT transfer\n itself rejects a reused nonce or an expired validity window.\n- The facilitator's fee is capped at ten percent in both the constructor and `setFacilitatorFee`, and only\n `FACILITATOR_ROLE` may settle, so an arbitrary caller cannot push payments through it.\n- The fee split happens inside wSALT against a digest that binds `(treasury, fee)`, so a submitter cannot\n redirect the fee, and the authorization is marked used once, so a settlement cannot be repeated.\n- As a reference, the paywall leaves one judgment to the integrator: its access window is a single\n per-`(payer, resourceId)` expiry, which is correct for a metered resource but should be reviewed for a\n service that needs per-request rather than per-window gating.\n\n## Access and canon\n\nTier commercial. These contracts are the network's pay-per-request settlement path; the bytecode and\ninterfaces are public on chain, but the settlement and fee detail is written for contracted builders. The\npaywall is a reference implementation for documentation and testing, not a turnkey production gate. No keys,\nmnemonics, or private endpoints appear on this page or are needed to read these contracts.\n\n## Source and verification\n\n- Source repo: `citrate-chain` at SHA `9d5959e`.\n- Files: `contracts/src/X402Facilitator.sol` and `contracts/src/X402Paywall.sol`.\n- Related: the wSALT wrapper these settle through is in `contracts/src/WrappedSALT.sol`, documented in\n [economics contracts](/contracts/economics); the node-level x402 precompiles are at `0x0200` to `0x0209`,\n documented in [chain precompiles](/chain/precompiles).\n- Addresses: both contracts are in the canonical registry `contracts/addresses/40204.json` for chain 40204,\n as `X402Facilitator` and `X402Paywall`. Resolve from there and confirm with `eth_getCode` before sending\n value.\n- Status: Implemented, pre-audit. Both contracts run on testnet 40204 and carry SOL-01 and SOL-12\n remediations in their source comments, but no external third-party audit has been completed. Treat as\n experimental.\n"},"/core/fleet-wizard":{"slug":"/core/fleet-wizard","title":"Connect your machines (fleet wizard)","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-core/src-tauri/src/fleet*.rs, src/fleet, docs/FLEET_WIZARD_RUNBOOK.md","syncedSha":"81ef7a0","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The fleet wizard connects the machines you own that run Citrate Core, so they know about each other\nand can work as one fleet. This page is for members with a second laptop, a desktop or a home server.\nThe technical runbook is\n[FLEET_WIZARD_RUNBOOK.md](https://github.com/CitrateNetwork/citrate-core/blob/main/docs/FLEET_WIZARD_RUNBOOK.md)\nin the core repo.\n\n## What it is\n\nThe wizard lives on the **Cluster** surface. It checks this machine, can look for your other\nmachines on the local network if you allow it, pairs two machines with a one-time link or QR code,\nand helps with Tailscale when the machines cannot reach each other. When both machines are linked to\nyou, the pairing also carries each machine's link code, so each one knows the other is yours.\n\n## How to use it\n\n1. On the machine that already runs Citrate Core, open **Cluster** and start the wizard. It shows\n this machine's tier (from the same local check as onboarding) and a suggested role. Rename the\n machine if you like; that name is what your other machines see.\n2. Optional: tick **Find machines** to look for other Citrate Core machines on this network. It is\n off by default and turns itself off when the wizard closes.\n3. Choose **Create pairing link**. The wizard shows a `citrate://pair` link and the same link as a\n QR code. It works once and expires after 10 minutes.\n4. On the new machine, install Citrate Core if needed (the pair step shows the download link and a\n QR code for it). Paste the pairing link, choose **Check link**, then **Pair with this machine**.\n Opening the link from the system also works; nothing pairs until you press **Pair**.\n5. If the machines cannot reach each other, the wizard reads Tailscale's status and tells you what\n to do next.\n6. **Your machines** lists this machine, the ones you paired and the ones seen on the network, each\n with tier and role.\n\n## Reference\n\n| Property | Value |\n|---|---|\n| Pairing link lifetime | 10 minutes (a default pending owner sign-off) |\n| Uses per link | one; a second use is refused as \"already used\" |\n| Open links per machine | at most 8 |\n| What discovery shares | a random per-run id, the machine name you typed, tier and role; never an account address or the computer's name |\n| Roles | T0 light, T1 worker, T2 heavy (defaults pending owner sign-off) |\n| Tailscale | read only: the wizard runs `tailscale status` and never signs you in or changes its settings |\n\n## Design rationale\n\nA pairing link is signed with a key that exists only in memory while the app runs. It is never the\nkey for your account and can sign nothing but pairing links, so pairing two machines cannot move\nvalue. A short lifetime and a single use mean a link that leaks in a chat or a screenshot is soon\nworthless. Discovery is opt-in and forgets your choice on restart, because announcing yourself on a\nshared network should be a decision you make each time.\n\n## Failure modes\n\n- **macOS asks to accept incoming connections** when you create a link. Allow it, or pairing over\n the local network cannot reach this machine.\n- **No other machines answered.** The other machine needs discovery on too, and some guest or office\n networks block it. Pairing by link does not need discovery.\n- **The other machine could not be reached.** Check the firewall, or turn on Tailscale on both\n machines with the same account and create a new link.\n- **\"Already used\" or \"expired\".** Create a new link on the first machine.\n- **A machine that belongs to someone else.** If the pairing carries another member's link code,\n the machine is reported and not added as yours.\n\n## Access and canon\n\nThe roster of your machines is a file in the app data folder on each machine. Nothing about the\nfleet is published to the network by the wizard.\n\n## Source and verification\n\nSource: `citrate-core/src-tauri/src/fleet.rs`, `fleet_pairing.rs`, `fleet_mdns.rs`,\n`fleet_tailscale.rs`, and `src/fleet/`. Audited against core `81ef7a0`. Status: **Implemented**,\npre-audit. Device links are **Verified** as a TLA+ model (`DeviceLink.tla` in `citrate-cluster`)\nchecked with TLC at small bounds. A recorded two-machine run on member hardware is still pending.\n"},"/core/for-agents":{"slug":"/core/for-agents","title":"For agents and headless operators","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-core/src/surfaces/Agent.tsx, src-tauri/src/ceremony.rs, src-tauri/src/lib.rs","syncedSha":"2d88191","toc":[{"depth":2,"text":"The one rule that makes this safe","anchor":"the-one-rule-that-makes-this-safe"},{"depth":2,"text":"What an agent can drive","anchor":"what-an-agent-can-drive"},{"depth":2,"text":"Connecting an external agent","anchor":"connecting-an-external-agent"},{"depth":2,"text":"A note on running unattended","anchor":"a-note-on-running-unattended"}],"body":"Citrate Core is designed to be operated by an agent as well as a person. This page is for anyone pointing an\nagent at their node and account, whether that is the on-device agent in the `Agent` surface or an external\none you connect.\n\n![The Agent surface: a keyless workbench whose actions route to you for approval.](/core/app-agent.png)\n\n## The one rule that makes this safe\n\nAn agent can do a great deal in Citrate Core, but it cannot sign. Every action that would produce a\nsignature, sending value, staking, registering the node, any on-chain write, is routed to the\nSignatureCeremony and waits for a human approval. The agent holds no key and cannot approve on your behalf.\nThis is what lets you give an agent broad reach without handing it your account.\n\nBecause of this, the useful division of labor is: let the agent read, plan, monitor, and prepare; keep the\napproval with a person. An agent can watch the node, summarize activity, draft an action, and queue it for\nyou; you approve the moment that matters.\n\n## What an agent can drive\n\nThrough the app's command surface, an agent can:\n\n- Start, stop, and monitor the node, and read its status, peers, and logs.\n- Read the account: balances, staking position, and activity.\n- Read and write the memory graph and files.\n- Prepare an on-chain action and submit it to the ceremony for approval.\n- Use on-device inference and the model surface.\n\nAnything that signs stops at the approval step. A locked account fails those requests closed rather than\ndegrading to something less safe.\n\n## Connecting an external agent\n\nThe app exposes its capabilities to agents over a local command surface rather than by handing out keys.\nThe same ceremony gate applies to an external agent as to the built-in one: it can request an action, and a\nperson approves it. Keep the node's ports on loopback while doing this; the agent talks to the app, not to\nan exposed node.\n\n## A note on running unattended\n\nIf you want the node to run while you are away, that is fine: the supervisor keeps the process healthy on\nits own and an agent can monitor it. What you should not do is arrange for signatures to happen without a\nperson. The design deliberately has no unattended-signing path, and working around it would defeat the\nprotection the ceremony exists to give you. Queue actions for approval instead, and approve them when you\nare back.\n\nFor the signing model in full, see [keys and safety](/core/safety). For the node lifecycle, see\n[run a node](/core/run-a-node).\n"},"/core/getting-started":{"slug":"/core/getting-started","title":"Getting started with Citrate Core","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-core/README.md, src/onboarding/Onboarding.tsx, src/App.tsx, src-tauri/src/node.rs","syncedSha":"2d88191","toc":[{"depth":2,"text":"Install and open","anchor":"install-and-open"},{"depth":2,"text":"Step 0: Welcome","anchor":"step-0-welcome"},{"depth":2,"text":"Step 1: Sign in","anchor":"step-1-sign-in"},{"depth":2,"text":"Step 2: Verify identity","anchor":"step-2-verify-identity"},{"depth":2,"text":"Step 3: Membership","anchor":"step-3-membership"},{"depth":2,"text":"Step 4: Account ready","anchor":"step-4-account-ready"},{"depth":2,"text":"Step 5: Grant and stake","anchor":"step-5-grant-and-stake"},{"depth":2,"text":"Step 6: Node ignition","anchor":"step-6-node-ignition"},{"depth":2,"text":"After onboarding","anchor":"after-onboarding"}],"body":"This page walks the first run of Citrate Core from the welcome screen to a live node, one step at a time.\nThe whole flow is a guided sequence: you sign in, verify who you are, take a membership, receive your\naccount and your grant, stake, and start the node. Every signature along the way goes through the on-screen\napproval described in [keys and safety](/core/safety).\n\n## Install and open\n\nDownload Citrate Core for your platform and open it. On first launch the app provisions a fresh local\nidentity and the onboarding sequence begins. If you are building from source instead, see the repository\nREADME; the packaged download is the supported path for most people.\n\n## Step 0: Welcome\n\nThe welcome screen states what the app is and offers two doors. **Join the network** starts the guided\nsetup. **Explore free** opens the app in a read-only preview so you can look around before you commit to a\nmembership.\n\n![Step 0, the welcome screen: the app introduces itself and offers Join the network or Explore free.](/core/onboarding-s0-welcome.png)\n\n## Step 1: Sign in\n\nYou sign in to a Citrate identity. The app opens a sign-in against the Citrate authority (`auth.citrate.ai`)\nover a local loopback so the exchange stays on your machine. A first-time visitor creates an identity here;\na returning one signs back in.\n\n![Step 1, sign in: authenticate to your Citrate identity to continue past the welcome screen.](/core/onboarding-s1-sign-in.png)\n\n## Step 2: Verify identity\n\nThe app asks you to verify your identity through VERI, Citrate's in-house verification, as part of membership. You\ncomplete a short check here. The result gates the steps that follow; the verification itself is handled by\nthe identity service, and Citrate Core only carries the outcome.\n\n![Step 2, verify identity: complete the VERI check that gates membership.](/core/onboarding-s2-verify-identity.png)\n\n## Step 3: Membership\n\nMembership is the single thing that funds everything else. Taking it here is what later covers your account\nand your validator stake and opens every surface. The step shows what the membership includes and confirms\nthe amount before anything is charged.\n\n![Step 3, membership: review and take the membership that funds the account and the stake.](/core/onboarding-s3-membership.png)\n\n## Step 4: Account ready\n\nYour Citrate Keyring account is provisioned. This is a smart account on chain 40204: it deploys lazily on\nits first outgoing action, and deposits to its address are safe immediately. The step confirms the account\nis ready and shows its address so you can receive to it.\n\n![Step 4, the account is ready: your Citrate Keyring smart account is provisioned and can receive.](/core/onboarding-s4-wallet-ready.png)\n\n## Step 5: Grant and stake\n\nMembership releases a grant into your account, and the grant is placed into a validator stake so your node\ncan take part in producing blocks. This is a signed sequence: the app walks you through it and asks you to\napprove each on-chain action. When it finishes, your stake is bonded and the membership token is minted to\nyour account.\n\n![Step 5, grant and stake: the grant lands in your account and bonds into a validator stake, step by step.](/core/onboarding-s5-grant-and-stake.png)\n\n## Step 6: Node ignition\n\nThe last step starts your node. The app spawns the node process, joins it to chain 40204, and shows it\ncoming online. It also offers to download the local inference model so on-device AI works without a network\nround trip. From here the app shell opens onto the full sidebar.\n\n![Step 6, node ignition: the node process starts, joins chain 40204, and comes online.](/core/onboarding-s6-node-ignition.png)\n\n## After onboarding\n\nYou land on the [dashboard](/core/tour). Two good next moves:\n\n- Read [run a node](/core/run-a-node) to understand what your node is doing, what it needs, and how to keep\n it healthy at home or in a business.\n- Read [keys and safety](/core/safety) so you understand where your keys live, how signing works, and how\n to back up your recovery phrase.\n\nIf you chose **Explore free** at the welcome screen, you can return to this sequence at any time from the\nmembership prompt; nothing on the network is charged until you take the membership at step 3.\n"},"/core/hermes":{"slug":"/core/hermes","title":"Hermes, the agent in Citrate Core","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-core/src/agent, src-tauri/src/hermes.rs; citrate-agent-runtime/agent-sidecar, agent-loop","syncedSha":"81ef7a0","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Hermes is the agent that runs inside Citrate Core, on your own machine and on a model your machine\ncan hold. This page is for members who want to know what Hermes can do in Citrate Core 0.5.0, what it\nasks you before it acts, and where the technical detail lives.\n\n## What it is\n\nHermes proposes; you decide. It can read, plan, search, write inside folders you grant, run a\ncontract through tests and audits, and prepare an on-chain action. Every effect on the world, such\nas a signature, a transaction, a file write outside a granted folder, a shell command, or a skill or\nmemory it wants to keep, waits behind a Human In Control (HIC) gate. Nothing is reported as done\nunless a check outside the model, such as a passing test or an audit report, says so.\n\nHermes runs as a separate process next to the app, the sidecar. The sidecar holds no key and cannot\nsign. When Hermes needs a signature it asks Citrate Core, and Core opens the same signing ceremony\nyou already use for every other approval. The chat in the app, the `citrate-agent` command line, and\nan MCP client can all look at the same Hermes session.\n\n## How to use it\n\n1. Open the **Agent** surface. Hermes starts with the model your machine was matched to during\n onboarding (the tier probe picks it; a small machine gets a small model).\n2. Ask for what you want in plain language. For a longer piece of work, pick a track (full project,\n smart contract, code, creative, or project management) and answer its short interview.\n3. Pick a voice if you like, at the end of onboarding or in **Settings** under **Hermes, persona**.\n Hermes ships with six [personas](/core/personas); each changes tone and the skills on offer, never\n the approval rules.\n4. Watch the approvals. An action that needs you appears as a card that says what will happen.\n Allow it once, or deny it.\n5. Turn on the extras you want in **Settings**. Web search, page reading, the managed browser, the\n shell and the [node MCP server](/core/node-mcp) are all off until you turn them on.\n\n## Reference\n\n| Ability | Default | Where it is described |\n|---|---|---|\n| Chat with a local model | on | [Getting started](/core/getting-started) |\n| Folder grants: read and write only inside folders you choose | no folder granted | [agent-grants](https://github.com/CitrateNetwork/citrate-agent-runtime/tree/main/agent-grants) |\n| Web search, page reading and the decide step | off | [HERMES_WEB_SEARCH_AND_DECIDE](https://github.com/CitrateNetwork/citrate-core/blob/main/docs/HERMES_WEB_SEARCH_AND_DECIDE.md) |\n| The Browser pop-out, where you watch Hermes browse | off | [HERMES_BROWSER](https://github.com/CitrateNetwork/citrate-core/blob/main/docs/HERMES_BROWSER.md) |\n| Sign-in to sites you chose, a bounded number of times | no budget granted | [WEB_SIGNING_BUDGETS](https://github.com/CitrateNetwork/citrate-core/blob/main/docs/WEB_SIGNING_BUDGETS.md) |\n| Tools from MCP servers you add | none added | [MCP_USER_SERVERS](https://github.com/CitrateNetwork/citrate-core/blob/main/docs/MCP_USER_SERVERS.md) |\n| Skills, and learning a new one from verified work | reviewed set installed | [Skills](/core/skills) |\n| Deploying a contract through the deploy gate | deploy waits for a READY verdict and your approval | the DeployGate section of [formal/README](https://github.com/CitrateNetwork/citrate-core/blob/main/src-tauri/formal/README.md); deploy gas from the faucet (off by default): [FAUCET_IN_APP](https://github.com/CitrateNetwork/citrate-core/blob/main/docs/FAUCET_IN_APP.md) |\n| Connecting your other machines | off | [Fleet wizard](/core/fleet-wizard) |\n\n## Design rationale\n\nOpen agents fail in familiar ways: they grade their own work as a success, they act before asking,\nand they hang without saying why. Hermes is built against each of those. The model never decides\nthat a step is done; verifiers do. The sidecar cannot sign at all, so a mistake in the agent cannot\nmove value without you. Each risky ability starts off, so a member who never opens Settings gets the\nsame app as before plus a better chat.\n\n## Failure modes\n\n- **A locked account.** Any request that would sign fails closed. Unlock and ask again.\n- **Untrusted content.** Once a session has read a web page or MCP output, it is marked as tainted.\n A tainted session cannot keep a skill or memory, and its effectful calls always ask you.\n- **The sidecar stops.** Core restarts it under a supervisor. A request that never reached a\n decision is not signed later; Hermes has to ask again.\n- **A small model.** Small models make more tool-call mistakes. Hermes on a small machine uses a\n guided mode with fewer tools at a time.\n\n## Access and canon\n\nHermes runs on your hardware. Prompts, files and memories stay on your machine unless you turn on a\nfeature that says it sends something out (web search sends the query; the Jina reader sends the URL).\nEvery decision you make on an approval card is written to a local decision log.\n\n## Source and verification\n\nSource: `citrate-core` (the app and Core side) and `citrate-agent-runtime` (the sidecar, the loop,\nthe tools). Audited against core `81ef7a0`. Status: **Implemented**, pre-audit, arriving with\nCitrate Core 0.5.0. The agent loop, folder grants, sign-in budgets, the deploy gate and the spend\nbudget are each **Verified** as a TLA+ model checked with TLC at small bounds; that checks the\ndesign, not the code.\n"},"/core/node-mcp":{"slug":"/core/node-mcp","title":"Use your node from another agent (node MCP server)","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-core/src-tauri/src/node_mcp_*.rs, docs/NODE_MCP_SERVER.md","syncedSha":"81ef7a0","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Citrate Core can act as an MCP server, so an agent you already use (Claude Code, Cursor, or Hermes\nitself) can read your node and ask you to approve an action. This page is for members who want to\nconnect one. The full technical reference is\n[NODE_MCP_SERVER.md](https://github.com/CitrateNetwork/citrate-core/blob/main/docs/NODE_MCP_SERVER.md)\nin the core repo.\n\n## What it is\n\nMCP (the Model Context Protocol) is a common way for an agent to discover and call tools. With the\nnode MCP server on, Citrate Core listens on this computer only and offers a set of read tools (node\nstatus, the chain head, balances, contract reads, the shared knowledge graphs, your clusters) and a\nsmall set of write tools. A write tool never acts by itself: it creates a request, the request shows\nup in the app, and anything that signs goes through the signing ceremony you already know.\n\n## How to use it\n\n1. In Citrate Core, open **Settings**, then **API endpoints & keys**, then **Node MCP server**.\n2. Turn it on. It listens at `http://127.0.0.1:47204/mcp`, on this computer only.\n3. Create a connect token and give it the name of the client it is for. The token is shown once,\n with ready-made commands for that client. Copy it then; Core keeps only a hash of it.\n4. Add the server to your client. For Claude Code, paste the command the panel shows. The panel also\n offers a stdio form that runs the app binary as a small relay.\n5. Ask your agent something simple, such as \"use citrate-node to show the chain head\".\n6. Revoke a token from the same list at any time. Its next request is refused and its pending\n requests close.\n\nTo let Hermes use the same tools, turn on **Your node** in the Agent surface under **Connected tools\n(MCP)**. Hermes gets the read tools only.\n\n## Reference\n\n| Kind | Examples | What happens |\n|---|---|---|\n| Read tools | `node_status`, `chain_head`, `get_balance`, `chain_call`, `get_logs`, `memory_search`, `cluster_status` | Answer at once. Each answer says whether it came from your node or the public 40204 endpoint. |\n| Write tools | `tx_propose`, `deploy_propose`, `pin_add`, `invite_create`, `cluster_join` | Create a request you approve in the app. A transaction is signed only through the signing ceremony. |\n| Budgeted tool | `faucet_request` | Runs inside a faucet budget you granted in Settings, Budgets. Off by default. |\n| Status | `request_status` | A client sees only its own requests: pending, approved, rejected, failed or expired. |\n\nThe complete list, the limits and the protocol notes are in\n[NODE_MCP_SERVER.md](https://github.com/CitrateNetwork/citrate-core/blob/main/docs/NODE_MCP_SERVER.md).\n\n## Design rationale\n\nA connect token is a password for one client, so it is shown once, stored only as a hash, and\nrevocable. The server binds to loopback so nothing on your network can reach it. Before the stdio\nrelay sends a token at all, it checks that the program on the port really is Citrate Core, so\nanother program that grabs the port while Core is closed never sees a token. Write tools only queue\nrequests, because an agent outside the app should have no more power than Hermes has inside it.\n\n## Failure modes\n\n- **The port is taken.** The panel shows the error. The port is a default pending owner sign-off.\n- **A token leaks.** Revoke it. Its sessions end and its pending requests close.\n- **A deploy is not ready.** `deploy_propose` is refused at once, naming the failing checks, unless\n the deploy gate marked exactly that code READY.\n- **Your node is still syncing.** Reads fall back to the public 40204 endpoint and say so.\n\n## Access and canon\n\nThe server is off until you turn it on and listens on this computer only. Personal memory is never\noffered to a token. Hermes's own token is read-only and lives in memory until the app quits.\n\n## Source and verification\n\nSource: `citrate-core/src-tauri/src/node_mcp_*.rs` and `hermes_mcp.rs`. Audited against core\n`81ef7a0`. Status: **Implemented**, pre-audit, off by default. External client runs with Claude Code\nand with Hermes are recorded in the core reference. The port, the task lifetime and whether Hermes\nmay use write tools are defaults pending owner sign-off.\n"},"/core/overview":{"slug":"/core/overview","title":"Citrate Core","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-core/README.md, src/App.tsx, src/shell/Sidebar.tsx, docs/CITRATE_CORE_FEATURE_MAP_AND_SITEMAP.md","syncedSha":"2d88191","toc":[{"depth":2,"text":"What is inside the window","anchor":"what-is-inside-the-window"},{"depth":2,"text":"How it is built","anchor":"how-it-is-built"},{"depth":2,"text":"What it connects to","anchor":"what-it-connects-to"},{"depth":2,"text":"Where to go next","anchor":"where-to-go-next"}],"body":"Citrate Core is the desktop home for the whole federation. It is one window that holds a Citrate Keyring\naccount, runs a full Citrate Network node, and opens onto every service in the network: the account and\nits staking position, a local reader for the BlockDAG, on-device AI inference, file storage, a memory\ngraph, groups and messaging, a compute cluster, model training, and an agent workbench. One membership\nfunds the account, covers the validator stake, and unlocks every surface. Keys are sealed in the operating\nsystem keyring and never leave the machine.\n\nIt is for people who want to hold their account and run a node on their own hardware, at home or in a\nbusiness, without stitching a browser, a key-holding extension, and a server together by hand. It runs on macOS,\nLinux, and Windows. Chain id is 40204 (testnet beta).\n\n![The Citrate Core dashboard: the node vitals strip, the on-device agent, recent activity, and tutorials, inside the grouped sidebar.](/core/app-dashboard.png)\n\n## What is inside the window\n\nThe window is a left sidebar and a main surface. The sidebar groups the surfaces the way you use them\n(`src/shell/Sidebar.tsx`):\n\n- **You** holds your own things: `Dashboard`, the `Wallet` account view, `Storage` (your memory graph),\n `Files` (your storage on the network), `Models`, `Agent`, `Connections`, and `Journal`.\n- **Your Groups** holds shared things: `People`, `Groups`, `Comms`, `Cluster`, `Train`, and `Community`.\n- **Your Node** holds `Node`, the operator surface for the node this app runs.\n- **More** holds `Commissary`, `Settings`, and the `ALF` learning surface.\n\nThe [tour](/core/tour) walks every surface with a screenshot. If you are installing for the first time,\nstart with [getting started](/core/getting-started); if you mainly came to run a node, jump to\n[run a node](/core/run-a-node).\n\n## How it is built\n\nCitrate Core is a Tauri application: a Rust backend (`src-tauri/`) and a React interface (`src/`), packaged\nas one signed desktop app. The backend supervises the node process and other local services (the inference\nruntime, an IPFS node, the memory service), talks to chain id 40204, and holds the account. Every signature,\nwhether it comes from you, from the on-device agent, or from a background service, goes through one\nHIC (Human In Control) approval path called the SignatureCeremony (`src-tauri/src/ceremony.rs`). Nothing else can\nsign, and no key or recovery phrase ever crosses the interface boundary. Key handling and safe operation are\ncovered in [keys and safety](/core/safety).\n\n## What it connects to\n\nCitrate Core is a client for the live federation, not a private copy of it. It reads and writes against the\nsame services documented across this handbook:\n\n| Surface in Core | What it talks to |\n|---|---|\n| `Node` | the Citrate Network node it runs locally, joined to chain 40204 |\n| `Wallet` | your account and its staking position, through the [Citrate Keyring](/aa/identity) |\n| `Models`, `Agent` | on-device inference and the [inference gateway](/sdks/inference-gateway) |\n| `Files`, `Storage` | network file storage and the [memory graph](/apps/memories) |\n| `Comms`, `Groups` | the server-blind [messaging relay](/apps/comms) |\n| `Train` | federated [model training](/research/learning) rounds |\n\n## Where to go next\n\n1. [Getting started](/core/getting-started) installs the app and walks the first-run flow end to end.\n2. [A tour of Citrate Core](/core/tour) is the screen-by-screen reference.\n3. [Run a node](/core/run-a-node) covers the `Node` surface and safe operation at home or in a business.\n4. [Keys and safety](/core/safety) covers the keyring, signing, backups, and updates.\n5. [For agents](/core/for-agents) covers operating the node and account from an agent.\n"},"/core/personas":{"slug":"/core/personas","title":"Hermes personas and tracks","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-agent-runtime/agent-loop/personas/personas.toml, agent-loop/tracks, agent-loop/PERSONAS.md; citrate-core/src/components/PersonaPicker.tsx","syncedSha":"81ef7a0","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"A persona is a voice for Hermes: how it writes, which skills it reaches for, and where it starts. A\ntrack is a goal, such as a full project or a contract audit. This page is for members choosing a\npersona or a track. The technical reference is\n[PERSONAS.md](https://github.com/CitrateNetwork/citrate-agent-runtime/blob/main/agent-loop/PERSONAS.md)\nin the runtime repo.\n\n## What it is\n\nEach persona bundles a voice and tone, a few writing rules, a default track and workflow, up to four\ntools it keeps in view, and a list of the skills it may load. Any persona can run any track. A persona\nchanges tone and the skills on offer. It never grants a tool, changes an approval, or touches the\nsigning ceremony.\n\n## How to use it\n\n1. Choose a persona at the end of onboarding, or later in **Settings** under **Hermes, persona**.\n The default is Hermes's own voice, which changes nothing.\n2. Start a piece of work. Hermes opens with the persona's default track unless you pick another.\n3. Run a track workflow from the chat with `/run `, for example `/run status-note`.\n4. To make your own persona, use the custom form in the same Settings card: a name, a voice, a tone\n and one to twelve writing rules. It is checked before it is saved, and it stays on your machine.\n5. Turn on **Read replies aloud** if you want Hermes to speak. It is off by default and uses your\n system's voice.\n\n## Reference\n\nThe six shipped personas. The names were chosen by the owner on 2026-10-01; the role is the stable\npart, and a rename never loses your saved choice.\n\n| Name | Role | Voice | Starts with |\n|---|---|---|---|\n| Graft | Builder: ships code and dApps | Direct and terse; shows the diff or the command | full project, hello mint |\n| Pith | Auditor: a skeptical reviewer | Calm and evidence-first; says \"not ready\" plainly | smart contract, audit a contract |\n| Zest | Maker: creative work | Playful and visual; offers options | creative, creative project |\n| Trellis | Steward: plans and tracks | Organized and brief; checklists | project management, project plan |\n| Sprout | Guide: onboarding and teaching | Warm and patient; explains why | full project, launch checklist |\n| Crew | Operator: nodes, fleet and learning together | Precise and numbers-first | project management, status note |\n\nThe five tracks are **full project**, **smart contract**, **code**, **creative** and **project\nmanagement**. Each owns a short interview and a family of workflows. A workflow is a list of steps,\nand only its checks (tests, scans, required answers) say a step is done.\n\nGuide and Operator have no track of their own yet; they start on the nearest one. That choice, and\nthe unset speaking voice, are shipped defaults pending owner sign-off.\n\n## Design rationale\n\nKeeping voice and capability apart is what makes personas safe to customize. A custom persona can\nchange how Hermes talks, but there is nothing in a persona that could widen what Hermes is allowed to\ndo. Narrowing the skill list per persona also keeps each request small, which matters on a local\nmodel.\n\n## Failure modes\n\n- **A persona names a skill that is not installed.** It is skipped and reported, never invented.\n- **A custom persona is malformed.** The check refuses it with a reason; nothing is saved.\n- **A custom persona tries to add a heading or instructions.** Each field is collapsed to one line,\n so it cannot open a new section of the prompt.\n\n## Access and canon\n\nYour persona choice and any custom persona are stored with your local settings.\n\n## Source and verification\n\nSource: `citrate-agent-runtime/agent-loop/personas/personas.toml` (the one data file for shipped\npersonas), `agent-loop/tracks/`, and `citrate-core/src/components/PersonaPicker.tsx`. Audited against\ncore `81ef7a0`. Status: **Implemented**, pre-audit.\n"},"/core/run-a-node":{"slug":"/core/run-a-node","title":"Run a node from Citrate Core","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-core/src/surfaces/Node.tsx, src-tauri/src/node.rs, src-tauri/config/member-node.toml","syncedSha":"2d88191","toc":[{"depth":2,"text":"The Node surface","anchor":"the-node-surface"},{"depth":2,"text":"What the node needs","anchor":"what-the-node-needs"},{"depth":2,"text":"How it stays correct","anchor":"how-it-stays-correct"},{"depth":2,"text":"Keeping it healthy","anchor":"keeping-it-healthy"},{"depth":2,"text":"Running it headless","anchor":"running-it-headless"}],"body":"Citrate Core runs a full Citrate Network node for you, supervised, inside the app. The `Node` surface is\nthe console for it. This page is for anyone keeping a node healthy at home or in a business. For the\ndaemon-level runbook (the same node, run by hand outside the app), see\n[run a Citrate Node](/operators/run-a-node).\n\n![The Node surface: start and stop, the live log tail, peers, validator status, resources, and crash records.](/core/app-node.png)\n\n## The Node surface\n\nThree tabs: **Operations**, **Earning**, and **Pinning**.\n\nOperations is the console. **Start** and **Stop** control the process. Below them:\n\n- **Log tail** streams the node log (`~/.citrate/core/node.log`) while it runs.\n- **Peers** lists who the node is connected to.\n- **Validator** shows whether your stake is registered, how many blocks you have proposed, your election\n odds, and how many times the supervisor has restarted the process.\n- **Resources and sidecars** shows CPU, memory, the data directory, and whether encryption at rest is on.\n Encryption at rest reads `ON, keyring`: the node's on-disk state is sealed with a key held in the\n operating system keyring.\n- **Crash records** lists any supervised restarts. The supervisor restarts the process with backoff, so a\n single sidecar failure does not take the app down.\n\n## What the node needs\n\nA node is a long-running process that keeps a copy of the ledger live and, once your stake is registered,\ntakes part in producing blocks. Practical requirements:\n\n| Need | Detail |\n|---|---|\n| Network | outbound peer connections on port `30303`; the app dials the network for you |\n| Local ports | the node binds its RPC to `127.0.0.1:8545` and WebSocket to `127.0.0.1:8546`, loopback only |\n| Disk | a data directory under the app's data folder; plan for the ledger to grow over time |\n| Memory | syncing is memory-heavy; budget several gigabytes of headroom while the node catches up |\n| Uptime | the more your node is online, the more consistently it validates and earns |\n\nThe RPC surface is bound to loopback on purpose. It is a local interface for the app, not something exposed\nto the internet. If you ever need to reach it from another machine, put it behind your own reverse proxy\ndeliberately; do not open the port directly.\n\n## How it stays correct\n\nCitrate Core injects the network's consensus settings when it starts the node, and it ships a node\nconfiguration whose peer list includes the sequencer, so a fresh install finds the network on the first\nrun. Two things are true by design and worth knowing:\n\n- Block production is a network role tied to your stake, not a local switch. The shipped configuration\n keeps the local `[mining]` flag off; your node validates through its registered stake, not by racing to\n produce blocks on its own.\n- The node binary is matched to the app. Do not replace it with a binary copied from elsewhere. A mismatched\n node can appear to sync cleanly while computing a different view of the ledger, which quietly forks you off\n the network. Always take the binary that ships with the app, and let the app update it.\n\n## Keeping it healthy\n\n- Leave the app running to keep the node online. The supervisor handles ordinary restarts on its own.\n- Watch the `Node` surface after an update or a restart: the log tail should advance and the peer count\n should be non-zero within a minute or two.\n- Back up your recovery phrase before you rely on the node for a stake. See [keys and safety](/core/safety).\n- The earning side, how a node is paid in SALT for the work it does, is covered under\n [rewards](/operators/rewards).\n\n## Running it headless\n\nAgents and headless operators can drive the same node lifecycle. The signing model stays the same: an agent\ncan start, stop, and monitor the node, but any on-chain action it wants to take is routed to a human\napproval. See [for agents](/core/for-agents).\n"},"/core/safety":{"slug":"/core/safety","title":"Keys, safety, and safe operation","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-core/src-tauri/src/ceremony.rs, src-tauri/src/wallet.rs, CLAUDE.md, src-tauri/src/node.rs","syncedSha":"2d88191","toc":[{"depth":2,"text":"Where your keys live","anchor":"where-your-keys-live"},{"depth":2,"text":"How signing works","anchor":"how-signing-works"},{"depth":2,"text":"Back up your recovery phrase","anchor":"back-up-your-recovery-phrase"},{"depth":2,"text":"Updates","anchor":"updates"},{"depth":2,"text":"What to expose","anchor":"what-to-expose"},{"depth":2,"text":"A safe-operation checklist","anchor":"a-safe-operation-checklist"}],"body":"Citrate Core is built so that the sensitive parts, your keys and your signatures, have exactly one path\nthrough the app and never leave it. This page explains where your keys live, how signing works, and how to\nkeep an install safe at home or in a business.\n\n## Where your keys live\n\nYour account keys are sealed in the operating system keyring and the node's on-disk state is encrypted at\nrest with a key held there too. The `Node` surface shows this as `encryption at rest: ON, keyring`. Keys\nare never written to the interface, never handed to a background service, and never sent over the network.\nNo part of the app can read a key or a recovery phrase across the boundary between the Rust backend and the\ninterface; the code is structured so that is not possible.\n\n## How signing works\n\nEvery signature in Citrate Core goes through one approval path, the SignatureCeremony. This is true whether\nthe request comes from you, from the on-device agent, or from a background service. The rules are strict on\npurpose:\n\n- **One human approval per signature.** There is no auto-approve and no approve-the-last-one. Each approval\n is bound to a specific request and yields exactly one signature.\n- **You see what you are signing.** The approval shows the decoded intent of the action. If the app cannot\n decode the request, it blocks it until you explicitly acknowledge that you are approving raw data.\n- **A locked account fails closed.** If the account is locked, a signing request fails rather than falling\n back to anything less safe.\n- **No sidecar ever signs.** No background service, daemon, or remote holds a key or signs on its own.\n\nThis is why the on-device agent is described as keyless: it can prepare an action, but it cannot complete\none without your approval.\n\n## Back up your recovery phrase\n\nDuring setup the app provisions your account and you take responsibility for its recovery phrase. Back it up\nbefore you rely on the account for anything that matters, especially before you place a stake. The phrase is\nthe only way to restore the account on another machine; Citrate cannot recover it for you. Keep it offline.\n\n## Updates\n\nCitrate Core updates itself through a signed updater. Take updates when the app offers them. The node binary\nis delivered with the app and matched to it: do not replace the node binary by hand, because a mismatched\none can sync cleanly while computing a different view of the ledger and quietly fork you off the network.\nLetting the app manage the binary is what keeps your node on the canonical chain.\n\n## What to expose\n\nThe node's read and write interface is bound to loopback (`127.0.0.1:8545` and `:8546`) so it is reachable\nonly from your own machine. Treat that as the default and keep it. If you have a genuine reason to reach the\nnode from another host, put it behind a reverse proxy you control, with its own authentication; do not open\nthe port to the network directly.\n\n## A safe-operation checklist\n\n- Membership and identity are set up, and your recovery phrase is backed up offline.\n- The app is the only thing that manages the node binary and the node configuration.\n- The node's ports stay on loopback unless you have deliberately fronted them.\n- You approve each signing request after reading its decoded intent, and you never blind-approve raw data\n you do not understand.\n- You take app updates promptly.\n\nFor running the node itself, see [run a node](/core/run-a-node). For driving the app from an agent under the\nsame signing rules, see [for agents](/core/for-agents).\n"},"/core/skills":{"slug":"/core/skills","title":"Hermes skills, and how Hermes learns","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-core/skills.lock, src-tauri/skills, docs/HERMES_LEARNING.md; citrate-agent-runtime/agent-loop/src/skills.rs, agent-learn","syncedSha":"81ef7a0","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"A skill is a short written method that Hermes can load when a task calls for it, such as how to\nreview a contract or how to write a status note. This page is for members who want to know where\nHermes's skills come from, how to see them, and how Hermes may add one of its own.\n\n## What it is\n\nEach skill is a folder with a `SKILL.md` file: a name, a one-line description, and the method in\nplain text. Hermes keeps a short index of the skills on offer and loads the full text of one only\nwhen it needs it, which keeps each request small enough for a local model.\n\nSkills come from three places:\n\n- **The reviewed set.** Third-party skills from Trail of Bits, frontend-skills, agentile-skills and\n the open-source hermes-agent project, each reviewed one by one before it ships. The review records\n the source commit and a hash of every file in `skills.lock`. On 2026-10-01 the lock held 296\n reviewed skills, of which 240 ship. Scripts and other executables are stripped from every skill\n that ships.\n- **Citrate skills.** Four skills written for Citrate itself: paraconsensus, the precompiles, Belnap\n aggregation and sidecar consensus.\n- **Skills Hermes learned.** Skills that you accepted from Hermes's own verified work (below).\n\n## How to use it\n\n1. In the **Agent** surface, ask Hermes which skills it has. A [persona](/core/personas) narrows\n the list to the skills that fit its role.\n2. Ask for the work, not the skill. Hermes loads a skill when the task matches its description.\n3. To teach Hermes, open **What Hermes learned**, then **Teach Hermes**. Write a task and the phrases\n a correct answer must contain. Hermes runs it on your local model.\n4. If every check passed, Hermes may propose keeping a skill or a memory. Review the card, which shows\n the content and every check result, then choose **Accept** or **Reject**.\n\n## Reference\n\n| Rule | What it means for you |\n|---|---|\n| Only verified work | A proposal must come from a run whose checks all passed. Hermes saying it succeeded never counts. |\n| Nothing is kept without you | Every proposal waits for Accept or Reject, and the decision is logged first. |\n| Untrusted input blocks learning | A session that read a web page or MCP output cannot propose anything. |\n| Contradictions are shown, not merged | A memory that disagrees with one you have is kept alongside it as unresolved until you choose **Keep this one**. |\n| Publishing is a signature | Sending a skill to the on-chain SkillRegistry needs one approval per publish through the signing ceremony. It is off in this release. |\n\nThe technical flow is in\n[HERMES_LEARNING.md](https://github.com/CitrateNetwork/citrate-core/blob/main/docs/HERMES_LEARNING.md),\nand the review of every third-party skill is in the core repo under\n[.agentile/skill-intake](https://github.com/CitrateNetwork/citrate-core/tree/main/.agentile/skill-intake).\n\n## Design rationale\n\nOpen agents tend to grade their own runs as a success and then save the mistake as a skill. Hermes\nseparates the two: a check outside the model decides whether a run passed, and you decide whether\nanything is kept. Third-party skills are pinned by hash so that a later change upstream cannot\nchange what Hermes reads without a new review.\n\n## Failure modes\n\n- **A skill does not load.** The loader is strict and refuses a malformed skill instead of guessing.\n The skill stays out of the index.\n- **A skill file changed on disk.** It no longer matches its recorded hash and is refused.\n- **A proposal from an unverified run.** It is refused before it reaches you.\n\n## Access and canon\n\nSkills and learned memories live in your app data folder on your machine. Nothing leaves it unless\nyou publish, and publishing is off in this release.\n\n## Source and verification\n\nSource: `citrate-core/skills.lock`, `src-tauri/skills/`, `docs/HERMES_LEARNING.md`, and in\n`citrate-agent-runtime` the skill loader (`agent-loop/src/skills.rs`) and `agent-learn`. Audited\nagainst core `81ef7a0`. Status: **Implemented**, pre-audit. Skill persistence is **Verified** as a\nTLA+ model (`SkillPersistence.tla`) checked with TLC at small bounds. \"Teach Hermes\" has not yet been\nrun in a packaged build.\n"},"/core/tour":{"slug":"/core/tour","title":"A tour of Citrate Core","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-core/src/surfaces/, src/shell/Sidebar.tsx, docs/CITRATE_CORE_FEATURE_MAP_AND_SITEMAP.md","syncedSha":"2d88191","toc":[{"depth":2,"text":"You","anchor":"you"},{"depth":3,"text":"Dashboard","anchor":"dashboard"},{"depth":3,"text":"Account","anchor":"account"},{"depth":3,"text":"Storage","anchor":"storage"},{"depth":3,"text":"Files","anchor":"files"},{"depth":3,"text":"Models","anchor":"models"},{"depth":3,"text":"Agent","anchor":"agent"},{"depth":3,"text":"Connections","anchor":"connections"},{"depth":3,"text":"Journal","anchor":"journal"},{"depth":2,"text":"Your Groups","anchor":"your-groups"},{"depth":3,"text":"Groups","anchor":"groups"},{"depth":3,"text":"Comms","anchor":"comms"},{"depth":3,"text":"Cluster","anchor":"cluster"},{"depth":3,"text":"Train","anchor":"train"},{"depth":3,"text":"Community","anchor":"community"},{"depth":2,"text":"More","anchor":"more"},{"depth":3,"text":"Commissary","anchor":"commissary"},{"depth":3,"text":"Settings","anchor":"settings"},{"depth":3,"text":"ALF","anchor":"alf"},{"depth":2,"text":"Next","anchor":"next"}],"body":"This is the screen-by-screen tour of Citrate Core. Each surface is a page in the sidebar; the sidebar groups\nthem as **You**, **Your Groups**, **Your Node**, and **More**. The `Node` surface has its own page,\n[run a node](/core/run-a-node).\n\n## You\n\n### Dashboard\n\nThe home surface. A strip of node vitals across the top (height, peers, finality, node state, staked\namount, and today's validating status), the on-device agent in the center, recent account activity on the\nright, and a set of short tutorials.\n\n![The dashboard: node vitals, the on-device agent, recent activity, and tutorials.](/core/app-dashboard.png)\n\n### Account\n\nYour Citrate Keyring account, under the `Wallet` label. Four tabs: **Overview**, **Staking**, **Activity**,\nand **Identity**. Overview shows liquid SALT, staked SALT, and wrapped SALT, a paymaster meter for\nsponsored actions, and the send and receive panels. The receive panel shows your smart account address,\nwhich deploys lazily on its first outgoing action, so deposits are safe before the account has ever sent\nanything.\n\n![The account surface, Overview tab: balances, the paymaster meter, and send and receive.](/core/app-wallet.png)\n\n### Storage\n\nYour memory graph: a local, searchable knowledge store that agents and the app read from and write to.\nEntries are yours, held on device, and can be anchored to the network when you choose.\n\n![Storage: the local memory graph you and your agents read and write.](/core/app-storage.png)\n\n### Files\n\nYour files on the network storage layer. Pin, browse, and manage content addressed by hash.\n\n![Files: content-addressed storage on the network.](/core/app-files.png)\n\n### Models\n\nBrowse and manage AI models available to the app, including the local inference model the node can serve.\n\n![Models: browse and manage the models available on device and on the network.](/core/app-models.png)\n\n### Agent\n\nA keyless agent workbench. The agent can act on your behalf, and every action it wants to sign is routed to\nyou for approval through the same ceremony the rest of the app uses. It never holds a key.\n\n![Agent: a keyless workbench whose signatures route to you for approval.](/core/app-agent.png)\n\n### Connections\n\nThe people and services your account is connected to across the federation.\n\n![Connections: your links to people and services across the federation.](/core/app-connections.png)\n\n### Journal\n\nA running record of what your node and account have done, in plain language, useful for keeping a personal\nlog or handing context to an agent.\n\n![Journal: a plain-language record of what your node and account have done.](/core/app-journal.png)\n\n## Your Groups\n\n### Groups\n\nCreate and join groups, assign roles, manage a roster, and send messages. Groups are the unit of shared\nmembership and access.\n\n![Groups: create and join groups, assign roles, and manage a roster.](/core/app-groups.png)\n\n### Comms\n\nMessaging over the server-blind Citrate relay. The relay carries sealed messages without being able to read\nthem; see [Citrate Comms](/apps/comms) for the protocol.\n\n![Comms: sealed messaging over the server-blind relay.](/core/app-comms.png)\n\n### Cluster\n\nJoin a compute cluster and share files and capacity with its members.\n\n![Cluster: join a compute cluster and share capacity with its members.](/core/app-cluster.png)\n\n### Train\n\nTake part in federated model training rounds from your own machine. Your data stays local; only the agreed\nupdates leave the device.\n\n![Train: take part in federated training rounds with your data staying local.](/core/app-train.png)\n\n### Community\n\nThe wider community surface: shared spaces and activity across the network.\n\n![Community: shared spaces and activity across the network.](/core/app-community.png)\n\n## More\n\n### Commissary\n\nWhere memberships and entitlements are taken and managed. Checkout opens in your browser; the outcome\nreturns to the app.\n\n![Commissary: take and manage memberships and entitlements.](/core/app-commissary.png)\n\n### Settings\n\nApplication settings, grouped into sections for the account, the node, identity, models, storage, and the\napp itself. This is where you manage the local configuration described in [keys and safety](/core/safety).\n\n![Settings: application, account, node, and identity configuration.](/core/app-settings.png)\n\n### ALF\n\nThe ALF learning surface: the education and mentorship programs on the network, reachable from inside the\napp.\n\n![ALF: the learning and mentorship surface inside the app.](/core/app-alf.png)\n\n## Next\n\nThe one surface not shown above is `Node`, the operator console for the node this app runs. It has its own\npage: [run a node](/core/run-a-node).\n"},"/core/troubleshooting":{"slug":"/core/troubleshooting","title":"Troubleshooting Citrate Core","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-core/src-tauri/src/node.rs, README.md, docs/RELEASE_LINUX.md","syncedSha":"2d88191","toc":[{"depth":2,"text":"The node will not come online, or shows zero peers","anchor":"the-node-will-not-come-online-or-shows-zero-peers"},{"depth":2,"text":"The node syncs but the height looks wrong, or you suspect a fork","anchor":"the-node-syncs-but-the-height-looks-wrong-or-you-suspect-a-fork"},{"depth":2,"text":"The node uses a lot of memory while catching up","anchor":"the-node-uses-a-lot-of-memory-while-catching-up"},{"depth":2,"text":"The window opens blank on Linux","anchor":"the-window-opens-blank-on-linux"},{"depth":2,"text":"An update prompt fails or an update banner appears repeatedly","anchor":"an-update-prompt-fails-or-an-update-banner-appears-repeatedly"},{"depth":2,"text":"A signing prompt will not let me approve","anchor":"a-signing-prompt-will-not-let-me-approve"},{"depth":2,"text":"On-device AI does not respond","anchor":"on-device-ai-does-not-respond"},{"depth":2,"text":"Where to get more detail","anchor":"where-to-get-more-detail"}],"body":"Common issues and what to do about them. If something here does not cover your case, the `Node` surface log\ntail and the app's settings are the first places to look.\n\n## The node will not come online, or shows zero peers\n\nGive it a minute or two after a start or an update: the log tail should begin to advance and the peer count\nshould rise above zero. If it stays at zero peers, the node is not reaching the network. Check that your\nmachine has outbound network access on port `30303` and that any firewall is not blocking it. Citrate Core\nships a node configuration whose peer list already includes the sequencer, so a fresh install should find\nthe network on its own; if you have edited the node configuration by hand, restore the shipped one.\n\n## The node syncs but the height looks wrong, or you suspect a fork\n\nDo not run a node binary you copied from somewhere else. A mismatched binary can sync cleanly while\ncomputing a different view of the ledger, which forks you off the network without any obvious error. Take\nthe binary that ships with the app and let the app update it. Reinstalling the current release restores the\nmatched binary.\n\n## The node uses a lot of memory while catching up\n\nSyncing is memory-heavy while the node is catching up to the current height. Budget several gigabytes of\nheadroom during the initial sync; usage settles once the node is caught up. If your machine is tight on\nmemory, keep other heavy applications closed during the first sync.\n\n## The window opens blank on Linux\n\nOn some Linux graphics stacks the window renders blank because the interface cannot get a GPU surface. Start\nthe app with `WEBKIT_DISABLE_DMABUF_RENDERER=1` set in the environment. This is a known interaction with\ncertain drivers and does not indicate a problem with your account or the node.\n\n## An update prompt fails or an update banner appears repeatedly\n\nIf an update check cannot reach a published release it may surface a one-off message. It is harmless: your\naccount and node are unaffected. Take the update when the app offers a working one; if a prompt is stuck,\nrestart the app.\n\n## A signing prompt will not let me approve\n\nIf the account is locked, signing requests fail closed by design. Unlock the account and try the action\nagain. If the prompt shows raw, undecoded data, the app is telling you it could not decode the action; only\nacknowledge raw mode if you are certain of what you are approving. See [keys and safety](/core/safety).\n\n## On-device AI does not respond\n\nThe local inference model is offered as a download during setup. If you skipped it, the on-device agent and\nsome model features will not have a model to run. Download the model from the `Models` surface, then retry.\n\n## Where to get more detail\n\n- The `Node` surface log tail and crash records for anything node-related.\n- [Run a node](/core/run-a-node) for what a healthy node looks like.\n- [Keys and safety](/core/safety) for anything about signing, the account, or updates.\n"},"/enterprise/compliance-full":{"slug":"/enterprise/compliance-full","title":"Compliance Posture, Full Package (Gated)","tier":"public","orgId":null,"sourceKind":"gated","source":"citrate-compliance/","syncedSha":"8757357","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to request access","anchor":"how-to-request-access"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This page confirms that a full compliance package exists and explains how to request it. It is a pointer, not the package. The audit-grade material itself is confidential and is never built into these public docs.\n\n## What it is\n\nBehind the public summary at [Compliance posture](/enterprise/compliance) sits a complete compliance package: System Security Plans, control crosswalks against the relevant NIST families, control narratives, plans of action with milestones, and the evidence that backs each one. We keep that package in a private home and treat it as the source of record for assessors and auditors.\n\nThe public summary tells you where we stand, in plain language. This package is what an assessor reads when they need the detail behind that summary. We do not reproduce any of that detail here: no control scores, no plan-of-action items, no evidence, and no named third parties appear on this page or anywhere in the public docs.\n\n## How to request access\n\nAccess is for people with a contractual reason to read the package: contracted assessors, issued auditors, and named principals running due diligence, each under a non-disclosure agreement.\n\n1. Ask through your commercial or compliance contact at Citrate, or through your account channel.\n2. We confirm your role and put the non-disclosure agreement in place.\n3. We grant time-bound access to the package in its private home.\n\n## Access and canon\n\nThe full package is confidential. It is served at request time from its private home, under a non-disclosure agreement, to named recipients only. It is never copied into this documentation tree, and the public build never includes it. Every access is logged. The sanitized public summary, which anyone may read, is at [Compliance posture](/enterprise/compliance).\n\nSome of the frameworks the package covers are certified and some are in progress. We label each one honestly in the package and in the public summary, and we show no scores on this page.\n\n## Source and verification\n\nPrivate source: the `citrate-compliance` corpus and its audit archive. Audited against `citrate-compliance` SHA `8757357`. Status: Implemented (the package exists and is maintained); certifications in progress are labeled as in progress, with no scores shown here.\n"},"/enterprise/compliance":{"slug":"/enterprise/compliance","title":"Compliance posture, public and sanitized","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-compliance/frameworks/README.md","syncedSha":"8757357","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes and honest gaps","anchor":"failure-modes-and-honest-gaps"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Where Citrate stands on the major compliance frameworks, stated honestly and by deployment context. This is\nthe sanitized public summary. It is a point-in-time status, not a claim of certification or attestation, and\nnot legal advice. Where a framework is in progress, it is in progress, and we say so plainly.\n\n## What it is\n\nTwo facts shape the whole posture, and the rest follows from them.\n\nFirst, Citrate ships on-premise. The customer operates the software on hardware it controls; there is no\nvendor-operated hosted environment. Citrate Ground is the private, on-premise half of the network, and it is\nthe default. The public Citrate Network only ever sees what an operator chooses to publish.\n\nSecond, the operator of the software performs no data services. It does not host, store, transmit, process,\nor access customer data, and it is not a data processor, controller, business associate, or sub-processor\nunder any regime. That keeps the software operator outside the customer's authorization boundary: the\ncustomer's own controls govern the regulated workload.\n\nBecause of those two facts, most frameworks apply to the customer's deployment rather than to a hosted\nservice. The compliance floor therefore depends on where Citrate runs:\n\n- Public Citrate Network. Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity. SOC 2 is the general floor\n for the surrounding services.\n- Citrate Ground, on-premise. The customer's deployment carries the floor for its own context: HIPAA for\n health, NIST 800-171 and 800-53 with ITAR considerations for federal and regulated work, SOC 2 generally.\n- Citrate Schools. FERPA, COPPA, and CIPA, covered separately under [K-12 and education](/enterprise/k12).\n\nThe table below describes Citrate's own readiness work to support customers who must meet these frameworks.\nIt does not assert that any certification has been earned where it has not.\n\n## How to use it\n\n1. Find your deployment context above, then read the matching rows in the table.\n2. Read the honest caveat in each row before you plan around the status. A framework \"in progress\" is not a\n framework held.\n3. For an authoritative, current, per-framework status with evidence, request the gated package. It is\n available to contracted and identity-verified principals and assessors under agreement. See\n [compliance posture, full](/enterprise/compliance-full).\n4. For the K-12 floor specifically, read [K-12 and education](/enterprise/k12). For the federal context,\n read [federal](/enterprise/federal).\n\n## Reference\n\nFramework status, sanitized and point-in-time. Source: `citrate-compliance/frameworks/README.md` and the\nper-framework digests in `citrate-compliance/frameworks/`.\n\n| Framework | Status | Honest caveat |\n|---|---|---|\n| SOC 2 (Type I and II) | In progress. Control narratives drafted across the Trust Service Criteria; a CPA engagement is underway. | Not yet attested. No SOC 2 report has been issued. Type II additionally requires an operating-effectiveness observation window. |\n| CMMC 2.0 Level 1 | Self-attestation model; the Level 1 practices are filled. | Self-attested, not third-party assessed. |\n| CMMC 2.0 Level 2 (NIST 800-171 r2) | In progress. A self-assessment draft is authored; remediation of submission blockers is underway. | Not yet submitted and not certified. A third-party assessment would be a later, separate step for contracts that require it. |\n| FedRAMP (Low and Moderate) | Not started as an authorization; sponsor-gated. Outline material is authored. | FedRAMP is the cloud pathway. Because the first deployments are on-premise, it is not required for them. An authorization requires an agency sponsor, a cloud provider, and a third-party assessor. |\n| FIPS 140-3 | In progress, and out of our hands. The underlying cryptographic module is in the validation queue. | Validation timing is controlled by the validation program, not by Citrate. Tracked as a known residual. |\n| ITAR and EAR | An export-control overlay; public-surface leakage scanning and a disclaimer-check gate are in place. | Responsibility for any controlled technical data deployed in the software remains with the customer. An outside-counsel opinion is still pending. |\n| FERPA, COPPA, CIPA | The education-privacy floor for the school product track. | See [K-12 and education](/enterprise/k12). |\n\n## Design rationale\n\nWe deliberately avoid certification language we have not earned. We describe the posture as high-assurance,\non-prem capable, air-gap friendly, role-gated, auditable, and private-network deployable, with encryption in\ntransit and at rest. We do not write \"military grade,\" \"fully compliant everywhere,\" \"impossible to hack,\"\nor \"zero risk.\" The reason is practical as much as honest: procurement teams want evidence, baselines, and\ncontract language, and a slogan fails every one of those tests.\n\nThe on-premise default is what makes the rest coherent. If the software operator never holds customer data,\nthen the customer's own assessment governs the regulated workload, and the questions a contracting officer\nasks have clean answers rather than negotiated ones.\n\n## Failure modes and honest gaps\n\n- SOC 2 is in progress, not attested. Treat any reliance on a SOC 2 report as premature until one is issued.\n- CMMC Level 2 is drafted, not submitted or certified. A third-party assessment is a separate future step.\n- FedRAMP is not pursued as an authorization for on-premise deployments and requires a sponsor if it ever is.\n- FIPS 140-3 validation timing depends on the external validation program, not on us.\n- The ITAR and EAR outside-counsel opinion is pending; export responsibility for controlled data stays with\n the customer.\n\n## Access and canon\n\nThis sanitized summary is intentionally public so a prospective customer or partner can understand where we\nstand without an agreement in place. What is gated is the full framework packages: the system security\nplans, control crosswalks, plan-of-action items, self-assessment detail, and audit evidence. Those live in\nthe private `citrate-compliance` corpus and the audit archive, behind contract and identity verification.\nSee [compliance posture, full](/enterprise/compliance-full) and [security questionnaires](/enterprise/questionnaires).\n\nNo control scores, plan-of-action item detail, operator personal data, named CPA, sponsor, or counsel, or\nremediation timelines appear on this page. Every node operator on the public network is identity-checked\nthrough VERI; Citrate keeps the verification result, not the personal data behind it.\n\n## Source and verification\n\nSource: `citrate-compliance/frameworks/README.md` and the per-framework digests under\n`citrate-compliance/frameworks/` (private repo), which point in turn to the executive posture briefing and\nthe audit archive. Audited against `citrate-compliance` SHA `8757357`. Status: Specified. The posture and the\nframework mappings are written down and current as of that SHA; the certifications described as \"in progress\"\nare not yet held, and none of the underlying scores or evidence are reproduced here.\n"},"/enterprise/dpa":{"slug":"/enterprise/dpa","title":"Data Processing Agreement (Gated)","tier":"public","orgId":null,"sourceKind":"gated","source":"citrate-compliance/","syncedSha":"8757357","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to request access","anchor":"how-to-request-access"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This page confirms that a Data Processing Agreement exists and explains how to request it. The agreement itself is contractual and gated; it is not authored in these public docs.\n\n## What it is\n\nCitrate maintains a Data Processing Agreement, with the service-level and subprocessor terms that go alongside it, as part of its contracting package. Because Citrate runs on-premise on Citrate Ground, a customer's data and models stay on the customer's own hardware, and the data-handling boundary sits with the customer. The agreement is what states that boundary in writing, so each side knows what it is responsible for.\n\nWe do not reproduce any of the terms here. No service levels, no subprocessor identities, and no customer specifics appear on this page or anywhere in the public docs.\n\n## How to request access\n\nAccess is for named principals with a contractual reason to read the agreement, each under a non-disclosure agreement.\n\n1. Ask through your account channel or your commercial contact at Citrate.\n2. We confirm your role and put the non-disclosure agreement in place.\n3. We share the current document of record from its private home.\n\n## Access and canon\n\nThe agreement is confidential. It is served at request time from its private home, under a non-disclosure agreement, to named recipients only. It is never copied into this documentation tree, and the public build never includes it. Every access is logged. The sanitized public summary of our compliance posture, which anyone may read, is at [Compliance posture](/enterprise/compliance).\n\n## Source and verification\n\nPrivate source: the `citrate-compliance` corpus. Audited against `citrate-compliance` SHA `8757357`. Status: Implemented (the agreement exists and is maintained as the document of record); no terms are shown here.\n"},"/enterprise/federal":{"slug":"/enterprise/federal","title":"Federal and On-Prem Isolation (Gated)","tier":"public","orgId":null,"sourceKind":"gated","source":"citrate-compliance + nist-agent","syncedSha":"8757357","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to request access","anchor":"how-to-request-access"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This page confirms that a federal and on-prem isolation package exists and explains how to request it. The package is confidential; it is never built into these public docs, and nothing here is a claim of certification or authorization.\n\n## What it is\n\nFor federal and defense engagements, Citrate maintains a confidential package covering the federal control families, the authorization pathway, and the export-control overlay that those engagements require. Citrate runs on-premise on Citrate Ground and can run air-gapped, so a customer deploys inside their own authorization boundary, on their own hardware. The vendor performs no data services and is not itself inside that boundary.\n\nThe package ties to the air-gapped deployment described for [Citrate NIST Agent](/apps/nist-agent), which is the surface that runs against the federal control corpus on Citrate Ground. The detail behind all of this, the control implementations, the system security plan bodies, and any sponsor or assessor particulars, stays in the private home. No scores, control detail, sponsor names, or customer specifics appear on this page or anywhere in the public docs.\n\nThese frameworks are in progress, not certified. Nothing on this page is a claim of authorization to operate, of certification, or of attestation. The honest public summary is at [Compliance posture](/enterprise/compliance).\n\n## How to request access\n\nAccess is for people with a contractual reason to read the package: federal contracting officers, sponsors, contracted assessors, and issued auditors, each under a non-disclosure agreement.\n\n1. Ask through your commercial or compliance contact at Citrate.\n2. We confirm your role and put the non-disclosure agreement in place.\n3. We grant time-bound access to the package in its private home.\n\n## Access and canon\n\nThe package is confidential. It is served at request time from its private home, under a non-disclosure agreement, to named recipients only. It is never copied into this documentation tree, and the public build never includes it. Every access is logged. The sanitized public summary, which anyone may read, is at [Compliance posture](/enterprise/compliance); for the schools deployment context see [Citrate Schools](/enterprise/k12).\n\n## Source and verification\n\nPrivate source: the `citrate-compliance` corpus and the `nist-agent` control corpus. Audited against `citrate-compliance` SHA `8757357`. Status: Specified (the package and the air-gapped deployment are designed and documented; the frameworks it targets are in progress, not certified, with no scores shown here).\n"},"/enterprise/procurement":{"slug":"/enterprise/procurement","title":"Procurement, how to buy Citrate","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-commercial/CITRATE_PROCUREMENT_ORDER_FORM.md","syncedSha":"fea06db","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes and honest gaps","anchor":"failure-modes-and-honest-gaps"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The non-sensitive shape of a Citrate procurement: a master agreement, order forms, and statements of work.\nThis page is for contracted and identity-verified principals planning a purchase. Deal-specific economics,\nnamed parties, and rate cards are negotiated privately and are not published here. Nothing on this page is\na binding offer.\n\n## What it is\n\nCitrate is procured as software you run, not a hosted service. The software is delivered to infrastructure\nyou control, and you operate it; there is no vendor-operated hosting environment. The standard commercial\nstructure has three layers:\n\n1. A master license and implementation services agreement, the umbrella contract that governs the\n relationship: definitions, IP ownership, confidentiality, warranties, indemnification, limitation of\n liability, term and termination, and dispute resolution. Signed once.\n2. Order forms. Each transaction, a license procurement, an implementation engagement, or ongoing support,\n is issued as an order form under the master agreement. An order form carries the commercial specifics for\n that transaction and prevails over the master agreement for the deal it governs.\n3. Statements of work. Implementation is scoped phase by phase as statements of work, signed by a joint\n steering committee, and billed on a time-and-materials basis against a rate card.\n\nThree commercial properties are fixed, not negotiable per deal:\n\n- On-premise, customer-controlled delivery. The software is delivered; you operate it. There is no\n vendor-operated hosting environment.\n- No data services. The software operator does not host, store, transmit, process, or access customer data,\n and is not a data processor, controller, business associate, or sub-processor under any regime. This keeps\n the operator outside your authorization boundary. See [compliance posture](/enterprise/compliance).\n- A one-time perpetual license for the software, plus separately billed time-and-materials implementation.\n There is no recurring subscription or usage fee for the license itself.\n\n## How to use it\n\nA typical deal proceeds in this order.\n\n1. Introduction and mutual non-disclosure, before any deal-specific material is shared.\n2. Discovery, the first implementation phase: an assessment of your environment, identity provider,\n monitoring stack, and compliance posture.\n3. The master agreement and the first order form for the license, executed with the deal-specific economics\n filled in by the commercial team and outside counsel.\n4. Statements of work per phase: discovery, then installation and configuration, then integration and\n validation, then knowledge transfer and operator training, then acceptance testing.\n5. Acceptance and escrow. The acceptance certificate triggers the final license milestone, and the source\n escrow is deposited.\n6. Optional ongoing support or a resale track, each via a separate order form.\n\nTo receive the procurement template, the master agreement, and the rate card under agreement, contact the\ncommercial team through your account channel. Per-deal materials are released after non-disclosure and\nidentity verification. See [district registration](/apps/district-registration) for the K-12 onboarding\npath specifically.\n\n## Reference\n\nThe procurement template, `citrate-commercial/CITRATE_PROCUREMENT_ORDER_FORM.md`, defines the following\ncomponents at a structural level.\n\n| Component | What it covers, non-sensitive |\n|---|---|\n| Order form, license | Effective date, parties, a fee schedule keyed to milestones (execution, delivery, acceptance), location of use, and hosting marked not applicable because delivery is on-premise. |\n| Order form, implementation | A phased engagement scoped against a rate card, invoiced monthly in arrears, governed by a steering committee. |\n| License grant | Perpetual, non-exclusive, worldwide; rights to use, copy, modify, deploy, and white-label on customer-controlled infrastructure; restrictions on standalone resale and on open-sourcing. |\n| Deliverables at delivery | Source, reproducible-build binaries, smart-contract source and tests, a documentation set, TLA+ specifications, build and test scripts, and detached cryptographic signatures over every artifact. |\n| Acceptance testing | A defined window, 90 days by default, with an acceptance-certificate or rejection-notice path, and deemed acceptance if neither issues. |\n| No-data-services carve-out | The explicit, absolute statement that the operator never holds customer data, central to your compliance boundary. |\n| Master agreement terms | Confidentiality, IP ownership, fees and payment, warranties, indemnification, limitation of liability, term and termination, governing law, and arbitration. |\n| Source-code escrow | A deposit with a mutually agreed escrow agent, with defined release conditions: insolvency, cessation of operations, or an uncured warranty breach. |\n| Resale addendum | An optional, separately executed track if you later wish to resell the software. |\n\nThe supporting templates in `citrate-commercial/commercial/` cover the federal procurement path: a\ncommercial-item determination, representations and certifications, a statement-of-work template, a mutual\nnon-disclosure template, a source-code escrow agreement, and an acceptance test plan, among others. They are\nreleased to contracted principals, not authored in this docs tree.\n\n## Design rationale\n\nThe structure exists to remove negotiation friction rather than create it. A perpetual license plus\ntime-and-materials implementation separates what you own from what you pay people to do, so the two are\npriced and accepted independently. The no-data-services carve-out is the load-bearing term: because the\noperator never holds your data, your own assessment governs the regulated workload, and a contracting\nofficer's questions have clean answers. The mandatory knowledge-transfer phase exists so your own operations\nteam can run the deployment without us, which is the point of buying software you run.\n\n## Failure modes and honest gaps\n\n- The procurement template is a draft for legal review. Outside counsel reviews every clause against your\n deal context, jurisdiction, and regulatory profile before execution.\n- The structure is final; the economics are not. Dollar values, dates, the customer entity, hosting\n locations, and warranty scope are negotiated per deal and are not published here.\n- Implementation estimates are good-faith estimates only. Actual hours are invoiced against the rate card.\n- Acceptance has a deadline. If you issue neither an acceptance certificate nor a rejection notice within the\n acceptance window, acceptance is deemed automatic.\n\n## Access and canon\n\nThis page is the commercial-tier structural summary, shared with contracted and identity-verified principals\nso a buyer's procurement team can plan. The terms and economics are gated to the private\n`citrate-commercial` repo. No dollar values, named customer or vendor entities, rate-card figures, patent\nschedule, signatory personal data, or hosting addresses are reproduced here; those are deal-specific and\nconfidential. Per-company spaces are provisioned at runtime per contract and are not authored in this docs\ntree. For where the operator sits in your authorization boundary, see [compliance posture](/enterprise/compliance).\n\n## Source and verification\n\nSource: `citrate-commercial/CITRATE_PROCUREMENT_ORDER_FORM.md` and the templates under\n`citrate-commercial/commercial/` (private repo). Audited against `citrate-commercial` SHA `fea06db`. Status:\nSpecified. The procurement structure is written down and current as of that SHA; it is a template for legal\nreview, not an executed agreement, and the deal-specific terms it brackets are negotiated privately.\n"},"/enterprise/questionnaires":{"slug":"/enterprise/questionnaires","title":"Security Questionnaires, SIG and CAIQ (Gated)","tier":"public","orgId":null,"sourceKind":"gated","source":"citrate-compliance/","syncedSha":"8757357","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to request access","anchor":"how-to-request-access"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This page confirms that prepared security-questionnaire responses exist and explains how to request them. The completed responses are gated; they are not authored in these public docs.\n\n## What it is\n\nCitrate keeps prepared responses to the standard third-party security questionnaires, the Shared Assessments SIG and the Cloud Security Alliance CAIQ, along with customer-specific variants. Each response is backed by the evidence in the private compliance corpus, so a reviewer reads answers that match the package, not answers written for the occasion.\n\nWe treat completed responses as confidential. They carry the same security-sensitive detail as the full compliance package, so we do not reproduce any answers, control mappings, or evidence on this page or anywhere in the public docs.\n\n## How to request access\n\nAccess is for procurement and security-review teams running vendor due diligence, as named principals under a non-disclosure agreement.\n\n1. Ask through your commercial contact at Citrate, naming the questionnaire you need.\n2. We confirm your role and put the non-disclosure agreement in place.\n3. We share the completed SIG or CAIQ response from its private home.\n\n## Access and canon\n\nThe completed responses are confidential. They draw on the gated [full compliance package](/enterprise/compliance-full) and are served at request time from their private home, under a non-disclosure agreement, to named recipients only. They are never copied into this documentation tree, and the public build never includes them. Every access is logged. The sanitized public summary, which anyone may read, is at [Compliance posture](/enterprise/compliance).\n\n## Source and verification\n\nPrivate source: the `citrate-compliance` corpus. Audited against `citrate-compliance` SHA `8757357`. Status: Implemented (prepared responses exist and are maintained); no answers or mappings are shown here.\n"},"/methodology/rules":{"slug":"/methodology/rules","title":"The 13 Agentile rules","tier":"public","orgId":null,"sourceKind":"linked","source":"docs/AGENTILE_RULES.md","syncedSha":"cd729ed","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The federation-wide rules that govern every repository under CitrateNetwork. People and agents follow them\nequally. This page is a one-line-each index; the full statement with rationale lives in\n`docs/AGENTILE_RULES.md`, with the federation-active copy in\n`citrate-federation/agentile/rules/CORE_RULES.md`. Per Rule 9, where this index and those files differ, the\nfiles win.\n\n## What it is\n\nThe rules constrain what can ship; the [workflow](/methodology/workflow) constrains when and how. They\ncarry over from the pre-split monorepo and are enforced by CI and review together. If the methodology is\nnew to you, start with the [Agentile primer](/start/agentile).\n\n## Reference\n\n| # | Rule | In one line |\n|---|---|---|\n| 0 | Read before writing code | Read `AGENT_ENTRY.md` and the repo's `owners.md` before starting. |\n| 1 | No mocks, no stubs, no TODOs | Production paths contain real code; mocks live behind `#[cfg(test)]` or a dev flag. |\n| 2 | Test count only goes up | The `cargo test --workspace` count is monotone within a sprint; removing a test needs an ADR. |\n| 3 | Audits are immutable | Reports in `audits/` are dated and never edited; corrections go in a dated follow-up. |\n| 4 | Sprint file is authoritative | Status lives in `sprints/active/`, not in chat, memory, or a PR description. |\n| 5 | Rule-12 frontmatter | Every document carries `created`, `branch`, `author`, and `status`. |\n| 6 | Daily benchmark on chain crates | Sessions touching core `citrate-chain` crates end with a benchmark run. |\n| 7 | Data-source tracing | Every endpoint declares its data source before it is implemented. |\n| 8 | Zero `.unwrap()` in production | `grep .unwrap() src/` returns zero in production crates; CI enforces it. Use `?` and typed errors. |\n| 9 | One source of truth per topic | Do not duplicate docs. Link, do not copy. |\n| 10 | Authorization before destruction | Force-push, delete, or rotation needs explicit human sign-off, not just green CI. |\n| 11 | Federation manifest is canonical | `citrate-federation/manifest.toml` wins over any per-repo divergence. |\n| 12 | Cross-repo deps follow the drift map | Add a `[[drift]]` manifest entry first, then the `Cargo.toml` or `package.json` dependency. |\n| 13 | Visibility flips need sign-off | Private to public on a Tier-1 repo needs federation-lead sign-off (and customer sign-off where the work is customer-specific). |\n\nA note on numbering: the frontmatter constraint is called Rule 5 in the federation renumbering and Rule 12\nin the archive numbering. It is the same rule.\n\n## Access and canon\n\nPublic. The rules are public-good methodology and contain no secrets. The internal procedures that apply\nthese rules to sensitive operations are gated; see [SOPs](/methodology/sops).\n\n## Source and verification\n\nThe canonical sources are `docs/AGENTILE_RULES.md` (with rationale) and\n`citrate-federation/agentile/rules/CORE_RULES.md` (federation-active), at SHA `cd729ed`. Status:\nImplemented.\n"},"/methodology/sops":{"slug":"/methodology/sops","title":"Standard operating procedures","tier":"public","orgId":null,"sourceKind":"authored","source":"per-surface tutorials + repo READMEs","syncedSha":"cd729ed","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"An index of the standard operating procedures Citrate documents for the people who use the network:\ncustomers, developers, and node operators. Internal procedures (incident response, access review, hardware\ndisposal) are gated and not listed here.\n\n## What it is\n\nA standard operating procedure is a repeatable, audited procedure. Citrate tiers them by audience:\n\n- **Public and developer procedures** that any builder can follow, written as runnable tutorials on the\n surface they belong to.\n- **Commercial and operator procedures** for paid seats and node operators, some identity-gated, written on\n the relevant Citrate Market, Citrate Node, and enterprise pages.\n- **Internal procedures** the team runs on the network itself (incident, access review, key rotation).\n These are confidential: served only through the gated `/internal/sops` route, sourced from a private\n repository, and never built into the public docs.\n\nThis page indexes the first two. Per Rule 9, each entry links to the page where the procedure actually\nlives rather than restating it.\n\n## Reference\n\nPublic and developer procedures:\n\n| Procedure | Audience | Where it lives |\n|---|---|---|\n| Your first 10 minutes on Citrate | developer | [tutorial](/start/tutorials/your-first-10-minutes) |\n| Call the Citrate RPC | developer | [tutorial](/chain/tutorials/call-citrate-rpc) |\n| Read the BlockDAG | developer | [tutorial](/chain/tutorials/read-the-dag) |\n| Deploy a contract with the CLI | developer | [tutorial](/chain/tutorials/deploy-a-contract-with-the-cli) |\n| Sign in with a passkey | developer, customer | [tutorial](/aa/tutorials/sign-in-with-a-passkey) |\n| Explore a transaction in CitrateScan | customer | [tutorial](/apps/tutorials/explore-a-transaction) |\n| Install the Citrate Keyring extension | customer | [tutorial](/apps/tutorials/install-the-wallet-extension) |\n\nCommercial and operator procedures (commercial tier; operator depth is identity-gated where noted):\n\n| Procedure | Audience | Where it lives |\n|---|---|---|\n| Run a node | operator | [Citrate Node](/operators/run-a-node) |\n| Sell compute end to end | operator (verified) | [node agent](/compute/node-agent) |\n| Run a training worker | operator | [compute pool](/compute/pool) |\n| Rewards, reputation, and slashing protection | operator | [Citrate Node](/operators/run-a-node) |\n| Request a verification packet | enterprise customer | enterprise and compliance (commercial) |\n| District onboarding | enterprise customer (verified) | [district registration](/apps/district-registration) |\n\nEntries without a live link are tracked stubs in the registry; this index is the checklist for filling\nthem.\n\nHow procedures are authored, numbered, reviewed, and retired is defined in the federation SOP standard\n(`ops/04_SOP_STANDARD.md`). That standard and its commitment template are internal and confidential: the\nstandard governs how the team writes procedures, and it is not part of the public build. What is public is\nthe outcome, the customer, developer, and operator procedures linked above.\n\n## Access and canon\n\nThis index is public. The individual operator and enterprise procedures are commercial (some identity-\ngated) and gate at the linked page. Internal procedures are confidential and excluded from the build. The\ninternal set, served only through the entitlement-gated `/internal/sops` route to admins and issued\nauditors, covers incident response, access review, key and secret rotation, hardware disposal, the FIPS\nmodule tracker, and auditor onboarding. The split is deliberate: public-good procedures stay public, and\nthe procedures for the network's own trust boundary are gated. Nothing is hidden by obscurity, and no\nprocedure on any tier contains a credential.\n\n## Source and verification\n\nProcedures are sourced from the per-surface tutorials and repository READMEs (linked, not copied, per\nRule 9). The SOP-authoring standard is `ops/04_SOP_STANDARD.md` (confidential, not transcribed here). At\nSHA `cd729ed`. Status: Implemented.\n"},"/methodology/workflow":{"slug":"/methodology/workflow","title":"The Agentile sprint workflow","tier":"public","orgId":null,"sourceKind":"linked","source":"docs/AGENTILE_WORKFLOW.md","syncedSha":"cd729ed","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"How work moves from idea to active to completed across the federation, and why that leaves a clean audit\ntrail. This page summarizes; the full choreography lives in `docs/AGENTILE_WORKFLOW.md`. Per Rule 9, the\ncanonical file governs where the two differ. Its companion is [the 13 rules](/methodology/rules).\n\n## What it is\n\nWhere the [rules](/methodology/rules) constrain what can ship, the workflow constrains when and how. The\nunit of work is a **sprint file**: a dated Markdown document, under Rule-12 frontmatter, that is the single\nsource of truth for a workstream's status (Rule 4). A federation sprint spans repositories and lives in\n`citrate-federation/agentile/sprints/`; a repo sprint touches one repository and lives in\n`citrate-federation/repos//sprints/`. Same format, same lifecycle.\n\n## How to use it\n\nThe lifecycle, step by step:\n\n1. **Kickoff.** Create `sprints/active/.md` with Rule-12 frontmatter and fill in the goal, scope,\n out-of-scope, and the plan table. Update `CURRENT.md` so observers can see it is active.\n2. **Daily updates.** Each session that advances the sprint appends one dated line to the daily-updates\n section. The sprint file is the record, not a chat thread (Rule 4).\n3. **Decisions become ADRs.** Architectural choices and trade-offs get a short ADR at\n `adrs/ADR-YYYY-MM-DD-.md` under Rule-12 frontmatter, linked from the sprint's decisions section.\n4. **Cross-repo and manifest.** If SHA pins shift, bump `citrate-federation/manifest.toml`, run\n `./scripts/pin-bump.sh ` to open the consumer PRs, merge on green, and let the nightly drift check\n verify (Rules 11 and 12).\n5. **Close.** When the exit criteria are met, move (do not copy) the file to\n `completed//.md`, set `status: archived`, write the close note recording any delta from\n the plan, and remove it from `CURRENT.md`.\n6. **Audit hand-off.** Completed sprints are immutable (Rule 3) and form the audit-evidence chain. Auditors\n read the `created:` dates and cross-reference the ADRs, the `audits/` directory, and the manifest.\n\n## Reference\n\nWhere each kind of work lives:\n\n| Work type | Home |\n|---|---|\n| Cross-repo workstream | `citrate-federation/agentile/sprints/active/.md` |\n| Single-repo workstream | `citrate-federation/repos//sprints/active/.md` |\n| Architectural decision | `…/adrs/ADR-YYYY-MM-DD-.md` (federation or repo) |\n| Audit report | `audits/YYYY-MM-DD-.md` in the affected repo |\n\nThe common anti-patterns, each of which breaks a rule: status kept in chat (Rule 4); two sprints for one\nworkstream (Rule 9); editing a closed sprint (Rule 3); a `TODO:` in production (Rule 1); a dependency with\nno manifest entry (Rules 11 and 12); a force-push taken without asking (Rule 10).\n\nConvenience skills (`/sprint kickoff|daily|close|status`, `/journal`, `/case-study`, `/audit-drive`)\nautomate the file-shuffling, but the methodology works with nothing more than git and an editor.\n\n## Access and canon\n\nPublic. This is process documentation and holds no secrets. Sprint contents for confidential work, such as\naudit, operations, and funding, live in private repositories and gate there; the workflow itself is public.\n\n## Source and verification\n\nThe canonical sources are `docs/AGENTILE_WORKFLOW.md` and the federation control plane under\n`citrate-federation/agentile/`, at SHA `cd729ed`. Status: Implemented.\n"},"/operators/rewards":{"slug":"/operators/rewards","title":"Rewards, reputation, and slashing","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/core/economics/, citrate-chain/contracts/src/NematocystSlashing.sol, citrate-node-agent","syncedSha":"9d5959e","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"The block reward and its four pools","anchor":"the-block-reward-and-its-four-pools"},{"depth":3,"text":"Institutional operators","anchor":"institutional-operators"},{"depth":3,"text":"Reputation","anchor":"reputation"},{"depth":3,"text":"Slashing","anchor":"slashing"},{"depth":3,"text":"What the node-agent does to keep you safe","anchor":"what-the-node-agent-does-to-keep-you-safe"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This is how an operator earns on the Citrate Network, how reputation tracks the work performed, and how\nmisbehavior is penalized. It is for operators who need to reason about what a node is paid, what raises or\nlowers its standing, and what the node-agent does on their behalf to keep an honest operator out of a slash.\nSALT settles the work; rewards, reputation, and slashing all point at contribution, not at holding.\n\n## What it is\n\nThe economics live in one crate, `core/economics/`. A node earns SALT for the work it performs: sealing\nblocks, hosting models, and completing compute jobs. The amounts, the reputation score, and any penalties\nare accounted on chain at chain id 40204, and the [node-agent](/compute/node-agent) keeps a local operator\ninside the safe envelope automatically. This page is consistent with [network economics](/chain/economics);\nwhere that page describes the supply and the reward schedule, this one describes what reaches the operator.\n\nThe reward for a sealed block is a base reward plus four bonus pools, each a percentage of that base reward.\nThe pools recognize four kinds of contribution, so a node that does more of the work the network values\nearns a larger share. The base reward halves every 2,100,000 blocks, so early seasons are more generous than\nlate ones. We describe the base reward as a configurable default rather than a fixed number, because\ngovernance can move it; what does not move is the halving cadence and the fixed one trillion supply.\n\n## How to use it\n\nYou read these values to project earnings and to understand why a score moved; you rarely set them.\n\n1. **Read your standing.** Call `citrate_getReputationScore` for an address's reputation and\n `citrate_getStakedBalance` for its stake over JSON-RPC. Both are documented in the\n [chain RPC reference](/chain/rpc).\n2. **Project a block reward.** Expect the base reward plus your earned share of the four pools, adjusted for\n where the chain sits in its halving schedule. See [network economics](/chain/economics) for the supply\n side.\n3. **Let the node-agent protect you.** The agent's bidder only accepts jobs it can finish on time and within\n a commitment cap, so it does not over-commit into a penalty. You read its alerts; you do not have to\n compute the envelope yourself.\n\n## Reference\n\n### The block reward and its four pools\n\nVerified in `core/economics/src/enhanced_rewards.rs` (`EnhancedRewardConfig`). The pool percentages are\ndefaults in source; treat them as defaults, not certified values, since governance can move them.\n\n| Element | Default | Source |\n|---|---|---|\n| Base block reward | configurable | `base_block_reward` |\n| Validator performance pool | 30% of base | `performance_bonus_pool` |\n| AI contribution pool | 25% of base | `ai_contribution_pool` |\n| Network health pool | 20% of base | `network_health_pool` |\n| Long-term staking pool | 25% of base | `staking_bonus_pool` |\n| Halving interval | 2,100,000 blocks | `calculate_total_reward_pool` |\n| Minimum validator stake | 32,000 SALT | `min_validator_stake` |\n\nA validator's share of the performance and staking pools is proportional to a score built from uptime,\nconsensus participation, validation efficiency, and a quality score, less a penalty for any prior slash. A\nnode below the minimum validator stake earns no share of those pools. The AI contribution pool is shared by\nscore across models deployed, inferences served, compute provided, and community standing.\n\n### Institutional operators\n\nSchool and institutional operators run under their own parameters, verified in\n`config/institutional_rewards.toml` and `core/economics/src/institutional.rs`.\n\n| Parameter | Value | Source |\n|---|---|---|\n| Block validation base | 150 SALT per month | `institutional_rewards.toml` |\n| Uptime bonus | 1.2x above the 90% uptime threshold | `institutional_rewards.toml` |\n| Model hosting | 25 SALT per model per 30-day epoch | `institutional_rewards.toml` |\n| Minimum uptime to earn | 0.90 | `institutional_rewards.toml` |\n\nSchools are not penalized for scheduled downtime, since their schedules are irregular by design.\n\n### Reputation\n\nReputation is tracked on chain and read with `citrate_getReputationScore`\n(`core/api/src/economics_rpc.rs`), expressed in basis points from 0 to 10,000. It rises with the work a node\nperforms and falls with missed liveness or a slash. The node-agent observes its own reputation each poll and\nraises a latched alert on a drop of more than five percent or on any decrease in stake, so an operator sees a\nproblem before it compounds (`citrate-node-agent`, supervision state).\n\n### Slashing\n\nSlashing categories live on chain in `NematocystSlashing.sol` (see [contracts security](/contracts/security)\nfor the category model). The institutional penalty schedule, verified in `core/economics/src/slashing.rs`,\npenalizes three offenses as a percentage of stake.\n\n| Offense | Penalty | Source |\n|---|---|---|\n| Equivocation (signing two blocks at one height) | 10% of stake | `equivocation_penalty_pct` |\n| Invalid state transition | 15% of stake | `invalid_state_penalty_pct` |\n| Transaction censorship | 5% of stake | `censorship_penalty_pct` |\n\nA first offense inside the grace window is recorded at zero penalty. A cooldown follows each slash, and an\noperator whose cumulative slash reaches 50% of stake is deactivated. Downtime is not a slashable offense for\ninstitutional operators.\n\n```rust\n// core/economics/src/slashing.rs, institutional defaults\nequivocation_penalty_pct: 10,\ninvalid_state_penalty_pct: 15,\ncensorship_penalty_pct: 5,\nfirst_offense_grace_epochs: 2,\ncooldown_epochs: 1,\nmax_cumulative_slash_pct: 50,\npenalize_downtime: false,\n```\n\n### What the node-agent does to keep you safe\n\nVerified in `citrate-node-agent`. The agent holds no keys: every write it wants to make, a heartbeat, a\nresult, a reward claim, is emitted as an unsigned signature request that a signing surface signs and\nbroadcasts, and the agent advances only on observed on-chain truth.\n\n- **Commitment cap and capacity check.** The bidder refuses a job at or above a commitment cap, and refuses\n new work once it is at roughly 80% of its concurrent-job capacity, so it does not accept work it cannot\n finish and slide into a penalty (`crates/bidder/src/lib.rs`).\n- **Deadline safety.** It only accepts a job when the time to the deadline is at least twice the estimated\n execution time.\n- **Liveness.** It sends a heartbeat on a 30-second cadence, and counts only broadcast heartbeats as\n liveness, never optimistically queued ones (`crates/heartbeat/src/lib.rs`).\n- **Reward claims.** It reads the claimable balance from the accounting contract and only emits a claim once\n the balance crosses a dust threshold, and never twice for the same claim in flight\n (`crates/earnings/src/lib.rs`).\n\n## Design rationale\n\nSplitting the block reward into four pools rather than paying a flat amount lets the network pay for the\nbehaviors it actually depends on, uptime, useful compute, a healthy peer set, and committed stake, instead\nof paying the same whether or not a node contributed beyond sealing the block. The trade is more parts to\nreason about; the benefit is that the reward points at the work. Slashing is the mirror: it penalizes the\nspecific harms, equivocation, invalid state, censorship, and leaves honest downtime alone for institutions\nthat cannot run around the clock. The node-agent's caps exist so that an operator who simply runs the daemon\nis kept inside the safe envelope without having to model it.\n\n## Failure modes\n\nThe honest invariant here is that rewards and penalties settle work, not promises.\n\n- **Over-commitment.** Left unprotected, an operator could accept more work than it can finish and be slashed\n for the misses. The agent's commitment cap, capacity check, and deadline safety factor are the guard, and\n they fail toward refusing work rather than accepting it.\n- **Silent reputation decay.** A drop in reputation or a slash to stake is easy to miss. The agent latches an\n alert on a greater-than-five-percent reputation drop or any stake decrease, so the alert cannot be polled\n past.\n- **Treating defaults as guarantees.** The base reward and the four pool percentages are governance-\n configurable. The load-bearing invariants are the halving cadence and the supply cap, not any single\n reward number; treat a published figure as a default.\n\n## Access and canon\n\nCommercial tier, operator implementation depth. SALT settles the work the network performs; reputation and\nslashing reward contribution and penalize misbehavior, and none of them is a speculative instrument. A node runs on hardware the operator controls. Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity. No keys appear here: rewards accrue to the operator's account, and the node-agent holds no keys,\nemitting unsigned requests for a separate signing surface to sign.\n\n## Source and verification\n\n- Source: `citrate-chain/core/economics/`, reward schedule in `src/enhanced_rewards.rs`, institutional\n parameters in `src/institutional.rs` and `config/institutional_rewards.toml`, slashing schedule in\n `src/slashing.rs`; slashing categories on chain in `contracts/src/NematocystSlashing.sol`; reputation and\n stake reads in `core/api/src/economics_rpc.rs` (`citrate_getReputationScore`, `citrate_getStakedBalance`).\n- Operator-side guards: `citrate-node-agent` (`crates/bidder`, `crates/heartbeat`, `crates/earnings`,\n supervision state), audited at `0e63363`.\n- Audited against SHA: `9d5959e` (chain), `0e63363` (node-agent).\n- Status: Implemented (testnet), internally tested, pre external audit. The reward pool percentages and base\n reward are configurable defaults in source, not certified values.\n"},"/operators/run-a-node":{"slug":"/operators/run-a-node","title":"Run a Citrate Node","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/README.md, citrate-chain/docs/OPERATIONS.md, citrate-chain/node-app/README.md, citrate-chain/docker-compose.yml, citrate-chain/config/","syncedSha":"9d5959e","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"Configuration","anchor":"configuration"},{"depth":3,"text":"Producer memory health","anchor":"producer-memory-health"},{"depth":3,"text":"Monitoring","anchor":"monitoring"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This is the operator's runbook for running a Citrate Node, the daemon that joins the Citrate Network and\nkeeps a copy of the ledger live. It is for anyone bringing up a node on their own hardware, from a single\nlocal instance to a node on testnet, chain id 40204.\n\n## What it is\n\nA Citrate Node is built from the `citrate-node` binary in the `citrate-chain` workspace. It wires storage,\nexecution, a mempool, peer management, and the RPC service into one process: a RocksDB-backed state store,\nan EVM-compatible executor, the GhostDAG consensus engine, and a JSON-RPC, WebSocket, and REST surface with\nPrometheus metrics. You run it on your own machine, on-premise by default, and it talks to other nodes over\nlibp2p. The node software is the same whether you run a local instance or join testnet, and it does not check operator identity.\n\nThe node binds its RPC surface to loopback by default, so the read and write surface is something you expose\ndeliberately behind your own reverse proxy, not by accident. The settlement and reward side of operating,\nhow a node earns SALT for the work it performs, is covered in [rewards](/operators/rewards); selling that\ncapacity into the marketplace is covered in [sell compute](/operators/sell-compute).\n\nNetwork parameters, verified against the chain README at SHA `9d5959e`:\n\n| Parameter | Value |\n|---|---|\n| Chain id | 40204 (testnet beta) |\n| Token | SALT, one trillion supply, 18 decimals |\n| Consensus | GhostDAG, k = 18, max-parents 10 |\n| Block production | a single block producer operated by Citrate today; ECVRF-P256-SHA256 (RFC 9381) proofs are checked; stake-gated eligibility is staged and off by default |\n| Finality | probabilistic confirmation, about 2 s per block; checkpoint finality is specified, not running (target design: a 100-member committee, 67 quorum, 50-block interval) |\n| Default JSON-RPC | `127.0.0.1:8545` |\n| Default WebSocket | `127.0.0.1:8546` |\n| Default REST | `127.0.0.1:3000` |\n| Default metrics | `0.0.0.0:9100` |\n\n## How to use it\n\nFollow these steps to bring up a node and confirm it is healthy.\n\n1. **Install the toolchain.** You need a Rust toolchain; the repository pins Rust 1.96.0 in\n `rust-toolchain.toml`, and the Docker build tracks that channel with `rust:stable`. Clone the\n `citrate-chain` workspace.\n\n2. **Build the binary.** From the workspace root:\n\n ```bash\n cargo build --release # builds the citrate-node binary\n ```\n\n3. **Choose how you run.** A local single node is the quickest path; testnet joins the public network at\n chain id 40204.\n\n ```bash\n # Local single node, block production on, fast blocks\n cargo run --bin citrate-node -- devnet\n\n # Join testnet\n cargo run --bin citrate-node -- --network testnet\n\n # Explicit config file and data directory\n cargo run --bin citrate-node -- --config /path/to/node.toml --data-dir /custom/path\n ```\n\n4. **Or run a small local network.** The helper script brings up three local nodes:\n\n ```bash\n ./scripts/launch_local_testnet.sh # preserve data\n ./scripts/launch_local_testnet.sh --clean # fresh start\n ./scripts/launch_local_testnet.sh --status # check status\n ```\n\n5. **Or run in Docker.** Compose profiles cover a single local node, testnet, and a five-node cluster.\n\n ```bash\n docker compose -f docker-compose.yml --profile devnet up --build\n docker compose -f docker-compose.yml --profile testnet up --build\n docker compose -f docker-compose.yml --profile cluster up --build # 5-node\n ```\n\n Inside a container the RPC binds to `0.0.0.0`, and the host exposure is set by the compose port mappings:\n the local profile maps host `8545`, `8546`, `30303`, and `9100`; the testnet profile maps host `18545`,\n `18546`, `30304`, and `19100`.\n\n6. **Confirm the node is answering.** Ask it for its current height:\n\n ```bash\n curl -s -X POST -H 'Content-Type: application/json' \\\n --data '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_blockNumber\",\"params\":[]}' \\\n http://127.0.0.1:8545\n ```\n\n7. **Watch producer memory.** A producing node should sit near 1 GB of resident memory. Sample it and read\n the thresholds below before you leave it unattended.\n\n ```bash\n ps -o rss= -p \"$(pgrep -f citrate-node | head -1)\" # resident memory, KiB\n ```\n\n## Reference\n\n### Configuration\n\nThe node reads its configuration from environment variables, verified in `node-app/README.md` and the\ncompose files. The defaults are deliberately conservative: the RPC binds to loopback, not all interfaces.\n\n| Variable | Default | Purpose |\n|---|---|---|\n| `CITRATE_DATA_DIR` | `/data` (Docker) | RocksDB storage directory |\n| `CITRATE_RPC_ADDR` | `127.0.0.1:8545` | JSON-RPC listen address |\n| `CITRATE_METRICS_ADDR` | `0.0.0.0:9100` | Prometheus metrics endpoint |\n| `CITRATE_METRICS` | `1` | metrics on |\n| `RUST_LOG` | `info,citrate=info` | log filter |\n| `CITRATE_OPERATOR_TOKEN` | unset | required for admin RPC on a non-loopback bind (fails closed) |\n\nNetwork bootstrap peers are listed by network in `config/bootstrap-nodes.json`. Testnet ships four bootstrap\nnodes across regions; the public RPC hostname is `https://rpc.citrate.ai`.\n\n### Producer memory health\n\nThe runbook in `docs/OPERATIONS.md` is the canon for producer memory. A healthy producing node sits near\n1.05 GB resident memory. The thresholds and the circuit-breaker below come from that runbook.\n\n| Resident memory | Meaning | Action |\n|---|---|---|\n| up to ~1.2 GB | healthy steady state | none |\n| 1.2 to 2 GB | elevated, watch | sample every 15 minutes, correlate with indexer or beacon load |\n| over 2 GB | leak-class behavior | trip the circuit-breaker below before the OOM killer acts |\n| over 3 GB | alert fires (`ProducerMemoryHigh`, critical) | trip the breaker immediately |\n\nThe memory-heavy startup path only runs when block production is on. The circuit-breaker is to turn it off:\nset `[mining] enabled = false` in the node config and restart. The node then serves RPC reads near 1 GB\nindefinitely, which is the safe degraded mode; only writes stop, the public read surface stays up.\n\n```bash\n# 1. Confirm you are in leak territory (resident memory, KiB):\nps -o rss= -p \"$(pgrep -f citrate-node | head -1)\"\n\n# 2. Trip the breaker: turn block production off, then restart.\n# edit the [mining] section of your node config: enabled = false\nsystemctl restart citrate-node\n\n# 3. Verify degraded but healthy: memory near 1 GB, RPC still answering.\ncurl -s -X POST -H 'Content-Type: application/json' \\\n --data '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_blockNumber\",\"params\":[]}' \\\n http://127.0.0.1:8545\n```\n\nRe-enable production only after the cause is identified, and watch memory for the first five minutes after\nrestart; the original leak fired during startup, within thirty seconds. Capture `ps` and `smaps` evidence\nbefore any restart if you can, and keep the previous release binary so you can roll back.\n\n### Monitoring\n\nThree permanent guards protect the producer, verified in `docs/OPERATIONS.md`:\n\n| Guard | Where | Trips when |\n|---|---|---|\n| `producer_steady_state` test | `core/sequencer/tests/producer_steady_state.rs` | a change re-materializes cumulative blue ancestry on the eager-load path (CI) |\n| `ProducerMemoryHigh` alert | `node/monitoring/alerts/citrate-alerts.yml` | resident memory over 3 GB sustained two minutes |\n| memory gauge sampler | `node/src/main.rs` (15s cadence) | feeds the alert |\n\nThe Docker monitoring profile brings up Prometheus and Grafana against the node's metrics endpoint.\n\n## Design rationale\n\nThe node binds to loopback by default because the safe state is the closed one: exposing the RPC surface is\na step you take deliberately, behind your own TLS terminator and access controls, not the default a fresh\ninstall hands you. Block production is gated behind a single flag so that the one memory-heavy path has a\nclean off switch, and turning it off degrades the node to a read-only server rather than taking it down. The\ntrade is that an operator who wants a public, writable endpoint has to do the reverse-proxy and token work\nthemselves; the benefit is that an unconfigured node cannot leak its admin surface onto the network.\n\n## Failure modes\n\nThis is where running a node is security relevant, so the defaults fail closed.\n\n- **No transport security on the raw RPC.** The RPC ships with no TLS and no authentication, and on bare\n metal it binds to loopback. If you bind to `0.0.0.0`, you must set `CITRATE_OPERATOR_TOKEN` and front the\n endpoint with a TLS-terminating reverse proxy and an explicit CORS allow-list. The admin methods refuse to\n serve on a non-loopback bind without the operator token.\n- **Memory leak under production load.** If resident memory climbs past 2 GB, trip the production circuit-\n breaker before the OOM killer acts; the node keeps serving reads.\n- **Reused development keys.** The local coinbase account is a well-known public test key. Never use it on\n testnet or in production, and never reuse the example development keys.\n- **Development-only switches left on.** The relaxed switches that disable signature checks or allow\n plaintext peer traffic must stay off outside local development.\n\n## Access and canon\n\nPublic. Running a node is public-good operator material, and the front door of the network. The node runs on hardware you control. Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity. No secrets, operator tokens, or private node\naddresses appear here. The public RPC hostname `https://rpc.citrate.ai` is the only network endpoint named.\nFor genesis and the network layout, see [the network](/chain/network) and [genesis](/chain/genesis).\n\n## Source and verification\n\n- Source: `citrate-chain`. Network parameters in `README.md`; the operator runbook and producer-memory\n thresholds in `docs/OPERATIONS.md`; environment variables in `node-app/README.md`; run profiles and port\n mappings in `docker-compose.yml`; bootstrap peers and institutional parameters in `config/`.\n- Audited against SHA: `9d5959e`.\n- Status: Implemented (testnet). The node, the run modes, the producer-memory guards, and the monitoring\n profile exist and run; the chain is live on testnet at chain id 40204 and has not had an external audit.\n Note: there is no `docs/PRIVATE_NETWORK.md` at this SHA; the canonical runbook is `docs/OPERATIONS.md`.\n"},"/operators/sell-compute":{"slug":"/operators/sell-compute","title":"Sell compute","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-node-agent (README.md, crates/)","syncedSha":"0e63363","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"1. Write your participation policy","anchor":"1-write-your-participation-policy"},{"depth":3,"text":"2. Self-check offline","anchor":"2-self-check-offline"},{"depth":3,"text":"3. Start the daemon","anchor":"3-start-the-daemon"},{"depth":3,"text":"4. Wire up the signer","anchor":"4-wire-up-the-signer"},{"depth":3,"text":"5. Operate","anchor":"5-operate"},{"depth":3,"text":"6. Understand the bidding so your bids win and stay profitable","anchor":"6-understand-the-bidding-so-your-bids-win-and-stay-profitable"},{"depth":3,"text":"7. Job lifecycle, experimental","anchor":"7-job-lifecycle-experimental"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This is the standing procedure for selling compute on Citrate Market with Citrate Node, end to end, on\nchain id 40204. It is written for operators who run their own hardware and want idle\nGPU hours to earn while the machine is otherwise unsupervised.\n\n## What it is\n\nYou sell compute by running Citrate Node as a daemon on your own host. The daemon watches Citrate Market,\ndecides which jobs to bid on by your policy, proves it is alive with a heartbeat, and drives a won job to\ncompletion. It holds no keys: every on-chain write is emitted as an unsigned `SignatureRequest` that your\nseparate signing surface, the operator's Citrate Keyring or a signing relay, signs and broadcasts. Plan\nfor two roles, the agent that decides and the signer that holds keys. The work you perform settles in SALT.\n\nThe field reference for every flag, route, and gate is [Citrate Node](/compute/node-agent), audited against\nthe same SHA. Bringing the node online at all is covered in [run a node](/operators/run-a-node), and the\nidentity step is covered under [verified identity](/aa/identity).\n\n## How to use it\n\n### 1. Write your participation policy\n\nCreate `compute.json`:\n\n```json\n{ \"enabled\": true, \"allocation_percent\": 50, \"schedule\": \"always\" }\n```\n\n`schedule` is `always`, `nights` (22:00 to 05:59 local), or `weekends`. Start with `enabled: false` to\ndry-run the wiring, then flip it to `true` (`crates/config`).\n\n### 2. Self-check offline\n\n```bash\nnode-agent path/to/compute.json\n```\n\nThis confirms your policy parses and prints the heartbeat calldata. No RPC is contacted.\n\n### 3. Start the daemon\n\n```bash\nexport CITRATE_RPC_URL=https:// # https or loopback http only\nexport CITRATE_PROVIDER_ADDRESS=0x\nexport CITRATE_NODE_AGENT_DAEMON=1\nnode-agent path/to/compute.json\n```\n\nThe daemon brings up the loopback supervision API on `127.0.0.1:19600`, reads chain state each tick, runs\nthe bidder, and beats every 30 seconds.\n\n### 4. Wire up the signer\n\nThe agent emits unsigned requests; your signer pulls, signs, broadcasts, then acknowledges:\n\n```bash\nTOKEN=$(cat ~/.citrate/node-agent/supervision.token)\ncurl -H \"Authorization: Bearer $TOKEN\" http://127.0.0.1:19600/signature-requests\n# sign and broadcast externally, then:\ncurl -X POST -H \"Authorization: Bearer $TOKEN\" \\\n -d '{\"tx_hash\":\"0x...\"}' \\\n http://127.0.0.1:19600/signature-requests//observed\n```\n\n### 5. Operate\n\n```bash\ncurl http://127.0.0.1:19600/health # liveness, no auth\ncurl -H \"Authorization: Bearer $TOKEN\" http://127.0.0.1:19600/status # idle|bidding|executing|paused\ncurl -X POST -H \"Authorization: Bearer $TOKEN\" http://127.0.0.1:19600/pause # stop new bids\ncurl -X POST -H \"Authorization: Bearer $TOKEN\" http://127.0.0.1:19600/resume\n```\n\n`pause` stops new bids but lets in-flight jobs finish; use it for maintenance.\n\n### 6. Understand the bidding so your bids win and stay profitable\n\nThe bidder skips a job unless it is enabled, inside the schedule window, under the 10-SALT Commitment cap,\na Commitment-tier job, under 80% capacity, deadline-feasible, and priced off a fresh oracle. It then bids\n`cost x 1.15`, capped at `0.9 x maxPrice`, and skips if that would fall below cost. Scoring on-chain is\n40% price, 30% reputation, 20% load, and 10% verification tier, so a low bid alone does not win; reputation\nearned by completing jobs matters. Tune `allocation_percent`, `schedule`, and `CITRATE_NODE_PFLOPS_1E18`,\nyour throughput, accordingly. See [rewards and reputation](/operators/rewards).\n\n### 7. Job lifecycle, experimental\n\nWhen the execution path is enabled (SELL-S2, set `CITRATE_IPFS_GATEWAY`, `CITRATE_LLAMA_URL`, and\n`CITRATE_JOB_INPUT_DIR`), a won job walks: `Assigned` → `startExecution` → run inference (weights fetched\nby model CID, digest recomputed locally, served) → `submitCommitment` → `submitResult` → `completeJob`.\n`submitResult` is refused past the execution deadline, an anti-slash guard. Earnings auto-claim once\nclaimable reaches `CITRATE_CLAIM_THRESHOLD_WEI`. Treat this path as experimental until SELL-S2 lands fully.\n\n## Reference\n\n| Surface | Where |\n|---|---|\n| Every flag, route, env var, and gate | [Citrate Node](/compute/node-agent) |\n| The on-chain marketplace functions | [compute contracts](/contracts/compute) |\n| Reputation, scoring, and slashing-protection | [rewards and reputation](/operators/rewards) |\n| Bringing the node online | [run a node](/operators/run-a-node) |\n| Identity verification through VERI | [verified identity](/aa/identity) |\n\n## Design rationale\n\nThe agent never holds a key because an unattended process reacting to live prices on a GPU host is the\nlast place a signing key belongs. Unsigned requests plus an external signer mean a daemon compromise cannot\nmove stake or funds. The conservative bidder, the 80% capacity cap, the night and weekend schedules, and\nthe twice-execution-time margins all exist so an operator can sell idle hours without watching the machine\nand without taking on work it cannot finish before a slashable deadline.\n\n## Failure modes\n\n- The daemon exits at startup if `CITRATE_NODE_AGENT_ADDR` is not loopback; the supervision surface is\n localhost-only by design.\n- No bids usually means a bidder gate fired: check `enabled`, the schedule window, capacity under 80%,\n deadline feasibility, oracle freshness, and the 10-SALT cap. `/status` and `/health` report the live\n state.\n- An RPC refused at startup is the outbound TLS gate; plaintext HTTP to a non-loopback host is rejected.\n Use https. Never set `CITRATE_NODE_AGENT_ALLOW_INSECURE_OUTBOUND` on a production node; it is a dev-only\n LAN escape hatch that exposes you to a network attacker rewriting chain state, oracle prices, and job\n state.\n- A late result is refused rather than submitted, so the lifecycle aborts cleanly instead of being slashed.\n\n## Access and canon\n\nTier commercial.kyc: operator-depth marketplace know-how, gated on identity verification through Citrate's in-house verification (VERI), not\non a seat. Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity. Compute is sold from your own hardware,\non-premise by default, and SALT settles the work performed; it is the unit you count in, not a product to\nhold. No secrets here: the supervision token is generated locally at mode 0600 and never transcribed, bind\nthe supervision API to loopback only, and key custody stays in your external signer. The agent never holds\nkeys.\n\n## Source and verification\n\nVerified against `citrate-node-agent` at `0e63363` (`README.md` and `crates/`). The policy fields against\n`crates/config`, the bidder gates and cost-plus pricing against `crates/bidder`, the lifecycle and unsigned\nsigning seam against `crates/lifecycle`, the supervision routes against `crates/supervision`, and the\nmarketplace scoring and fee split against `ComputeMarketplace.sol`. Status: SELL-S1 (settings, bidder,\nheartbeat, supervision, live reads) Implemented, pre-audit; SELL-S2 (execution and earnings) Specified and\nexperimental.\n"},"/operators/tutorials/become-a-seller":{"slug":"/operators/tutorials/become-a-seller","title":"Become a seller","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-node-agent (README.md, crates/) + ComputeMarketplace.sol","syncedSha":"0e63363","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Step 1, complete membership verification","anchor":"step-1-complete-membership-verification"},{"depth":3,"text":"Step 2, build the agent","anchor":"step-2-build-the-agent"},{"depth":3,"text":"Step 3, write a policy","anchor":"step-3-write-a-policy"},{"depth":3,"text":"Step 4, self-check offline","anchor":"step-4-self-check-offline"},{"depth":3,"text":"Step 5, register as a provider","anchor":"step-5-register-as-a-provider"},{"depth":3,"text":"Step 6, run live","anchor":"step-6-run-live"},{"depth":3,"text":"Step 7, drive it","anchor":"step-7-drive-it"},{"depth":3,"text":"Step 8, win a job","anchor":"step-8-win-a-job"},{"depth":3,"text":"Step 9, execute and settle","anchor":"step-9-execute-and-settle"},{"depth":3,"text":"Step 10, verify you are selling","anchor":"step-10-verify-you-are-selling"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"A walkthrough from a fresh checkout to a live seller on Citrate Market, chain id 40204. You will complete membership verification, build and configure Citrate Node, register as a provider on the marketplace contract,\nlet the agent bid and win a job, and walk that job to payment, settled in SALT. It is written for operators\nwho run their own hardware. Allow roughly twenty minutes for the local steps; verification and on-chain\nconfirmations take their own time.\n\n## What it is\n\nCitrate Node decides; you sign. The daemon reads your policy, watches the market, and produces unsigned\nwrites; your signing surface, the operator's Citrate Keyring or a relay, signs and broadcasts them. The\nmarketplace itself, `ComputeMarketplace` on Citrate Network, is the contract that holds escrow, runs the\nbidding, and releases payment. This tutorial touches both: the daemon for decisions, the contract for the\nmoney. Bringing the node online first is covered in [run a node](/operators/run-a-node), and the full\ncontract surface in [compute contracts](/contracts/compute).\n\n## How to use it\n\n### Step 1, complete membership verification\n\nMembership includes identity verification through VERI, Citrate's in-house verification. Node and consensus code do not check it.\nComplete verification through [verified identity](/aa/identity). Citrate keeps the verification result, not the\npersonal data behind it. You cannot register as a provider without it.\n\n### Step 2, build the agent\n\n```bash\n# from the citrate-node-agent workspace root\ncargo build --release\n# the binary is target/release/node-agent\n```\n\n### Step 3, write a policy\n\nCreate `compute.json`:\n\n```json\n{\n \"enabled\": true,\n \"allocation_percent\": 25,\n \"schedule\": \"nights\"\n}\n```\n\nThis allots 25% of the GPU, at night only (22:00 to 05:59 local). Fields are validated against\n`crates/config`; `allocation_percent` must be 0 to 100.\n\n### Step 4, self-check offline\n\n```bash\nnode-agent compute.json\n```\n\nThe agent prints your enabled, allocation, and schedule values, the current clock, the heartbeat calldata,\nand a self-check. No RPC is contacted. If you set `enabled: false`, it reports disabled, a safe way to\nconfirm wiring.\n\n### Step 5, register as a provider\n\nRegistration is an on-chain write to `ComputeMarketplace.registerProvider(bytes32[] supportedModels)`. It\nis payable and requires a stake: `MIN_PROVIDER_STAKE` is 1000 SALT (`ComputeMarketplace.sol`). The stake is\nyour collateral; the contract slashes it if you take a job and miss the deadline. Pass the model hashes you\nwill serve, and your profile starts at full reputation (10000 basis points) with a default of 10 concurrent\njobs. You sign and broadcast this from your Citrate Keyring, not from the daemon. Add more stake later with\n`addStake()`.\n\n### Step 6, run live\n\n```bash\nexport CITRATE_RPC_URL=https:// # https or loopback http only\nexport CITRATE_PROVIDER_ADDRESS=0x\nexport CITRATE_NODE_AGENT_DAEMON=1\nnode-agent compute.json\n```\n\nThe daemon starts the supervision API on `127.0.0.1:19600` and begins reading chain state, bidding, and\nbeating every 30 seconds.\n\n### Step 7, drive it\n\nIn a second terminal:\n\n```bash\nTOKEN=$(cat ~/.citrate/node-agent/supervision.token)\n\ncurl http://127.0.0.1:19600/health # no auth\ncurl -H \"Authorization: Bearer $TOKEN\" http://127.0.0.1:19600/status # idle|bidding|executing|paused\n\n# pull unsigned writes for your signer to sign and broadcast\ncurl -H \"Authorization: Bearer $TOKEN\" http://127.0.0.1:19600/signature-requests\n\n# after signing and broadcasting externally, acknowledge:\ncurl -X POST -H \"Authorization: Bearer $TOKEN\" \\\n -d '{\"tx_hash\":\"0x...\"}' \\\n http://127.0.0.1:19600/signature-requests//observed\n\n# maintenance: stop new bids (in-flight jobs finish), then resume\ncurl -X POST -H \"Authorization: Bearer $TOKEN\" http://127.0.0.1:19600/pause\ncurl -X POST -H \"Authorization: Bearer $TOKEN\" http://127.0.0.1:19600/resume\n```\n\n### Step 8, win a job\n\nWhen a requester posts a job with `postJob`, the contract locks their `maxPrice` in escrow and opens the\nbidding window. The agent's bidder evaluates it, and if every gate passes it queues a `bidOnJob(jobId,\nprice, estimatedLatency)` write for your signer. After the bid deadline, anyone can call `assignBestBid`,\nwhich scores the bids (40% price, 30% reputation, 20% load, 10% verification tier) and assigns the winner.\nA low price alone does not win; the reputation you earn by completing jobs is what moves you up.\n\n### Step 9, execute and settle\n\nOnce assigned, the lifecycle planner walks the job through the contract, one signed write at a time:\n\n```text\nAssigned -- startExecution(jobId) -------------------------> Executing\nExecuting -- submitCommitment(jobId, SHA3(in||out||nonce)) -> commitment recorded\nExecuting -- submitResult(jobId, outputHash, proof) --------> Verifying (verifies inline)\nVerifying -- completeJob(jobId) ----------------------------> Completed (releases payment)\n```\n\n`submitResult` is refused past the execution deadline, so a late result aborts cleanly rather than being\nslashed. On `completeJob` the escrow is released: 95% to you, 2.5% burned, 2.5% to the treasury\n(`BME_BURN_DIVISOR` and `TREASURY_DIVISOR` are both 40 in `ComputeMarketplace.sol`). The execution path\nruns only when `CITRATE_IPFS_GATEWAY`, `CITRATE_LLAMA_URL`, and `CITRATE_JOB_INPUT_DIR` are set; this is\nSELL-S2, experimental. Earnings sweep with `claimRewards()` once claimable reaches your threshold.\n\n### Step 10, verify you are selling\n\n- `/status` shows `bidding` or `executing` when there is matching demand.\n- `/health` shows a recent heartbeat age.\n- Your provider address shows broadcast transactions on the network explorer, and `getProvider` reflects\n your stake, active jobs, and reputation.\n\n## Reference\n\nThe contract functions you touch, audited against `citrate-chain/contracts/src/ComputeMarketplace.sol` at\n`e6f11ef`:\n\n| Function | What it does |\n|---|---|\n| `registerProvider(bytes32[])` | Register as a provider; payable, requires `MIN_PROVIDER_STAKE` (1000 SALT). |\n| `addStake()` | Add collateral to a registered provider. |\n| `bidOnJob(uint256,uint256,uint256)` | Place a bid at or below the job's `maxPrice`. |\n| `assignBestBid(uint256)` | Score the bids and assign the winner; callable by anyone after the bid deadline. |\n| `startExecution(uint256)` | Assigned provider confirms work has begun. |\n| `submitCommitment(uint256,bytes32)` | Record `SHA3(input || output || nonce)` before the result. |\n| `submitResult(uint256,bytes,bytes)` | Submit the output hash and tier proof; refused past the deadline. |\n| `completeJob(uint256)` | Release escrow: 95% provider, 2.5% burn, 2.5% treasury. |\n| `getProvider(address)` | Read a provider profile (stake, active jobs, reputation). |\n| `getJob(uint256)` | Read a job's state and parameters. |\n\nSlashing on timeout is 5% of stake (`TIMEOUT_SLASH_BPS = 500`). A disputed result requires a 10-SALT bond\n(`DISPUTE_BOND`) that is burned if the dispute fails. See [compute contracts](/contracts/compute) and\n[rewards and reputation](/operators/rewards) for the rest.\n\n## Design rationale\n\nThe stake-and-slash design is what lets a requester trust an unknown provider: your 1000 SALT is collateral\nthat you will finish what you bid on, and the scoring formula rewards a record of completed jobs over a\nsingle cheap bid. The agent never signs, so the daemon reacting to live prices on your GPU host cannot move\nthat stake; only your Citrate Keyring can. The conservative bidder keeps you on the safe side of the\ndeadline that the slash protects.\n\n## Failure modes\n\n- The daemon exits immediately if `CITRATE_NODE_AGENT_ADDR` is not loopback; the supervision surface is\n localhost-only by design.\n- No bids usually means a bidder gate fired: check `enabled`, the schedule window, capacity under 80%,\n deadline feasibility, oracle freshness, and the 10-SALT cap.\n- An RPC refused at startup is the outbound TLS gate rejecting plaintext HTTP to a non-loopback host. Use\n https. Do not set `CITRATE_NODE_AGENT_ALLOW_INSECURE_OUTBOUND` on a production node; it is a dev-only LAN\n escape hatch that exposes you to a network attacker rewriting chain state.\n- `registerProvider` reverts below 1000 SALT, with no supported models, or if you are already registered.\n- A missed execution deadline slashes 5% of your stake; the agent aborts before submitting late to avoid\n exactly this.\n\n## Access and canon\n\nTier commercial.kyc. No secrets in this tutorial: the supervision token is generated locally at mode 0600\nand read from its file, keys live only in your external signer, and the dev-only insecure-outbound flag is\ncalled out as forbidden in production. Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity. Compute is sold\nfrom your own hardware, on-premise by default, and SALT settles the work performed; it is the unit you\ncount in, not a product to hold. The agent holds no keys.\n\n## Source and verification\n\nVerified against `citrate-node-agent` at `0e63363` (the agent, `crates/`) and\n`citrate-chain/contracts/src/ComputeMarketplace.sol` at `e6f11ef` (the marketplace, which lives in the\n`citrate-chain` repository, not the node-agent). The build, policy, self-check,\ndaemon, and supervision steps against `crates/config`, `crates/node-agent`, and `crates/supervision`; the\nbid decision against `crates/bidder`; the lifecycle writes and the unsigned signing seam against\n`crates/lifecycle`; registration, stake, scoring, the fee split, the timeout slash, and the dispute bond\nagainst `ComputeMarketplace.sol`. Status: SELL-S1 (register, bid, heartbeat, supervision, live reads)\nImplemented, pre-audit; SELL-S2 (execution and earnings) Specified and experimental.\n"},"/research/atis":{"slug":"/research/atis","title":"ATIS, Analog Token Importance Scoring","tier":"public","orgId":null,"sourceKind":"linked","source":"citrate-docs/gradient_papers_v3/Gradient_Papers_No5_ATIS_v3.md","syncedSha":"cd729ed","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"ATIS is a research direction, not a feature. It asks whether the decision of which tokens a transformer\nshould attend to could be made in analog hardware, before the digital arithmetic begins, to push the\nenergy cost of inference below the digital floor. This page is for researchers; it summarizes Gradient\nPaper V and is clear about what does not exist: there is no prototype, no simulation, and no code in the\nCitrate Network for any of it.\n\n## What it is\n\nPruning tokens that contribute little to the next layer is a well-studied way to speed up attention,\nusually two to four times. ATIS, Analog Token Importance Scoring, proposes computing the importance score\nitself in a Field-Programmable Analog Array placed before the digital query and key projection, so the\ncheap analog stage decides which tokens are worth the expensive digital stage. The appeal is energy:\nattention grows with the square of sequence length, and on long contexts that quadratic term dominates\nboth memory bandwidth and power.\n\nThis is theoretical work. The paper is honest that the naive version does not pay off, and its value is\nthe framing, filter before the expensive operation, together with a clear account of why the obvious\ndesign fails. We carry it here in the same spirit: as a direction a researcher might pursue on the\nCitrate substrate, not as anything the network does.\n\n## How to use it\n\nThere is nothing to run. Read the paper if you work on inference hardware or efficient attention, and\ntreat the page as orientation. Where ATIS touches the network is only conceptual: the network pays for\ninference by work performed, so any genuine energy saving would flow to the operator who earned it, which\nis the economic reason a researcher might build on the substrate at all. The relevant built surfaces are\nthe hardware-agnostic inference router and the attestation gates, described under\n[verifiable inference](/research/verifiable-inference), neither of which depends on ATIS.\n\n## Reference\n\nA summary of the paper's structure, not a copy. The honest core is the second table: the naive design\nspends almost all its energy converting digital signals to analog.\n\n| Section | Claim |\n|---|---|\n| The problem | Attention is quadratic in sequence length; on 8K-token contexts the matrix dominates energy and bandwidth. |\n| The proposal | Insert an analog filter between embedding and digital attention: convert to analog, approximate dot products against a learned query prototype, compare to a threshold, and run the digital pipeline only on the surviving tokens. |\n| The bottleneck | The digital-to-analog conversion, not the analog compute, dominates the budget, so the naive design costs more than the attention it was meant to avoid. |\n| The honest conclusion | ATIS does not pay off without an architecture that removes the conversion step. |\n\nThe paper sketches three conversion-free directions, all multi-year hardware research: charge-domain\ncompute inside memory sense amplifiers, mixed-signal stores that keep embeddings analog from training\ntime, and photonic dot products driven by a laser modulator. It also notes a purely digital fallback: a\nsmall importance-predicting network run before attention captures most of the framing's value, because\npruning the token set shrinks the attention matrix quadratically across layers, without any analog\nhardware at all.\n\n## Design rationale\n\nThe paper earns its place in the series by being candid rather than promising. An earlier revision named\nthe conversion bottleneck; this one sharpens it into the conclusion that the naive approach should not be\nbuilt. That is the right altitude for a research page: state the idea plainly, state why it is hard, and\ndo not let the framing's appeal stand in for a result. The connection to Citrate is economic, not\ntechnical. Because the network settles work performed, an operator who found a real efficiency would keep\nthe gain, which is the incentive that makes hardware research on the substrate rational. The network does\nnot require ATIS, and ATIS does not require the network.\n\n## Access and canon\n\nAcademic tier. Nothing here is sensitive; the paper cites public literature and commercial datasheets,\nand there is no Citrate Network surface, contract, or endpoint involved. This is the soil intelligence\ncould one day run on more cheaply, described as research and not as a claim.\n\n## Source and verification\n\n- Paper, linked, not copied: `citrate-docs/gradient_papers_v3/Gradient_Papers_No5_ATIS_v3.md`.\n- Code anchor: none. This is hardware research with no implementation in `citrate-chain`, and the series\n index correctly lists the paper with no code anchor.\n- Status: Theoretical. There is no Field-Programmable Analog Array prototype, no SPICE simulation, and no\n measured energy figure. The digital importance-predicting fallback is a researcher's option, not a\n shipped feature, and the custom-hardware attestation extension is specified pending the attestation\n surface. This page frames a direction honestly; it does not describe a feature of the network.\n- Audited against SHA: `cd729ed` (citrate-docs).\n- Related: [the Gradient Papers](/research/gradient-papers), [verifiable inference](/research/verifiable-inference).\n"},"/research/bdd":{"slug":"/research/bdd","title":"The Gherkin acceptance library","tier":"public","orgId":null,"sourceKind":"linked","source":"citrate-chain/specs/gherkin/ + per-repo .agentile features","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This is the behavioral half of how Citrate pins down correctness: a library of Gherkin acceptance\nspecifications that say, in plain Given/When/Then steps, what each piece of the system must do before any\ncode is written. It is for researchers and reviewers who want to read the protocol as observable behavior.\nWe link the features here; we do not copy them.\n\n## What it is\n\nA Gherkin feature file states a behavior as a set of scenarios, each written in the same shape: a `Feature`\nthat names the surface, a `Background` that fixes the starting conditions, and one or more `Scenario` blocks\nof `Given` a situation, `When` an action, `Then` an expected outcome. At Citrate the feature file is the\nspecification, not a description written after the fact. A feature file that disagrees with the code is a\ncontinuous-integration failure, because the steps are run as integration tests on every commit.\n\nThe reason for working this way is that coding agents fail in recognizable patterns: they drift from the\nagreed scope, they forget earlier architecture, they let regressions through quietly, they leave stubs in\nplace, and they make assumptions about the platform. Stating a work package as concrete observable behavior\nblocks each of these. A scenario that begins `Given an empty pool` will fail any hardcoded stub return, and\na `Background` that pins the chain id and the RPC endpoint stops platform assumptions from drifting. The\nmethodology is argued in Gradient Paper No. 4, `Behavioral Issues`.\n\n## How to use it\n\nThe cycle ties each feature to code and to a test, in order.\n\n1. A person writes the Gherkin. The feature file is the contract between the operator and the agent; each\n scenario is a behavior to implement.\n2. An agent writes the step definitions so the scenarios become failing tests, for example under\n `core/execution/tests/` or `contracts/test/`. They fail first, on purpose.\n3. The agent writes the implementation until every step passes.\n4. The code is refactored while the tests stay green.\n5. The feature lands in the repository beside the implementation, and continuous integration runs the steps\n as integration tests from then on.\n\nTo read a feature, open the `.feature` file under the relevant repo's `specs/gherkin/` or `.agentile`\nfeatures directory and read its scenarios top to bottom; each one is a behavior the running system commits\nto. To check that the code still honors a feature, run that repo's test suite, which executes the step\ndefinitions.\n\n## Reference\n\nThe largest single library is in `citrate-chain/specs/gherkin/`, with 31 feature files. A representative\nscenario, abridged from `mentor_matching.feature`:\n\n```gherkin\nFeature: Mentor-mentee matching + adapter verification flow\n Background:\n Given the Citrate testnet (chain id 40204) is live\n And the inference-proof-verify precompile is dispatched at 0x0108\n And the trust floor is set to accuracy >= 0.30 (Q16: 19661)\n\n Scenario: Standard mentor match with adequate accuracy gap\n Given my profile shows a weak score on dimension AdapterCreation\n When LearningCycleManager.advanceCycle() is called\n Then the protocol selects the top candidates by AdapterCreation score\n And filters by blue_score above the floor\n And emits MentorAssigned(me, mentor, AdapterCreation)\n```\n\nThe chain library spans these areas, named from the real feature files:\n\n| Area | Example features |\n|---|---|\n| Credits and accounts | `token_transfer.feature`, `wallet_integration.feature` |\n| Contracts and deploy | `contract_deploy.feature`, `model_deploy.feature` |\n| Learning and mentorship | `learning_daemon.feature`, `mentor_matching.feature`, `belnap_aggregation.feature`, `dataparallel_training.feature` |\n| Inference and routing | `model_inference.feature`, `inference_pool.feature`, `routing_model.feature`, `pipeline_parallel_inference.feature` |\n| Gateway and billing | `gateway_inference.feature`, `gateway_batch.feature`, `gateway_api_key.feature`, `gateway_usage.feature`, `credit_billing.feature`, `x402_payment.feature` |\n| Compute settlement | `computepool_settlement.feature` |\n| Desktop flows | `assistant_pane_flow.feature`, `drawer_lifecycle.feature`, `modal_lifecycle.feature`, `toast_lifecycle.feature`, `scope_switch_flow.feature`, `batch_operation.feature` |\n| Governance and safety | `role_escalation_timer.feature`, `school_safety.feature`, `listing_visibility.feature` |\n| Research hypotheses | `hypothesis_h1.feature`, `hypothesis_h2.feature`, `hypothesis_h3.feature` |\n\nThe convention reaches across the federation, each repo keeping its behavior contracts next to its code:\n\n- `citrate-chain/specs/gherkin/`: 31 features; step definitions in `core/execution/tests/` and\n `contracts/test/`.\n- `citrate-agentile-archive/bdd/agent/`: the agent-harness contracts, grouped as `approval/`, `audit/`,\n `break_glass/`, `capsule_install/`, and `data_class/` (for example\n `low_risk_auto_approve.feature`, `single_security_officer.feature`, `no_read_up.feature`).\n- `citrate-explorer/.agentile/specs/features/`: 17 features covering the explorer surfaces (for example\n `live-dag.feature`, `search.feature`, `authentication.feature`, `data-privacy-storage.feature`).\n- `citrate-federation/.agentile/gtm-spine/features/`: 25 features for identity, console, sell, and\n inference sprints (for example `IDP-S3-wallet-linking.feature`, `SELL-S1-node-agent-mvp.feature`).\n- `citrate-agent-runtime/capsules/*/gherkin/`: one feature per capsule (for example `hello`,\n `anchor-session`, `revoke-role`, `verify-provenance-chain`).\n- `nist-agent/features/`: features grouped under `core/`, `chain/`, `capsule/`, `distribution/`,\n `overlays/`, and `surfaces/`.\n\nA feature describes behavior, run as an executable acceptance test, while the [TLA+ corpus](/research/tla)\nproves state-machine invariants with a model checker. Many features have a TLA+ counterpart for the same\nsurface; `belnap_aggregation.feature` lines up with `BelnapLattice.tla` and `ParaconsistentAggregation.tla`,\nand `computepool_settlement.feature` with `GatewayBatchLifecycle.tla`. The engineering rules that make a\nfeature file mandatory are in [the rules](/methodology/rules).\n\n## Design rationale\n\nWriting the behavior first, then the test, then the code, is slower at the start of a work package and\ncheaper across its life. The feature file gives the operator and the agent one artifact to agree on before\nwork begins, and because it is executed on every commit, it cannot quietly fall out of step with the code\nthe way prose documentation can. The cost is discipline: a behavior that is hard to state as a scenario is\nusually a behavior that is not yet well understood, and the method forces that to surface early rather than\nlate.\n\n## Access and canon\n\nAcademic tier. The features describe behavior over public addresses and the public testnet RPC; no keys or\ncredentials appear in them or on this page. We link the `.feature` files and their step definitions rather\nthan copy them, and show only an abridged illustrative scenario, so the files in the repositories remain the\ntruth.\n\n## Source and verification\n\n- Source: `citrate-chain/specs/gherkin/` (31 features) plus the per-repo libraries named above under\n `bdd/`, `.agentile/`, and `features/` directories. Methodology: Gradient Paper No. 4,\n `gradient_papers_v3/Gradient_Papers_No4_Behavioral_Issues_v3.md` (linked, not copied).\n- Audited against SHA: `e68af83` (citrate-chain); per-repo libraries pinned at each repo's HEAD.\n- Status: Specified, the features are written and run as acceptance tests in continuous integration; a\n feature with passing steps in CI is Verified for the surface it covers. The `.feature` files and their\n step definitions are the truth; this page links them.\n"},"/research/gradient-papers":{"slug":"/research/gradient-papers","title":"The Gradient Papers (v3)","tier":"public","orgId":null,"sourceKind":"linked","source":"citrate-docs/gradient_papers_v3/","syncedSha":"cd729ed","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The Gradient Papers are the research corpus the Citrate Network grew from, an eleven-part working\ndissertation plus a series index. This page is for researchers, engineers, and reviewers who want the\nreasoning behind the design. It indexes the papers and points each one at the surface in Almanac that\ntreats it; following Rule 9, it summarizes and links the papers, it does not copy them.\n\n## What it is\n\nThe papers argue one thesis: a public ledger and a learning network are the same shape. A ledger is many\nmachines agreeing on one view of truth; a learning network is many machines converging on one\nrepresentation of the world. Both are gradient processes, one over disagreement, one over loss. The\nseries makes that identity load-bearing, so every paper that claims to learn points at a contract that\nrecords contribution, and every paper that claims to reach consensus points at a finality mechanism that\ntreats disagreement as data.\n\nThe papers are research, not a product manual. Some describe surfaces that run on testnet today, some\ndescribe designs written down but not yet built, and one describes a hardware direction with no code at\nall. We keep those honest by carrying a maturity tag on each paper and, in the table below, naming the\nAlmanac page where the work actually lives when it has been built.\n\nThe v3 revision was written in April 2026 against the v0.5.0 testnet on chain id 40204, so every\nmechanical claim traces to a file path or a public contract address in the source. The series index,\n`Gradient_Papers_No0_Series_Index_v3.md`, carries the v2 to v3 change log and the reading paths by role.\n\n## How to use it\n\nRead the index first, then the paper your role calls for. The suggested orders, taken from the No.0\nindex, are:\n\n| Reader | Suggested order |\n|---|---|\n| Engineers | I, X, XI, IV, II, III |\n| Researchers | XI, II, V, X, III, I |\n| Operators | I, IV, X, IX, VIII |\n| Community | VIII, VII, VI, IX, I |\n\nWhen a paper has a surface in Almanac, read the Almanac page for what is built and the paper for why it is\nbuilt that way. When a paper is theoretical, the paper is all there is, and the page says so.\n\n## Reference\n\nEleven papers numbered No.1 through No.11, plus the No.0 series index. The maturity column is the paper's\nown header tag. The Almanac page column links to the surface that treats the work; where a paper is\nresearch with no built surface, that is stated instead.\n\n| No. | Title | One line | Treated in Almanac |\n|---|---|---|---|\n| 0 | Series Index | The map: change log, maturity tags, and reading paths by role. | this page |\n| 1 | Citrate Technical Paper | The foundational specification, a Layer-1 BlockDAG with the EVM-compatible Lattice VM and AI-native precompiles that make models first-class on the ledger. | [Lattice VM](/chain/lvm), [precompiles](/chain/precompiles) |\n| 2 | Paraconsistent Consensus | Treats disagreement as information; runs federated meta-learning over GhostDAG and BFT checkpoints, combining views with Belnap four-valued logic. | [paraconsistent consensus](/research/paraconsistent) |\n| 3 | The Mentorship Protocol | The social layer of learning: how a weaker node finds a stronger one to learn from, who earns mentor standing, and how a mentorship is told apart from extraction. | [mentorship](/research/mentorship) |\n| 4 | Behavioral Issues | Catalogues five reproducible agent failure modes and the behavior-driven development discipline, Gherkin contracts, that blocks each one. | [behavior-driven development](/research/bdd) |\n| 5 | ATIS | Proposes computing transformer attention's token-importance score in analog hardware before the digital projection; carries an honest analysis of why the naive version does not pay off. | [ATIS](/research/atis) |\n| 6 | The Memetic Money Portal | Moving value in and out of the network; replaces the earlier automated bridge with a contracted market-maker model governed on the ledger. | [the bridge](/chain/bridge) |\n| 7 | The Cooperative Model (conceptual) | A conceptual, currently-tabled research direction: a third path between concentrated ownership and flat cooperatives, where standing accrues in proportion to contribution, recorded on the ledger. Not a current legal entity. | [marketplace economics](/contracts/economics) |\n| 8 | The BR1J Constitution | The constitutional law of the Citrate organization, the boundaries no proposal can cross, enforced through the treasury governor. | [governance](/contracts/governance) |\n| 9 | The Medusa Paradigm | Derives the architecture from cnidarian biology, nerve nets, siphonophore colonies, and Turritopsis, mapping the motifs to slashing and attestation surfaces. | research only; motifs surface in [security](/contracts/security) |\n| 10 | The Substrate of Verifiable Inference | How on-chain verification of off-chain model work is mechanized: Halo2-KZG proofs, deterministic Q16 compute, and attestation gates. | [verifiable inference](/research/verifiable-inference) |\n| 11 | The Neuroarchitectural Transformer (NAT) | Verifiable-by-construction model architecture: the hidden representation is partitioned into named zones wired over a declared, model-checkable topology, merged on a deterministic Q16.16 path, with a provenance trace emitted as a first-class output of every forward pass. | research only; the public `nat` architecture |\n\n## Design rationale\n\nThe series is written to be auditable, not persuasive. v3 added a Verified tag for claims with on-chain\nor audit evidence, switched code references from filenames to file and line against a pinned commit, and\nreplaced hand-waved statistics with measured benchmarks. The stated honesty principle is that each paper\nsays plainly whether a thing works today, is specified, or is conjectural, and a continuous-integration\ncheck moves a paper out of draft only once every numeric claim either cites code or is tagged as a\nhypothesis. We index them the same way: the table above does not promote a theoretical paper to a built\nfeature, and the maturity column is the paper's own, not ours.\n\n## Access and canon\n\nAcademic tier. The papers cite public contract addresses and the public testnet RPC only; no keys,\nrecovery phrases, or private endpoints appear in them or here. The network is a public ledger paired with\nprivate on-premise instances, and the research describes the public half. The papers are authored by Larry Klosowski\nand Lauren Mendenhall, Citrate Inc.\n\n## Source and verification\n\n- Source: `citrate-docs/gradient_papers_v3/` in this repository, eleven numbered papers plus\n `Gradient_Papers_No0_Series_Index_v3.md`.\n- Audited against SHA: `cd729ed` (citrate-docs).\n- Rule 9: this page is an annotated index. The papers are the source of truth; Almanac links them and does\n not duplicate their text.\n- Status: Specified. The papers are a written corpus; the maturity of each described surface is the\n paper's own tag, shown above and detailed on the linked Almanac pages.\n"},"/research/learning":{"slug":"/research/learning","title":"Citrate Orchard, federated learning cycles","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/core/learning/","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"The learning cycle and its four phases","anchor":"the-learning-cycle-and-its-four-phases"},{"depth":2,"text":"How the cycle records its result","anchor":"how-the-cycle-records-its-result"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Citrate Orchard is the part of the network where models learn together without their training data leaving the machines that hold it. This page explains the learning cycle, how its result is recorded, and where the engine ends and the on-chain wiring is still ahead of us. It is written for researchers and protocol engineers.\n\n## What it is\n\nA model on Citrate does not learn by gathering everyone's data into one place. Each node serves a reference input through its own local model and publishes the result of that, an embedding vector with a per-dimension confidence, never the data behind it. Many nodes do this; their published vectors are combined into one shared learning state. The raw material stays on the hardware that produced it, in keeping with the on-premise default that holds across the network.\n\nThe combining happens on a rhythm. The chain reaches a checkpoint on a fixed cadence, and each checkpoint is the moment the published vectors are gathered and reduced. The engine that does this work lives in the `citrate-learning` crate. It runs alongside the chain: it reads from consensus, blue scores and finalized embeddings, and it writes nothing back into transaction execution or block ordering.\n\nThe single load-bearing property is that the learning result is recorded separately from the ledger's state. At each checkpoint the engine produces a `learning_root`, a hash of the aggregated learning state. That root is independent of the `state_root`: changing one cannot change the other. We return to this below, because it is what lets learning ride alongside consensus without ever endangering it.\n\n## How to use it\n\nYou do not call the learning engine directly the way you call an RPC method; it runs inside a node at checkpoint boundaries. To exercise it yourself, the most direct path is to drive the pipeline in a short Rust program, which is exactly what the companion tutorial walks through.\n\n1. Read this page for the model, then [reproduce a learning round](/research/tutorials/reproduce-a-learning-round) to run the four phases end to end against the real crate.\n2. To understand how a checkpoint becomes a synchronization point for both blocks and learning, read [the checkpoint mechanism](/chain/consensus), where the `state_root` independence invariant (INV-4) is defined.\n3. To see how disagreement between nodes is preserved rather than averaged away, read [paraconsistent aggregation](/research/paraconsistent).\n4. To see where the published embeddings come from in production, read [the compute pool](/compute/pool) and [the operator dashboard](/apps/dashboard).\n\n## The learning cycle and its four phases\n\nA learning round moves through four phases, named in the code as the `OodaPhase` enum: Observe, Orient, Decide, Act. They are the four phases of the learning cycle, and they run once per checkpoint.\n\n| Phase | What happens | Code |\n|---|---|---|\n| Observe | Each participating node serves a reference input through its local model and submits an embedding with a per-dimension confidence. | `phases.rs::OodaPhase::Observe`, `orchestration.rs::PeerEmbedding` |\n| Orient | The submitted embeddings are combined by the dual-output aggregator into an aggregated embedding, a Belnap state vector, and a confidence. | `phases.rs::LearningPipeline::orient`, `aggregation.rs::ParaconsistentAggregator` |\n| Decide | A small multilayer-perceptron router reads the query, the aggregated embedding, and the state vector, and chooses a destination. | `phases.rs::LearningPipeline::decide`, `routing.rs::MlpRouter` |\n| Act | If the network has matured enough, a LoRA adapter is produced from the aggregated embedding; otherwise nothing is emitted. | `phases.rs::LearningPipeline::act`, `adapters.rs::AdapterFactory` |\n\nAbove this per-checkpoint cycle sits a slower, network-wide progression, the macro-phase: `Collection`, then `RoutingActive`, then `FullSystem` (`phases.rs::NetworkLearningPhase`). The network only starts routing once it has accumulated confident embeddings across enough checkpoints, and only starts producing adapters once the router's loss has settled. The Act phase produces an adapter only in `FullSystem`. The transition rule is in `MacroPhaseManager::evaluate_checkpoint`: a fixed number of consecutive checkpoints must clear a confidence threshold to advance to `RoutingActive`, then clear a loss threshold to reach `FullSystem`. `FullSystem` is terminal.\n\n## How the cycle records its result\n\nWhen a checkpoint height is reached, `LearningOrchestrator::run_checkpoint_aggregation` gathers the local embedding and the peer embeddings, drops any that fail validation, and checks quorum. If fewer than the configured minimum of valid embeddings are present, it returns a zero `learning_root` rather than an error, which matches the quorum invariant (INV-5) in the formal spec. With quorum met, it runs the aggregation and computes:\n\n```text\nlearning_root = SHA3-256( aggregated_embedding (f32 LE) || state_vector (1 byte each) || checkpoint_height (u64 LE) )\n```\n\nThe `learning_root` is a separate field on the block header (`core/consensus/src/types.rs`). The block's own hash, `Block::compute_hash`, deliberately excludes it: it hashes the header, the `state_root`, the transaction root, the receipt root, and the artifact root, and not `learning_root`. This is the independence property in concrete terms, two blocks identical except for their `learning_root` produce the same `compute_hash`, so the learning result can never alter the ledger's state or the ordering of blocks. The consensus crate labels this invariant INV-4, StateRootIndependent, and verifies it against the TLA+ spec `StrobilationCheckpoint.tla`.\n\nOne honest note on the hash. An earlier draft of this page described `learning_root` as MiMC-hashed. The code uses SHA3-256, chosen specifically to avoid a circular dependency with the execution crate's MiMC implementation; the determinism guarantee, same inputs always yield the same root, is identical either way (`orchestration.rs::compute_learning_root`).\n\n## Reference\n\nThe surface of the `citrate-learning` crate, with source paths. All paths are relative to `citrate-chain/core/learning/src/`.\n\n| Item | Kind | Source |\n|---|---|---|\n| `OodaPhase` | enum, the four phases Observe / Orient / Decide / Act | `phases.rs` |\n| `PhaseManager` | per-checkpoint phase transitions | `phases.rs` |\n| `NetworkLearningPhase`, `MacroPhaseManager` | network-wide macro-phase progression | `phases.rs` |\n| `LearningPipeline` | coordinates orient, decide, act | `phases.rs` |\n| `ParaconsistentAggregator::aggregate_paraconsistent` | dual-output aggregation | `aggregation.rs` |\n| `AggregationResult` | aggregated embedding, state vector, confidence | `aggregation.rs` |\n| `BelnapValue`, `classify_belnap`, `reduce_belnap_states` | four-valued logic, see [paraconsistent](/research/paraconsistent) | `belnap.rs` |\n| `EmbeddingVector` | a fixed-dimension vector with L2 norm and cosine similarity | `embeddings.rs` |\n| `LearningOrchestrator::run_checkpoint_aggregation` | gather, validate, aggregate, hash | `orchestration.rs` |\n| `compute_learning_root` | the SHA3-256 root | `orchestration.rs` |\n| `LearningCheckpoint` | the checkpoint record and its learning fields | `checkpoint.rs` |\n| `SafetyGuard`, `LearningMode` | enforces the state-root invariant; modes Disabled / Passive / Active | `safety.rs` |\n\nThe defaults from `config.rs`: embedding dimension 768, minimum 3 participants, confidence thresholds 0.8 and 0.3, softmax temperature 1.0, LoRA rank 16, and 3 consecutive checkpoints to advance a macro-phase.\n\n## Design rationale\n\nMost learning systems move the data to the model. For a school or a hospital that is not an option, so Citrate moves only the result of local learning, an embedding, and combines those. Tying the combining to consensus checkpoints means learning inherits the chain's safety and liveness for free, and computing a separate `learning_root` rather than folding the result into the `state_root` means a bug or a disagreement in learning can never corrupt the ledger. That separation is the price and the point: learning is a passenger on consensus, never a driver of it.\n\n## Failure modes\n\n- Below quorum, the orchestrator returns a zero `learning_root` rather than aggregating thin data, so a checkpoint with too few participants is recorded as having learned nothing rather than something unreliable.\n- Embeddings with the wrong dimension, with non-finite values, with a confidence vector of the wrong length, or with a negative blue score are filtered out before aggregation and logged (`orchestration.rs::validate_embeddings`).\n- The `SafetyGuard` keeps learning in one of three modes and audits every transition; in `Disabled` no embeddings are collected, so a node can run consensus with learning fully off and produce a bit-identical `state_root`.\n\n## Access and canon\n\nAcademic tier. The learning engine is a research contribution and its on-chain orchestration is not yet a finished product surface, which is why this page sits here rather than under a public surface. No keys, endpoints, or credentials appear on this page. The on-premise default holds: a node publishes embeddings only when its operator has chosen to take part, and identity on the public network is verified through VERI, Citrate's in-house verification.\n\n## Source and verification\n\n- Source: `citrate-chain/core/learning/`, audited against SHA `e68af83`.\n- Key files: `phases.rs` (the four phases, macro-phase, pipeline), `aggregation.rs` (dual-output aggregation), `belnap.rs` (four-valued logic), `orchestration.rs` (`LearningOrchestrator`, `compute_learning_root`), `checkpoint.rs` (`LearningCheckpoint`), `safety.rs` (`SafetyGuard`), `embeddings.rs`.\n- The block header `learning_root` field and its exclusion from `Block::compute_hash` are in `core/consensus/src/types.rs`; the invariant is INV-4 (StateRootIndependent) against `specs/tla/StrobilationCheckpoint.tla`.\n- Status by surface. The `citrate-learning` crate is Implemented (pre-audit), with unit, property, and integration tests across the four phases, the four-valued lattice laws, and the safety invariant. The on-chain wiring, the orchestrator driven by a live block producer and federated rounds on testnet 40204, is Specified, not yet a production feature. Treat this page as documenting a real, tested engine whose chain integration is in progress.\n"},"/research/mentorship":{"slug":"/research/mentorship","title":"The Mentorship Protocol","tier":"public","orgId":null,"sourceKind":"linked","source":"citrate-docs/gradient_papers_v3/Gradient_Papers_No3_Mentorship_Protocol_v3.md","syncedSha":"03d7851","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The mentorship protocol is how a weaker node in the Citrate Orchard finds a stronger one to learn from.\nAt each checkpoint the network pairs nodes by their measured strengths and weaknesses, the stronger node\nproduces a small update that moves the weaker one toward it, and the whole exchange is recorded. This\npage is for researchers and operators; it documents the matching code that runs today and summarizes\nGradient Paper III for the protocol design around it.\n\n## What it is\n\nThink of the Orchard as a grove where some trees fruit well in one season and poorly in another. Rather\nthan let each tree learn alone, the network grafts: a node strong in some region of the model's behavior\nlends a weaker node an adapter, a small set of weights that nudges the weaker node's representation\ntoward the stronger one's. The paper's argument is that distributed model swarms fail the way human\norganizations fail, when knowledge transfer is implicit, unrecorded, and one-directional, so Citrate\nmakes each transfer an explicit, recorded action.\n\nTwo layers are worth keeping separate. The matching layer, which decides who learns from whom, runs in\nthe node software today. The fuller protocol around it, the trust gating and pricing and dispute handling\nthe paper describes, is partly on the ledger and partly still specified. The sections below say which is\nwhich.\n\n## How to use it\n\nMatching is not something an operator invokes by hand; it runs inside the learning cycle. The path it\ntakes each checkpoint is:\n\n1. Each participant carries a performance profile, its measured accuracy and the domains it works in.\n2. Participants are sorted by accuracy. The stronger half are candidate mentors, the weaker half candidate mentees.\n3. Each mentee is matched to the mentor with the highest complementarity that still has spare capacity, where complementarity rewards a wide accuracy gap and shared working domains.\n4. The chosen mentor produces a delta adapter, the element-wise difference between its embedding and the mentee's, which applied to the mentee moves its representation toward the mentor's.\n5. The adapter is wrapped with provenance and a content hash so the exchange can be checked later.\n\nTo follow the surrounding surfaces, read [federated learning cycles](/research/learning) for where\nmatching sits in the cycle, and [model contracts](/contracts/models) for the LoRAFactory registry that\nrecords adapters on the ledger.\n\n## Reference\n\nThe matching surface, anchored in `citrate-chain` at `03d7851`.\n\n| Surface | Where | What it does |\n|---|---|---|\n| `select_mentors` | `core/learning/src/mentor.rs` | Sorts participants by accuracy, splits into mentor and mentee halves, and pairs each mentee to the best uncapped mentor. |\n| `MentorPairing` | `core/learning/src/mentor.rs` | The record of one pairing: mentor, mentee, complementarity score, both accuracies, and shared domains. |\n| `generate_delta_adapter` | `core/learning/src/mentor.rs` | Computes the mentor-minus-mentee embedding delta, rejecting mismatched dimensions or non-finite values. |\n| `generate_adapter_for_mentee` | `core/learning/src/mentor.rs` | Wraps the delta in a `LearningAdapter` with metadata, provenance, and hash, ready to broadcast. |\n| `validate_pairing` | `core/learning/src/mentor.rs` | A pure predicate mirroring the `MentorMatcher.sol` contract check, in Q16.16 fixed point so the node and the contract agree exactly. |\n\nThe constants that bound matching are explicit in the code: `MIN_ACCURACY_GAP` is `0.05`, so a mentor\nmust be at least five points more accurate than its mentee, and `MAX_MENTEES_PER_MENTOR` is `3`, so no\nmentor can take more than three mentees in a cycle. The complementarity score is\n`max(1, shared_domains) * accuracy_gap`, which keeps zero-overlap pairs scorable while rewarding shared\nground. The `validate_pairing` helper enforces the on-ledger gate in lockstep with the contract, and its\nvariant order is load-bearing because it ABI-decodes from the Solidity enum:\n\n```rust\npub enum PairingValidity {\n Ok = 0,\n SelfMentor = 1,\n MentorBelowTrustFloor = 2,\n AccuracyGapTooSmall = 3,\n MentorAtCapacity = 4,\n MenteeAlreadyAssigned = 5,\n}\n```\n\nThe paper adds the protocol layer the matching code rides on. The `ContributionAccounting` contract\nweights seven contribution types, with adapter creation weighted highest at 2.0, so the candidate pool is\nnodes that have actually produced useful adapters. A node's standing in consensus, its blue score from\nGhostDAG, is reused as a necessary trust floor: a node that cannot keep up with consensus is an unlikely\nsource of good adapters, though a high blue score alone does not earn mentor standing. A first-time\nmentee can require a Halo2-KZG proof of adapter quality before integrating, described under\n[verifiable inference](/research/verifiable-inference). Fees are calibrated by a pricing oracle so\nmentoring is paid but not rent-extracting.\n\n## Design rationale\n\nThe matching code is deliberately fixed point, not floating point, on its on-ledger path. Float results\nare not guaranteed identical across processors, and the node and the contract must agree on whether a\npairing is valid; the Q16.16 representation in `validate_pairing` makes the two implementations produce\nthe same answer over the full input space, which the property tests in the file pin. The accuracy-gap\nfloor and the per-mentor capacity cap are the smallest set of rules that prevent the obvious failures: a\nnode mentoring itself, a node with no standing posing as a mentor, a pairing with no real gap to learn\nacross, and one strong node saturating all demand. The paper's wider counters, an open adapter registry\nso any mentee can use a published adapter, per-checkpoint rotation, usage-weighted scoring so spam\nadapters earn nothing, and slashing for adapters later proven adversarial, address mentor capture and\nadapter pollution at the protocol level.\n\n## Access and canon\n\nAcademic tier. The mentorship surface runs inside the on-premise node software in the Citrate Orchard;\nmatching operates over performance profiles and embeddings that stay on the operator's hardware, and only\nthe adapter and its provenance are published when an operator chooses to. Contract addresses cited in the\npaper are public; no keys or credentials appear here.\n\n## Source and verification\n\n- Paper, linked, not copied: `citrate-docs/gradient_papers_v3/Gradient_Papers_No3_Mentorship_Protocol_v3.md`.\n- Code anchor, citrate-chain at `03d7851`: `core/learning/src/mentor.rs` for matching, delta-adapter\n generation, and the `validate_pairing` mirror; surrounding surfaces in `core/learning/src/adapters.rs`,\n `contracts/src/ContributionAccounting.sol`, `contracts/src/LoRAFactory.sol`, and `contracts/src/MentorMatcher.sol`.\n- Status: Implemented for matching. `select_mentors`, the delta-adapter pipeline, and the\n `validate_pairing` predicate exist, run, and are covered by unit and property tests in `mentor.rs`; this\n is pre-audit. Specified for the full distillation pipeline: the wider protocol the paper describes,\n blue-score trust gating, on-ledger per-cycle assignment, priced mentoring, and the application-layer\n proof-of-quality flow, is designed and partly built but not yet shipped end to end. The delta adapter\n in the code is a real update vector, not the full low-rank LoRA decomposition the paper envisions.\n- Related: [federated learning cycles](/research/learning), [model contracts](/contracts/models),\n [paraconsistent consensus](/research/paraconsistent), [verifiable inference](/research/verifiable-inference),\n [the Gradient Papers](/research/gradient-papers).\n"},"/research/paraconsistent":{"slug":"/research/paraconsistent","title":"Paraconsistent aggregation, Belnap four-valued logic","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/core/learning/src/belnap.rs","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"The four values","anchor":"the-four-values"},{"depth":2,"text":"How the implementation works","anchor":"how-the-implementation-works"},{"depth":2,"text":"How it maps to the network","anchor":"how-it-maps-to-the-network"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"When nodes in Citrate Orchard disagree about what a model should have learned, the network records the disagreement as information rather than averaging it into a single answer nobody holds. It does this with Belnap's four-valued logic. This page documents the implementation and is written for researchers.\n\n## What it is\n\nClassical agreement treats a difference of opinion as a fault to be resolved: honest parties are expected to converge on one value. Paraconsistent aggregation declines that frame. If one node's data says a given dimension of an embedding should be strongly positive and another node's says it should be strongly negative, the mean sits near zero, a value that is right for neither and that quietly erases the fact that the two nodes saw different worlds. That difference is often the most useful thing in the data: it is what distinguishes a personalized model, a regional dialect, or a domain specialist from a generic one.\n\nSo the aggregator produces two outputs per dimension, computed independently. One is a numeric value, a confidence-and-trust-weighted mean over the consenting sources. The other is a Belnap state, a label that says whether the sources agreed, disagreed, or said nothing. The router downstream reads both, so a dimension marked as contradictory can be sent to several sources for cross-validation instead of being trusted as a fictional average.\n\n## The four values\n\nNuel Belnap's logic admits four truth values. In `belnap.rs` they are the variants of `BelnapValue`, and each carries a meaning for the network.\n\n| Value | In code | Reading | Network meaning |\n|---|---|---|---|\n| True | `BelnapValue::True` | known true | the trusted sources agree this dimension is positive |\n| False | `BelnapValue::False` | known false | the trusted sources agree it is negative |\n| Both | `BelnapValue::Both` | true and false at once | sources of comparable trust genuinely disagree |\n| Neither | `BelnapValue::Neither` | no information | no source spoke with enough confidence |\n\nA mean collapses Both and Neither into the True/False continuum and loses them. The four-valued reduction keeps them. A `Both` is the signal that a dimension is contested; a `Neither` is the signal that it is simply unknown.\n\n## How the implementation works\n\nThe four values form a bilattice with two orderings. `belnap.rs` implements both: a knowledge ordering, where Neither sits below True and False, which sit below Both, and a truth ordering, where False sits below Neither and Both, which sit below True. The operations are `join` (combine information), `meet` (keep only what both inputs agree on), and `negation` (swap True and False, leave Both and Neither unchanged). Property tests check that join and meet are commutative, associative, idempotent, satisfy absorption, and that negation is its own inverse.\n\nAggregation runs in three steps inside `ParaconsistentAggregator::aggregate_paraconsistent` (`aggregation.rs`):\n\n1. Trust weights come from consensus. Each source's blue score is turned into a softmax weight, `softmax(blue_score / temperature)`, so a node that cannot keep up with consensus carries little weight in learning (`belnap.rs::softmax_weights`, `blue_scores_to_trust_weights`).\n2. Each source is classified per dimension by the function `classify_belnap`. A source above the high-confidence threshold that agrees with the trust-weighted majority is True; one that disagrees alone is False; one that disagrees but has a comparably trusted ally on its side is Both; one below the threshold is Neither.\n3. The per-source classifications are reduced to one state vector by joining across sources (`reduce_belnap_states`). If any source is Both, or sources split True against False, the result is Both. If all agree, it is True. Neither is absorbed by any other value.\n\nThe numeric embedding is computed separately, as a weighted mean using each source's trust weight times its per-dimension confidence. The two outputs, the embedding and the state vector, never read each other, which is what lets a dimension be numerically near zero and still be labelled Both.\n\n## How it maps to the network\n\n- The aggregation is checkpoint-aligned. Validators co-sign learning roots at the checkpoint barrier rather than per block, so learning safety inherits from the chain's safety and learning liveness from its liveness. See [the checkpoint mechanism](/chain/consensus).\n- The state vector is carried into [the learning cycle](/research/learning): the router reads it, and the macro-phase progression uses the aggregation's confidence.\n- A verifiable, in-circuit form of this aggregation is proposed but not built. The plan is a Belnap reduction over a fixed-point representation so the result is bit-deterministic and can be proved, which would let the aggregation be checked rather than trusted. See [zero-knowledge precompiles](/chain/precompiles-zkp) and [verifiable inference](/research/verifiable-inference).\n\n## Reference\n\n| Item | Kind | Source |\n|---|---|---|\n| `BelnapValue` | the four values | `core/learning/src/belnap.rs` |\n| `join`, `meet`, `negation` | lattice operations | `core/learning/src/belnap.rs` |\n| `k_leq`, `t_leq` | knowledge and truth orderings | `core/learning/src/belnap.rs` |\n| `softmax_weights`, `blue_scores_to_trust_weights` | trust weights from blue scores | `core/learning/src/belnap.rs` |\n| `classify_belnap` | per-source per-dimension classification | `core/learning/src/belnap.rs` |\n| `reduce_belnap_states` | reduce sources to one state vector | `core/learning/src/belnap.rs` |\n| `ParaconsistentAggregator::aggregate_paraconsistent` | the dual-output aggregation | `core/learning/src/aggregation.rs` |\n\n## Design rationale\n\nAveraging is cheap and almost always wrong when the inputs come from different distributions. Treating disagreement as information costs a richer representation, a state vector alongside the numbers, and a router that knows how to read it. The return is that the network can tell the difference between a dimension everyone agrees on, one that is genuinely contested, and one nobody has an opinion on, and it can act differently in each case. The two outputs are kept independent so that the label is never quietly derived from the number it is meant to qualify.\n\n## Access and canon\n\nAcademic tier. No keys, endpoints, or credentials appear here. The logic is a research contribution; the in-circuit precompile that would make the aggregation verifiable is a design direction, labelled below.\n\n## Source and verification\n\n- Source: `citrate-chain/core/learning/src/belnap.rs` and `aggregation.rs`, audited against SHA `e68af83`. Adversarial tests in `core/learning/tests/belnap_adversarial.rs`.\n- Status by surface. The Belnap lattice, the classification function, the reduction, and the dual-output aggregator are Implemented (pre-audit), with property tests for the lattice laws. The fixed-point, in-circuit aggregation precompile and the proof tie-in are Specified, not yet built.\n- Related: [Citrate Orchard, federated learning cycles](/research/learning), [zero-knowledge precompiles](/chain/precompiles-zkp), [verifiable inference](/research/verifiable-inference).\n"},"/research/tla":{"slug":"/research/tla","title":"The TLA+ formal specification corpus","tier":"public","orgId":null,"sourceKind":"linked","source":"citrate-agentile-archive/formal/specs/ + per-repo specs/tla/","syncedSha":"f28358f","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This is the machine-checked half of how Citrate establishes that its protocol is correct: a body of TLA+\nspecifications, each stating the safety properties of one state machine and checked with the TLC model\nchecker. It is for researchers and reviewers auditing protocol correctness. We link the corpus here; we do\nnot copy specs into the docs.\n\n## What it is\n\nA TLA+ specification is the source of truth for a state machine. It declares the legal states and the legal\ntransitions, then asserts invariants that must hold no matter how the machine moves, for example that no\nblock finalizes without a valid quorum. The TLC model checker explores the reachable state space and, if an\ninvariant can be broken, returns the exact sequence of steps that breaks it. This catches a design error\nbefore it becomes code, which is cheaper than catching it after.\n\nCitrate keeps a large corpus of these specifications, organized by domain. The public, verifiable figure is\n100+ TLA+ specifications across the public repositories (citrate-chain, nat, nist-agent, coop, core,\nmemories, comms, explorer, and others), 102 today. Anyone can reproduce that number by cloning those repos\nand running `find -name '*.tla' | wc -l`; the specs sit under each repo's `specs/tla/` (or `formal/`,\n`specs/`) tree so they can be exercised in continuous integration next to the code they constrain. The\nfuller authored corpus (roughly 169 specs, with its consolidated index and the spec-to-code mapping,\n`tla_to_solidity.md` and `tla_to_slint.md`) lives in an internal archive that is not public, so it is not the\nnumber to cite publicly; count the specs in the public repos instead. For invariant totals and TLC outcomes,\nread each repo's `INDEX.md` and `VERIFICATION_REPORT.txt`, not older summary files.\n\n## How to use it\n\nThe specs are checked with TLC, which needs Java and `tla2tools.jar`. The convention across repos is a\n`run_all.sh` driver in `specs/tla/` that auto-downloads the checker if it is missing and runs every\n`.tla` that has a matching `.cfg` in the domain subdirectories.\n\n```bash\n# Run the local runnable subset (one config per spec, four workers)\ncd specs/tla && bash run_all.sh\n\n# Deep verification (more workers, long timeout), run on demand\ncd specs/tla && bash run_deep.sh\n\n# A single spec\njava -jar tla2tools.jar -config consensus/GhostDAGConsensus.cfg \\\n consensus/GhostDAGConsensus.tla\n```\n\n`run_all.sh` runs the standard sweep, one `.cfg` per spec. Additional parameter configs (for example\n`_medium.cfg` or `_liveness.cfg`) are deep verifications driven by `run_deep.sh` on demand, not\nby the standard sweep. The rule for when a spec is required lives in `FORMAL_VERIFICATION_RULES.md`: it must\nbe written for any change to consensus, finality, or proposer election; it should be written for\nstate-machine or economic-rule changes and new protocol flows; it may be written for complex data-structure\nor interface-state invariants.\n\n## Reference\n\nThe corpus is grouped by domain. The table below names real specifications you will find in the canonical\ntree; `INDEX.md` carries the full per-domain list and invariant counts.\n\n| Domain | What it constrains | Representative specs |\n|---|---|---|\n| consensus | Blue-set ordering, VRF proposer election, finality, the concurrent executor | `GhostDAGConsensus.tla`, `VRFElection.tla`, `VRFChainContinuity.tla`, `PrevrandaoPipeline.tla`, `ExecutorMVCC.tla`, `GhostDAGAuditAnchor.tla` |\n| zk and halo2 | Proof lifecycle, verifying-key management, verifier version monotonicity | `ZKProofLifecycle.tla`, `ZKKeyManagement.tla`, `Halo2VerifierVersionMonotonic.tla` |\n| learning | OODA cycle, adapter provenance, paraconsistent aggregation, mentor selection, checkpointing | `OODACycle.tla`, `AdapterProvenance.tla`, `ParaconsistentAggregation.tla`, `BelnapLattice.tla`, `MentorSelection.tla`, `StrobilationCheckpoint.tla` |\n| contracts | Trust scoring, the spec registry, inference-request lifecycle, role-escalation grants | `TrustScoring.tla`, `SpecRegistryLifecycle.tla`, `InferenceRequestLifecycle.tla`, `RoleEscalationGrant.tla` |\n| compute | Settlement, batch-inference escrow, data and pipeline parallel jobs, disputes | `X402FacilitatorSettle.tla`, `GatewayBatchLifecycle.tla`, `DataParallelTrainingJob.tla`, `DisputeResolution.tla` |\n| agent | Approval, break-glass, capability grants, emergency stop, append-only trails | `ApprovalStateMachine.tla`, `BreakGlass.tla`, `CapabilityGrantLifecycle.tla`, `EmergencyStopProtocol.tla`, `TrailAppendOnly.tla` |\n| gui | Desktop state machines: onboarding, account session, send and deploy flows, role-escalation timer | `OnboardingStateMachine.tla`, `WalletSessionLifecycle.tla`, `SendTransactionFlow.tla`, `ContractDeploymentFlow.tla`, `RoleEscalationTimer.tla` |\n| network | Peer handshake, block sync, mempool gossip and routing | `P2PPeerHandshake.tla`, `BlockSyncProtocol.tla`, `MempoolGossipProtocol.tla` |\n| iot | Inter-organizational envelope transfer, sub-secret derivation | `InterOrgEnvelopeChain.tla`, `HKDFSubSecretDerivation.tla` |\n| account | Key lifecycle, signing, recovery safety, session limits | `WalletKeyLifecycle.tla`, `TransactionSigningFlow.tla`, `MnemonicRecoverySafety.tla`, `SessionRateLimiting.tla` |\n\nThe archive also carries `legacy-gui` and `audit-archive` trees, historical specs from the 2026-03 security\ndeep audit, which are excluded from the authored count.\n\nPer-repo runnable subsets sit next to the code they govern:\n\n- `citrate-agentile-archive/formal/specs//`, the fuller authored corpus in a private internal\n archive (not public, so not the publicly countable figure).\n- `citrate-chain/specs/tla/{consensus,zk,learning,contracts,compute,gui,network}/`, with `run_all.sh`,\n `run_deep.sh`, and `VERIFICATION_REPORT.txt`; the chain README notes this is a runnable subset, not the\n authority for counts.\n- `citrate-explorer/specs/tla/` (for example `SelectedParentReconcile.tla`).\n- `citrate-memories/specs/` (`Authz.tla`, `Ingestion.tla`, `SupersededDag.tla`, with `check.sh`).\n- `citrate-comms/formal/` (`AuditChainIntegrity.tla`, `RelayCommitOrder.tla`).\n\nThe spec registry that records which spec governs which surface is documented under\n[governance](/contracts/governance); the methodology that says when to write a spec is in the\n[workflow](/methodology/workflow). The behavioral counterpart, what the system does rather than what states\nit may occupy, is the [BDD library](/research/bdd).\n\n## Design rationale\n\nWe separate two questions on purpose. A TLA+ spec answers \"can this state machine ever reach a bad state\",\nwhich a model checker can decide by exhaustive search of an abstract model. A behavior test answers \"does\nthe running code do the right thing on this input\". Keeping the abstract model in TLA+ lets us find ordering\nand concurrency bugs, the ones that hide between valid steps, before any code exists, and the spec-to-code\nmapping keeps the model honest about what it actually constrains. The cost is that a spec is an abstraction\nand can drift from the code; the mapping files and the per-repo runnable subsets exist to keep that drift\nvisible.\n\n## Access and canon\n\nAcademic tier. The specifications are abstract state machines; no keys, hostnames, or credentials appear in\nthe corpus or on this page. We link the specs and their indices rather than copy them, so the `.tla` and\n`.cfg` files and the TLC run artifacts remain the truth in their repositories.\n\n## Source and verification\n\n- Source: `citrate-agentile-archive/formal/specs/` (canonical) plus the per-repo `specs/tla/` runnable\n subsets named above.\n- Audited against SHA: `f28358f` (citrate-agentile-archive); per-repo subsets pinned at each repo's HEAD,\n for example citrate-chain at `e68af83`.\n- Status: a subset of specs carry recorded TLC runs - for example `ExecutorMVCC.tla` (checked at Small,\n Liveness, and Medium configurations, with a deep run reported clean) and `Halo2VerifierVersionMonotonic.tla`\n (four invariants). These are **bounded** model checks over abstract, finite configurations, not exhaustive\n proofs over the unbounded system, and the largest specs (the `cit-agent` domain) are checked at bounded\n parameters only. The corpus as a whole is Specified and being checked spec by spec; do not read a global\n \"N verified\" figure into it - consult `INDEX.md` and each repo's `VERIFICATION_REPORT.txt` for the current,\n reproducible outcome of any one spec.\n"},"/research/tutorials/reproduce-a-learning-round":{"slug":"/research/tutorials/reproduce-a-learning-round","title":"Reproduce a learning round","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/core/learning/","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Step 1, build and run the learning suite","anchor":"step-1-build-and-run-the-learning-suite"},{"depth":3,"text":"Step 2, run one round yourself","anchor":"step-2-run-one-round-yourself"},{"depth":3,"text":"Step 3, read what happened","anchor":"step-3-read-what-happened"},{"depth":3,"text":"Step 4, see the learning root","anchor":"step-4-see-the-learning-root"},{"depth":3,"text":"Step 5, see the safety invariant","anchor":"step-5-see-the-safety-invariant"},{"depth":3,"text":"Step 6, the on-chain path, Specified, not yet wired","anchor":"step-6-the-on-chain-path-specified-not-yet-wired"},{"depth":2,"text":"What you reproduced","anchor":"what-you-reproduced"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"A walk-through of one complete learning round, run locally against the real `citrate-learning` crate, so you can watch the four phases produce an aggregated embedding, a Belnap state vector, and a deterministic learning root. For researchers. The crate-level steps run today; the on-chain steps are marked where they are not yet wired.\n\n## What it is\n\nThe learning engine that ships in `citrate-chain` is exercised by the crate's own tests, so you can reproduce a round without a running node. You will build the crate, run its end-to-end suite, then write one short test that walks the four phases by hand and prints what each produces. The concepts map one to one to [Citrate Orchard](/research/learning) and [paraconsistent aggregation](/research/paraconsistent).\n\n## How to use it\n\nYou need a Rust toolchain (`rustup`, stable; `cargo --version` should work), a checkout of `citrate-chain` at SHA `e68af83` or later, and about five minutes.\n\n### Step 1, build and run the learning suite\n\nThe crate already contains the full pipeline as tests. Confirm it builds and the Observe-through-Act path passes.\n\n```bash\ncd citrate-chain\ncargo test -p citrate-learning\n```\n\nThe rounds that matter live in these tests:\n\n- `core/learning/tests/e2e_ooda_pipeline.rs`, a full cycle across three participants covering aggregation, Belnap classification, routing, LoRA, safety, and Byzantine detection.\n- `core/learning/tests/belnap_adversarial.rs`, disagreement handling.\n- `core/learning/tests/lora_provenance.rs`, the adapter provenance chain.\n\nTo run just the end-to-end cycle:\n\n```bash\ncargo test -p citrate-learning --test e2e_ooda_pipeline\n```\n\n### Step 2, run one round yourself\n\nAdd the following as `core/learning/tests/my_round.rs`. The API is taken directly from `core/learning/src/phases.rs`. Three participants submit embeddings; participant three disagrees on dimension 0.\n\n```rust\nuse citrate_learning::aggregation::AggregationInput;\nuse citrate_learning::config::LearningConfig;\nuse citrate_learning::embeddings::EmbeddingVector;\nuse citrate_learning::phases::{LearningPipeline, MacroPhaseManager};\n\n#[test]\nfn reproduce_a_learning_round() {\n let dim = 4;\n\n // Tiny config so the round is fast. One good checkpoint reaches FullSystem.\n let config = LearningConfig {\n embedding_dimensions: dim,\n lora_rank: 2,\n macro_confidence_threshold: 0.5,\n macro_loss_threshold: 0.5,\n macro_consecutive_checkpoints: 1,\n ..LearningConfig::default()\n };\n\n let mut pipeline = LearningPipeline::new(&config);\n let mut macro_mgr = MacroPhaseManager::new(config.clone());\n\n // --- Observe: three participants each submit an embedding plus confidence ---\n let e1 = EmbeddingVector::new(vec![0.9, 0.8, 0.7, 0.6]).expect(\"e1\");\n let e2 = EmbeddingVector::new(vec![0.85, 0.75, 0.65, 0.55]).expect(\"e2\");\n let e3 = EmbeddingVector::new(vec![-0.8, 0.7, 0.66, 0.50]).expect(\"e3\"); // dim 0 disagrees\n let conf = vec![0.9; dim];\n let query = EmbeddingVector::new(vec![0.5; dim]).expect(\"query\");\n\n let input = AggregationInput {\n embeddings: &[&e1, &e2, &e3],\n confidences: &[&conf, &conf, &conf],\n blue_scores: &[1.0, 1.0, 1.0], // trust weights from consensus\n temperature: 1.0,\n theta_high: 0.8,\n theta_low: 0.3,\n };\n\n // --- Orient: dual output, aggregated embedding plus Belnap state vector ---\n let agg = pipeline.orient(&input).expect(\"aggregate\");\n println!(\"aggregated embedding: {:?}\", agg.embedding);\n println!(\"Belnap state vector : {:?}\", agg.state_vector); // expect Both on dim 0\n\n // --- Decide: the router reads the state vector, not just the mean ---\n let decision = pipeline.decide(&query, &agg).expect(\"route\");\n println!(\"routed to destination: {}\", decision.selected);\n\n // --- Act: advance the macro-phase to FullSystem, then produce an adapter ---\n macro_mgr.evaluate_checkpoint(0.8, Some(0.2)); // drive toward FullSystem\n let result = pipeline\n .execute_cycle(&query, &input, macro_mgr.can_adapt(), [1u8; 32], 100)\n .expect(\"cycle\");\n\n if let Some(adapter) = result.adapter {\n println!(\"LoRA adapter dim={} rank={}\", adapter.dim, adapter.rank);\n assert_eq!(adapter.dim, dim);\n }\n}\n```\n\nRun it:\n\n```bash\ncargo test -p citrate-learning --test my_round -- --nocapture\n```\n\n### Step 3, read what happened\n\n- Dimension 0 had one strongly negative contributor against two positive ones, so its Belnap state resolves to `Both`. The network records the disagreement instead of averaging it to a misleading near-zero. This is the point of [paraconsistent aggregation](/research/paraconsistent).\n- The router received the query, the aggregated embedding, and the state vector together, so it can send a contested dimension to several destinations rather than trusting a fictional mean.\n- The adapter is produced only once `macro_mgr.can_adapt()` is true, which is the `FullSystem` macro-phase. In `Collection` or `RoutingActive` the Act phase produces no adapter; pass `false` to `execute_cycle` to confirm.\n\n### Step 4, see the learning root\n\nThe orchestrator is what a checkpoint actually calls. Add this to the same file to see the deterministic `learning_root` and confirm it is stable across runs.\n\n```rust\nuse citrate_learning::orchestration::{compute_learning_root};\nuse citrate_learning::belnap::BelnapValue;\n\n#[test]\nfn learning_root_is_deterministic() {\n let embedding = vec![0.1f32, 0.2, 0.3, 0.4];\n let state = vec![\n BelnapValue::True,\n BelnapValue::Neither,\n BelnapValue::Both,\n BelnapValue::False,\n ];\n let root_a = compute_learning_root(&embedding, &state, 100);\n let root_b = compute_learning_root(&embedding, &state, 100);\n assert_eq!(root_a, root_b); // same inputs, same root (INV-2)\n assert_ne!(root_a, [0u8; 32]); // and non-trivial\n}\n```\n\nThe root is `SHA3-256(aggregated_embedding || state_vector || checkpoint_height)` (`core/learning/src/orchestration.rs::compute_learning_root`). It is the value a node would place in the block header's `learning_root` field, which is independent of the `state_root`; see [Citrate Orchard](/research/learning) for that invariant.\n\n### Step 5, see the safety invariant\n\nA learning round must never change execution state. The `SafetyGuard` (`core/learning/src/safety.rs`) enforces that the `state_root` is identical whether learning is on or off. The crate's safety tests check it:\n\n```bash\ncargo test -p citrate-learning safety\n```\n\n### Step 6, the on-chain path, Specified, not yet wired\n\nIn production the embeddings are not hand-written; they are gossiped from finalized blocks, and the round is driven by the block producer at a checkpoint height. That wiring is Specified, not yet a running feature, and the relevant surfaces are honest about it:\n\n- The chain id is 40204 (`0x9d0c`); confirm with `eth_chainId`, see [the JSON-RPC reference](/chain/rpc).\n- `citrate_getTrainingJob` reads a job from storage by id. `citrate_createTrainingJob` is present but returns a placeholder today (its handler responds with \"Training job creation not fully implemented yet\" in `core/api/src/server.rs`), so do not expect it to enqueue real work yet.\n- The on-chain learning cycle contract `AILearningCycleCorePortable` in `contracts/src/edu/ai-gateway/` models the same shape on chain, `openCycle`, `joinCycle`, `startCollecting`, `submitCommitment`, `startAggregating`, `recordAdapter`, `finalizeCycle`, and is the intended home for the cycle state once the node wiring lands.\n\n## What you reproduced\n\nYou ran the four phases a live checkpoint runs, Observe, Orient with paraconsistent dual output, Decide, and Act, and you computed the deterministic `learning_root` the same way the orchestrator does. The difference from a live network is the source of the embeddings, hand-written here against gossiped on chain, and the node wiring, which is the integration frontier described on [Citrate Orchard](/research/learning).\n\n## Source and verification\n\n- Engine: `citrate-chain/core/learning/` at SHA `e68af83`. Pipeline API in `src/phases.rs`; aggregation in `src/aggregation.rs`; four-valued logic in `src/belnap.rs`; the root in `src/orchestration.rs`.\n- Reference tests: `tests/e2e_ooda_pipeline.rs`, `tests/belnap_adversarial.rs`, `tests/lora_provenance.rs`.\n- On-chain surfaces named above: `core/api/src/server.rs` (`citrate_createTrainingJob`, `citrate_getTrainingJob`) and `contracts/src/edu/ai-gateway/AILearningCycleCorePortable.sol`.\n- Status by surface. The crate-level round (Steps 1 through 5) is Implemented (pre-audit) and runs as shown. The on-chain path (Step 6) is Specified, with `citrate_getTrainingJob` and the cycle contract present and `citrate_createTrainingJob` not yet functional.\n- No keys, endpoints, or credentials appear in this tutorial.\n- Related: [Citrate Orchard](/research/learning), [paraconsistent aggregation](/research/paraconsistent), [education contracts](/contracts/edu), [JSON-RPC reference](/chain/rpc).\n"},"/research/verifiable-inference":{"slug":"/research/verifiable-inference","title":"The substrate of verifiable inference","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain/core/execution/src/precompiles/{verify.rs,inference.rs}","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"This is the research-angle account of how a contract on the Citrate Network can trust the result of an AI\ncomputation it did not run. It is a summary for researchers and reviewers; the proving internals that make\nit work are public in the `citrate-chain` repository and are linked rather than reproduced here.\n\n## What it is\n\nA model that runs off-chain produces a number, and a contract on-chain wants to act on that number without\npaying to run the model itself. The problem is trust: the contract has no reason to believe the result\nunless it can check it. Citrate answers this with a small set of precompiles that let the chain verify a\nclaim about off-chain work rather than repeat it, and it rests on three building blocks we can name in the\nopen.\n\nThe first is determinism. Floating-point math gives different answers on different hardware, depending on\nrounding modes and instruction ordering, so it cannot be the basis of a result every node must agree on.\nCitrate uses Q16.16 fixed-point arithmetic instead: a number is a 32-bit integer read as 16 integer bits\nand 16 fractional bits, computed with saturating integer operations. The same input gives the same bytes\non every machine, which is what makes a computation reproducible and therefore checkable.\n\nThe second is proof verification. An inference proof is checked by a Halo2-KZG verifier, a pairing-based\nsystem that lets the chain confirm a proof in one verification step instead of re-running the computation\nthe proof stands for. The contract sees a yes or no, not the work behind it.\n\nThe third is attestation. Some computations, a large language model on a GPU, cannot be made bit-identical\nand so cannot be proven this way. For those, the chain consults a hardware-attestation gate that decides\nwhether a non-deterministic path is allowed to run at all. Today that gate refuses by default; see the\nstatus below.\n\nThese three building blocks back the determinism that [paraconsistent consensus](/research/paraconsistent)\nand the [learning cycle](/research/learning) depend on, and they are summarized for builders on the chain\npage, [verification, inference, and attestation precompiles](/chain/precompiles-zkp). This page is the companion to that one and does\nnot contradict it.\n\n## How to use it\n\nYou meet this substrate through precompile addresses, the same way you would call any contract on the chain.\n\n1. To commit to a tensor, call `0x0107`. It returns a 32-byte field element that binds the tensor's data\n and its shape, so two payloads with the same bytes but different shapes commit differently.\n2. To check an inference proof, call `0x0108`. It returns a 32-byte boolean, one for valid and zero for\n invalid.\n3. To check that a single element belongs to a committed tensor, call `0x0109` with a Merkle path. It\n returns a 32-byte boolean.\n4. To run inference itself, call into the hosted-inference family, `0x0100` to `0x0106`. This path is\n model-runtime-backed and returns a signed receipt over the result, gated by hardware attestation - an\n attestable statement about what ran, not a cryptographic proof that the output is correct. The\n non-deterministic paths, `0x0101` and `0x0102`, first consult the attestation gate, which on a default\n validator binary refuses them and returns a discoverable error rather than a fabricated result. For a\n result that is verifiable on-chain, verify a proof through `0x0108` instead.\n\n## Reference\n\nThe verification surface, named from the precompiles that implement it. The deterministic verification\nfamily verifies claims; the inference family runs and registers models.\n\n| Address | Name | What it does |\n|---|---|---|\n| `0x0107` | `TENSOR_COMMIT` | Commitment over a canonical-format tensor; returns a 32-byte field element |\n| `0x0108` | `INFERENCE_PROOF_VERIFY` | Halo2-KZG verification of an inference proof; returns a 32-byte boolean |\n| `0x0109` | `MERKLE_VERIFY_TENSOR` | Merkle inclusion check over a committed tensor; returns a 32-byte boolean |\n| `0x0100` to `0x0106` | hosted inference family | Model deployment, single and batch inference, metadata, benchmarking, model encryption; returns a signed, attestation-gated receipt, not a proof of correctness |\n\nThe verification family at `0x0107` to `0x0109` is deterministic by construction, hash and pairing and\ninteger math only, and its byte-level output is frozen: any drift would fork the chain and invalidate every\nprior commitment. The compute family at `0x010A` to `0x010F`, six Q16.16 tensor primitives (matmul, dot,\nsoftmax, relu, linear, transpose), gives the in-circuit math its deterministic floor.\n\n## Design rationale\n\nThe whole design follows from one decision: check a proof instead of repeating the work. That is only sound\nif every step is reproducible, which is why commitments and proofs sit on deterministic primitives and why\nthe result bytes are frozen rather than versioned in place. The one place determinism cannot reach is\nfloating-point inference on a GPU, and there the choice is to refuse rather than trust. The attestation gate\ndefaults to rejecting an unattested non-deterministic path, with no allow-by-omission, so a missing or stale\nattestation fails closed. Failing closed is the safe trade for a path that touches non-deterministic\ncompute.\n\n## Access and canon\n\nThis is a summary. It names the verification surface and the three building blocks, Q16.16\nfixed-point determinism, Halo2-KZG proof verification, and hardware attestation. The proving-system\ninternals - circuit construction, prover and verifier internals, any structured reference string or setup\nmaterial, and the exact proof wire formats - are public in the `citrate-chain` repository (Apache-2.0);\nthis page summarizes and links to them rather than reproducing them. No setup seed, ceremony material,\nkeys, or credentials appear on this page. Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity. For the full internals, read the `verify.rs`, `inference.rs`, and `attestation/` precompile\nsources in `citrate-chain`.\n\n## Source and verification\n\n- Source: `citrate-chain/core/execution/src/precompiles/{verify.rs,inference.rs}`, with the compute\n primitives in `core/execution/src/precompiles/compute.rs` and the attestation gate in\n `core/execution/src/precompiles/attestation/`. Research context: Gradient Paper No. 10,\n `gradient_papers_v3/Gradient_Papers_No10_Substrate_of_Verifiable_Inference_v3.md` (linked, not copied).\n- Audited against SHA: `e68af83`.\n- Status: the deterministic verification path (`0x0107` to `0x0109`) and the Q16.16 compute primitives are\n Implemented (pre-audit) on testnet 40204. The `0x0108` verifier is Implemented behind a build feature so\n nodes that do not host it stay lean. The attestation gate is Implemented in its always-reject default;\n live hardware-attestation verification is Specified, not yet enabled.\n"},"/sdks/bundler":{"slug":"/sdks/bundler","title":"Citrate Bundler","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-bundler/gate/src/server.ts, citrate-bundler/gate/src/precheck.ts, citrate-bundler/gate/src/config.ts, citrate-bundler/Caddyfile, citrate-bundler/Dockerfile","syncedSha":"e1aa264","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"The paymaster precheck","anchor":"the-paymaster-precheck"},{"depth":3,"text":"citrate_getUserAddress","anchor":"citrate_getuseraddress"},{"depth":3,"text":"Examples","anchor":"examples"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The Citrate Bundler is the ERC-4337 service that accepts UserOperations for Citrate Keyring accounts on\nthe Citrate Network, chain id 40204, and submits them on chain. It is the piece that lets a person act\nthrough a smart-contract account without holding native SALT for gas, because a paymaster sponsors the\nwork. This page is for integrators wiring an account-abstraction flow against it.\n\n## What it is\n\nThe bundler is two parts on one host. The first is an eth-infinitism v0.7 reference bundler, vendored as a\nDocker image and run unchanged. The second is a thin Citrate gate written in TypeScript that sits in front\nof it. A client never talks to the reference bundler directly; it talks to the gate, and the gate proxies\nthe call upstream after it has checked the request.\n\nThe gate does three things, in order, before it forwards a call: it validates an API key, it applies a\nper-IP and a per-key rate limit, and on `eth_sendUserOperation` it runs a paymaster precheck against the\nchain. Every other JSON-RPC method passes through to the reference bundler unchanged, so a standard\nERC-4337 SDK treats this as an ordinary bundler. The gate is the only Citrate-specific code in the path;\nthe method surface is the standard one.\n\nThe bundler is one corner of the Citrate Keyring account-abstraction topology. A Citrate Keyring account\nis a smart-contract account, often controlled by a passkey rather than a stored secret, described in\n[passkeys](/aa/passkeys). When that account wants to act, it builds a UserOperation, names the\n[CitratePaymaster](/aa/paymaster) to cover gas, and sends it to this bundler. The bundler hands the\noperation to the EntryPoint, the EntryPoint validates it and pulls gas from the paymaster's deposit, and\nthe account's intent lands on chain. The JavaScript helpers that build and sign those operations live in\nthe [JavaScript SDK](/sdks/js).\n\n## How to use it\n\n1. Point your ERC-4337 client at the bundler host. The public endpoint is a JSON-RPC POST to `/rpc`, with\n TLS terminated at the edge by the reverse proxy. Use a placeholder host such as `` until\n you have the deployed name.\n2. Confirm you are talking to chain 40204 by calling `eth_chainId`. It returns `0x9d0c`. This call needs\n no API key.\n3. Obtain an API key. Keys are issued by the operator and carry a `bk_` prefix. Send it as\n `Authorization: Bearer ` on any call that submits work.\n4. Build a UserOperation in your SDK, name the CitratePaymaster, and submit it with\n `eth_sendUserOperation`. The gate runs its precheck, then forwards to the reference bundler, which\n bundles and submits it.\n5. Poll for the result with `eth_getUserOperationReceipt`, passing the hash returned by the send call.\n\n## Reference\n\nThe surface is `API-BUNDLER`, the JSON-RPC methods reachable at `POST /rpc`. The gate special-cases only\n`eth_sendUserOperation`; the rest are served by the eth-infinitism v0.7 upstream, so the standard v0.7\nmethod set applies.\n\n| Method | Params | Returns | Handled by |\n|---|---|---|---|\n| `eth_chainId` | `[]` | hex chain id, `0x9d0c` for 40204 | proxied to upstream |\n| `eth_supportedEntryPoints` | `[]` | array of configured EntryPoint addresses | proxied to upstream |\n| `eth_sendUserOperation` | `[userOp, entryPoint]` | UserOperation hash | gate precheck, then upstream |\n| `eth_estimateUserOperationGas` | `[userOp, entryPoint]` | gas estimate | proxied to upstream |\n| `eth_getUserOperationByHash` | `[hash]` | UserOperation and its location | proxied to upstream |\n| `eth_getUserOperationReceipt` | `[hash]` | receipt | proxied to upstream |\n\nThe host also answers two non-RPC routes. `GET /health` returns the string `ok` and is served by the\nreverse proxy without touching the bundler, a cheap liveness probe. `GET /healthz` returns a composite\nstatus served by the gate, `{ \"status\": \"ok\", \"redis\": true, \"upstream\": true }`, which checks Redis and\nthe upstream bundler. The `/metrics` route is Prometheus exposition and is not public; the edge returns\n`403` and operators scrape it from inside the host.\n\n### The paymaster precheck\n\nWhen a UserOperation names the CitratePaymaster and a paymaster is configured on the gate, the gate runs an\noff-chain precheck before it forwards the call (`gate/src/precheck.ts`). The precheck is an optimization,\nnot a security boundary; the EntryPoint re-validates everything on chain regardless. Its three steps:\n\n1. The category byte, the first byte of `paymasterData`, must be a known category, `0`, `1`, or `2`.\n2. The sender must be registered on the paymaster, read as `CitratePaymaster.isRegistered(sender)`.\n3. The paymaster must hold a non-zero EntryPoint deposit, read as `EntryPoint.balanceOf(paymaster)`,\n because a zero deposit would surface as an AA31 revert.\n\nA failed precheck returns JSON-RPC error code `-32002` with a reason. If the chain itself is unreachable\nthe precheck fails open with a logged reason, since on-chain validation remains authoritative. Operations\nthat pay their own gas, with no paymaster named, skip the precheck entirely.\n\n### citrate_getUserAddress\n\nA method named `citrate_getUserAddress(userId)`, which would predict the smart-contract account address\nfor a Citrate user id, is described in the repository README. It is **not implemented**. We searched the\ngate and the upstream method surface at the audited SHA and found no handler for it; the README documents\nan intended method that has not landed. Do not call it on the public endpoint. To predict an account\naddress today, use the on-chain factory through the [JavaScript SDK](/sdks/js). Status for this method:\nSpecified.\n\n### Examples\n\n```bash\n# Confirm chain connectivity, no API key required.\ncurl -s -X POST https:///rpc \\\n -H 'content-type: application/json' \\\n -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_chainId\",\"params\":[]}'\n# {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x9d0c\"}\n```\n\n```bash\n# List the configured EntryPoints.\ncurl -s -X POST https:///rpc \\\n -H 'content-type: application/json' \\\n -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_supportedEntryPoints\",\"params\":[]}'\n```\n\n```bash\n# Submit a UserOperation, API key required.\ncurl -s -X POST https:///rpc \\\n -H 'content-type: application/json' \\\n -H 'Authorization: Bearer ' \\\n -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_sendUserOperation\",\"params\":[{ /* userOp */ }, \"\"]}'\n```\n\n## Design rationale\n\nThe gate is deliberately thin. The reference bundler is a known quantity, audited upstream and run without\nmodification, so the only Citrate logic in the path is the gate, which is small enough to read in one\nsitting. The split also isolates failure: the bundler lives on its own host, so a bundler outage cannot\ntake down the identity service or the inference gateway.\n\nThe reference bundler runs in `--unsafe` mode (`Dockerfile`). That mode skips the `debug_traceCall`\nfull-validation step, which the Citrate Network RPC does not yet expose, a method that exists only on\ncertain client implementations. For the current single-tenant deployment, where every UserOperation\noriginates from verified Citrate clients rather than an open mempool, signature, nonce, and paymaster\nvalidation are sufficient. The flag is dropped once the chain gains the trace method. We note this here so\nthe trade-off is visible, not buried.\n\n## Failure modes\n\nThis surface is security relevant, and it fails closed where it matters.\n\n- **Missing or invalid API key.** When the gate requires a key, a missing key returns `-32001` and a bad\n key returns `-32001`; neither reaches the bundler. The key requirement is configurable, and the default in\n `gate/src/config.ts` is now on (`GATE_REQUIRE_API_KEY` defaults to `true`). An operator who wants an open\n endpoint must set `GATE_REQUIRE_API_KEY=false`, and in production must also set `GATE_ALLOW_ANONYMOUS=true`\n to accept the risk, or the gate warns.\n- **Rate limit exceeded.** A per-IP limit, default 60 per minute, and a per-key limit, default 600 per\n minute, both return `-32005`. If the backing Redis store is unavailable the rate limiter fails closed,\n rejecting rather than waving traffic through.\n- **Doomed sponsored operation.** The paymaster precheck rejects an unregistered sender, a bad category\n byte, or an empty paymaster deposit with `-32002`, before a bundler slot is spent. If the chain is\n unreachable the precheck fails open and the EntryPoint catches the same conditions on chain.\n- **Upstream unreachable.** If the gate cannot reach the bundler it returns `-32003` rather than hanging.\n- **Production config gaps.** In production the gate refuses to boot if a security-relevant value is unset,\n for example a missing Redis URL or an unset paymaster address; in development the same gaps degrade to\n logged warnings with safe defaults.\n\n## Access and canon\n\nTier: commercial. The method surface itself is standard ERC-4337, but operating against the bundler, API\nkeys, the precheck semantics, the rate-limit behavior, the EntryPoint configuration, is integration depth\nfor contracted builders and is gated from anonymous scraping.\n\nNo secrets appear here. The deployment runbook holds operator material, an operator account mnemonic and a\ngenerated store password, which live only in a `0600` `.env` on the host and are never transcribed into\ndocumentation. Examples use a placeholder host and ``. Do not paste any key, password,\nmnemonic, or private host detail into an example.\n\n## Source and verification\n\n- Source: `citrate-bundler`. Routing, auth, and rate limits in `gate/src/server.ts`; the paymaster\n precheck in `gate/src/precheck.ts`; the fail-closed config in `gate/src/config.ts`; public routing and\n the `/health` versus `/healthz` split in `Caddyfile`; the `--unsafe` upstream invocation in `Dockerfile`.\n The `citrate_getUserAddress` reference is in `README.md`. Operator secrets live in `DEPLOY.md` and are\n not reproduced here.\n- Audited against SHA: `e1aa264`.\n- Status: Implemented (pre-audit). The gate, the precheck, the rate limits, and the standard method surface\n exist and run; this slice has not had an external audit. The `citrate_getUserAddress` method is\n Specified, declared in the README but not implemented at this SHA.\n"},"/sdks/entitlements":{"slug":"/sdks/entitlements","title":"Entitlements and capabilities","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-sdk-js/src/entitlements/capabilities.ts","syncedSha":"9664fa8","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Citrate Identity mints a signed entitlement claim, `https://citrate.ai/entitlement`, that carries one of five\ntiers: `public`, `commercial`, `commercial.kyc`, `academic`, `confidential`. The SDK ships one canonical way to\nread that claim so relying parties stop disagreeing about what a tier means. It is available in the TypeScript\nSDK (`@citratelabs/sdk`, the `entitlements` namespace) and the Python SDK (`citrate_sdk.entitlements`).\n\n## What it is\n\nThe model is capabilities, not a global rank. There is no \"tier A outranks tier B\" comparison anywhere.\n`normalizeTier` collapses any unknown value to `public` and never escalates; `capabilities` returns an explicit\nset of what a principal may do. This matters because a bare ordinal is fragile. An unmapped tier that sorts as\n`undefined` once took a relying party's whole app down, and two apps that ordered the tiers differently reached\nopposite authorization decisions on the same signed claim.\n\nThe one decision worth stating plainly: `commercial.kyc` is the tier every KYC-verified principal receives, and\nit opens ecosystem transactions but not confidential content. Passing KYC lets you transact; it does not buy a\ncontent seat. `commercial.kyc` is not above `commercial`; it carries the same content capabilities.\n\n## How to use it\n\n```typescript\nimport { entitlements } from '@citratelabs/sdk';\n\nentitlements.normalizeTier('made-up'); // \"public\", unknown never escalates\nentitlements.capabilities('commercial.kyc').ecosystemTx; // true\nentitlements.capabilities('commercial.kyc').confidentialDocs; // false\n\n// can() applies expiry and the role bypass, matching the authority's resolveEntitlementClaim.\nentitlements.can(claim, 'confidentialDocs');\n```\n\n```python\nfrom citrate_sdk import entitlements\n\nentitlements.normalize_tier(\"made-up\") # \"public\"\nentitlements.capabilities(\"commercial.kyc\").ecosystem_tx # True\nentitlements.capabilities(\"commercial.kyc\").confidential_docs # False\nentitlements.can(claim, \"confidential_docs\")\n```\n\n## Reference\n\n| Name | What it does |\n|---|---|\n| `normalizeTier(value)` | Return one of the five tiers; anything unknown collapses to `public`. Never escalates. |\n| `capabilities(tier, overrides?)` | The capability set: `ecosystemTx`, `gatewayKeys`, `academicData`, `confidentialDocs`. |\n| `can(claim, capability, opts?)` | Whether a claim grants a capability, applying expiry and the `citrateRole` bypass. |\n| `DEFAULT_CAPABILITIES` | The canonical tier to capability map an RP can override. |\n\nThe default map: `public` grants nothing; `commercial` and `commercial.kyc` grant `ecosystemTx` and\n`gatewayKeys`; `academic` adds `academicData`; `confidential` adds `confidentialDocs`. A role-bearing principal\n(`citrateRole` set) bypasses the gate, and an expired claim collapses to `public`.\n\n## Design rationale\n\nShipping a single default map is the point: relying parties import it instead of each inventing an ordering, so\nthe federation cannot disagree by accident. An RP with a genuinely different policy passes an override map,\nwhich keeps the divergence explicit and local rather than silent and global.\n\n## Access and canon\n\nPublic. The tiers and capabilities are authorization facts, not secrets, and are derivable from public on-chain\nentitlement claims.\n\n## Source and verification\n\n- Source: `citrate-sdk-js/src/entitlements/capabilities.ts`, `citrate-sdk-python/citrate_sdk/entitlements.py`.\n- Status: Implemented (pre-audit); the JavaScript and Python matrices are unit-tested to give identical answers.\n"},"/sdks/identity":{"slug":"/sdks/identity","title":"Identity and the embedded Keyring account","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-sdk-js/src/identity/index.ts","syncedSha":"9664fa8","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Sign in with OIDC (PKCE)","anchor":"sign-in-with-oidc-pkce"},{"depth":3,"text":"The embedded account","anchor":"the-embedded-account"},{"depth":3,"text":"Read claims and capabilities","anchor":"read-claims-and-capabilities"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"Sign-in","anchor":"sign-in"},{"depth":3,"text":"ID-token verification","anchor":"id-token-verification"},{"depth":3,"text":"The embedded account","anchor":"the-embedded-account"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The identity module is the turnkey authorization spine for Citrate Network. It lets an app sign a user in\nagainst `auth.citrate.ai`, verify the resulting token safely, give that user a Citrate Keyring account they own\nwithout ever handling a private key, and read their entitlement, all from one typed surface. It ships in both\nthe TypeScript SDK (`@citratelabs/sdk`, the `identity` namespace) and the Python SDK (`citrate_sdk.identity`),\nwith the same behavior and the same account address on both.\n\n## What it is\n\nThree things sit behind the module, and they compose:\n\n- **Sign-in.** OIDC Authorization Code flow with PKCE, and Sign-In With Ethereum (EIP-4361). Either way you\n get an ID token that the SDK verifies before you trust a single claim.\n- **The embedded account.** An OIDC subject deterministically owns a counterfactual ERC-4337 Citrate Keyring\n account. The SDK computes that address locally and can verify it against the on-chain factory. You never see\n or hold a key.\n- **Claims and capabilities.** After sign-in, `userInfo` returns the subject, account address, KYC status, and\n a normalized entitlement tier with a capability set (see [entitlements](/sdks/entitlements)).\n\nEndpoints, scopes, chain addresses, and the entitlement claim URI are all read from the generated federation\ncontract, so nothing is hand-typed and nothing goes stale against a chain reroll.\n\n## How to use it\n\n### Sign in with OIDC (PKCE)\n\n```typescript\nimport { identity } from '@citratelabs/sdk';\n\nconst client = new identity.IdentityClient({\n clientId: 'your-app',\n redirectUri: 'http://127.0.0.1:8899/auth/callback',\n});\n\n// 1. Send the user to the authorize URL; keep the PKCE verifier.\nconst { url, pkce } = client.authorizeUrl({ state, nonce });\n// ...redirect the browser to `url`, receive `code` on the callback...\n\n// 2. Exchange the code. The returned ID token is already verified.\nconst tokens = await client.exchangeCode({ code, codeVerifier: pkce.verifier, nonce });\nconsole.log(tokens.claims.sub);\n```\n\nIn Python the shape is identical:\n\n```python\nfrom citrate_sdk.identity import IdentityClient\n\nclient = IdentityClient(client_id=\"your-app\", redirect_uri=\"http://127.0.0.1:8899/auth/callback\")\nurl, pkce = client.authorize_url(state=state, nonce=nonce)\ntokens = client.exchange_code(code=code, code_verifier=pkce.verifier, nonce=nonce)\n```\n\n### The embedded account\n\nAn app never derives the address from the authority's `/aa/address` endpoint. It computes the address locally\nand verifies it against the factory, which is the deployer and therefore the only ground truth.\n\n```typescript\nimport { identity } from '@citratelabs/sdk';\n\nconst userId = identity.uuidToUserId(tokens.claims.sub); // keccak256(utf8(lowercase uuid))\nconst address = identity.predictWalletAddress(userId); // local, offline\n\n// Where an RPC is available, confirm against the on-chain factory:\nconst confirmed = await identity.verifyWalletAddressOnChain(userId, provider);\n```\n\nTo stand the account up, request a factory deploy permit. The authority signs it with its identity-signer, and\nthe SDK never signs.\n\n```typescript\nconst permit = await client.requestDeployPermit({ userId, initData, expiresAt }, tokens.accessToken);\n```\n\n### Read claims and capabilities\n\n```typescript\nconst info = await client.userInfo(tokens.accessToken);\ninfo.tier; // normalized: unknown values collapse to \"public\"\ninfo.capabilities.ecosystemTx; // what this principal may do\ninfo.walletAddress; // the Keyring account, if the claim carried one\n```\n\n## Reference\n\nEach name below is exported from `@citratelabs/sdk` (`identity` namespace) and mirrored in\n`citrate_sdk.identity`.\n\n### Sign-in\n\n| Name | What it does |\n|---|---|\n| `IdentityClient(config)` | The client. Config: `clientId`, `redirectUri`, optional `scopes`, injectable `fetch`. |\n| `discover()` | Fetch and cache the OIDC discovery document; the issuer is pinned to the artifact. |\n| `authorizeUrl({state, nonce, pkce?})` | Build the PKCE S256 authorize URL; returns the URL and the PKCE pair. |\n| `exchangeCode({code, codeVerifier, nonce?})` | Exchange a code for tokens; the ID token is verified before return. |\n| `refresh(refreshToken, expectedSub)` | Rotate tokens with a refresh token. The refreshed ID token must name the same `sub` (Python: `refresh(refresh_token, expected_sub)`). |\n| `siweChallenge()` / `buildSiweMessage({address, nonce})` / `siweVerify({message, signature})` | EIP-4361 sign-in. `siweChallenge` GETs a single-use nonce, `buildSiweMessage` builds the message the authority accepts (chain 40204, expiry of at most 24 h), and `siweVerify` returns `{kind: 'redirect', redirectTo}` inside an OIDC login or `{kind: 'token', idToken, claims}` for the headless grant. Python: `siwe_challenge()`, `build_siwe_message(...)`, `siwe_verify(...)`. |\n| `userInfo(accessToken)` | Fresh claims plus a normalized tier and capability set. |\n| `logout(accessToken)` | End the session; fires the cross-instance revocation cascade. |\n\n### ID-token verification\n\n`verifyIdToken(token, { issuer, audience, jwks })` is the trust boundary and is called for you by\n`exchangeCode` and `refresh`. It accepts only `RS256`, verifies the signature before reading any claim, and\nrejects `alg:none`, algorithm confusion, a wrong audience or issuer, an expired or not-yet-valid token, a\ntampered payload, and a token whose `kid` matches no key. It also requires numeric `exp` and `iat` claims\n(an `iat` more than the clock tolerance in the future is refused) and a `typ` of `JWT` or none.\n\n### The embedded account\n\n| Name | What it does |\n|---|---|\n| `uuidToUserId(uuid)` | `keccak256(utf8(lowercase uuid))`, the 32-byte AA userId for a UUID subject. |\n| `addressToUserId(address)` | Left-pad a 20-byte EOA to a 32-byte userId (SIWE subjects). |\n| `predictWalletAddress(userId, opts?)` | The counterfactual Citrate Keyring address. Pure and offline. |\n| `verifyWalletAddressOnChain(userId, provider, opts?)` | Verify the local prediction against the factory; throws on mismatch. |\n| `requestDeployPermit({userId, initData, expiresAt}, accessToken)` | Ask the authority to sign a factory deploy permit. |\n| `listValidators(userId)` / `guardianConfig(accessToken)` | Read installed validators and the guardian nomination. |\n\n## Design rationale\n\nThe module holds no keys and verifies every token, so a claim is never trusted before its signature. Account\naddress prediction is duplicated byte-for-byte across the TypeScript SDK, the Python SDK, the Rust `wallet-aa`\ncrate, and the on-chain factory, on purpose: the address a user funds must be identical no matter which surface\ncomputes it. The SDK computes it locally and checks it against the factory rather than trusting a service,\nbecause a service can drift out of sync with the chain while still answering confidently.\n\n## Failure modes\n\nThis surface guards money and identity, so it fails closed.\n\n- `verifyIdToken` throws on any verification failure. There is no best-effort path; an unverifiable token is\n rejected.\n- `predictWalletAddress` throws on a malformed userId, and `verifyWalletAddressOnChain` throws if the local\n address and the on-chain factory disagree, with a do-not-fund message. Prefer the on-chain check before\n showing a deposit address to a user.\n- The client is fail-closed without a `fetch` implementation, and the module never returns or logs a private\n key, seed, or bearer secret.\n\n## Access and canon\n\nPublic. This is open SDK reference and carries no secrets. Access tokens, refresh tokens, and gateway keys are\nthe caller's to hold; the module never persists them. The hostnames named here, `auth.citrate.ai` and\n`rpc.citrate.ai`, are public production endpoints, not credentials.\n\n## Source and verification\n\n- Source repos: `citrate-sdk-js` (`src/identity/`), `citrate-sdk-python` (`citrate_sdk/identity/`).\n- Zero new runtime dependencies: TypeScript uses `node:crypto` and `ethers`; Python uses `cryptography`,\n `eth_utils`, and `requests`.\n- Status: Implemented (pre-audit), unit-tested including the OIDC attack rejections and the on-chain\n account-address parity vector.\n"},"/sdks/inference-gateway":{"slug":"/sdks/inference-gateway","title":"Citrate Inference Gateway","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-inference-gateway/gateway/src/, citrate-inference-gateway/crates/x402-axum/src/","syncedSha":"603fe92","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"OpenAI-compatible client pattern","anchor":"openai-compatible-client-pattern"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"REST routes (`API-GW-rest`)","anchor":"rest-routes-api-gw-rest"},{"depth":3,"text":"Model name resolution, a current limit","anchor":"model-name-resolution-a-current-limit"},{"depth":3,"text":"x402 payment (`API-GW-x402`)","anchor":"x402-payment-api-gw-x402"},{"depth":3,"text":"Configuration, environment variable names only","anchor":"configuration-environment-variable-names-only"},{"depth":3,"text":"Examples","anchor":"examples"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":3,"text":"Known limits at this SHA","anchor":"known-limits-at-this-sha"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The Citrate Inference Gateway is an OpenAI-compatible HTTP service in front of Citrate Market on the\nCitrate Network, chain id 40204. You point an existing OpenAI client at it, call the standard `/v1/*`\nroutes, and the gateway selects a provider, meters the work, and settles payment per request. This page is\nfor application developers and integrators who want to run inference against the marketplace without\nwriting new client code.\n\n## What it is\n\nThe gateway speaks the OpenAI REST shape, so an existing client works by changing one setting, its base\nURL. Behind that familiar surface it resolves the requested model against on-chain registries, picks a\nprovider, dispatches the call, counts tokens, and settles the cost. Settlement uses x402, an HTTP `402`\npayment handshake described below and shared with the [Marketplace SDK](/sdks/marketplace#x402).\n\nThe service runs in one of two modes, selected at boot by `CITRATE_GATEWAY_MODE`.\n\n- **Marketplace mode**, the default. The gateway reads the on-chain `ModelRegistry`,\n `ComputePricingOracle`, and `InferenceRouter`, selects a provider for the requested model, and gates paid\n routes behind x402. This is the mode this page documents.\n- **Local-proxy mode.** A leaner build that fronts a resident inference server, for example a llama-server\n on a single machine, gated by a `cgk_` API key rather than x402, with no chain calls. It exists so a\n single-node deployment can serve the same OpenAI surface; it is named here for completeness and is not\n the marketplace surface.\n\nThe two read paths fit Citrate Market as follows. A model's price comes from the\n[x402 pricing contract](/contracts/x402) and the pricing oracle; the provider that runs the work is one of\nthe operators selling [compute](/compute/pool). The gateway is the thin layer that turns an OpenAI request into\na metered, paid marketplace job and an OpenAI response.\n\nThere are two documented surfaces:\n\n- **`API-GW-rest`**, tier public, the OpenAI-compatible routes and their request and response shapes, what\n a developer needs to call it.\n- **`API-GW-x402`**, tier commercial, the x402 payment handshake that gates the paid routes, shared with\n the [Marketplace SDK](/sdks/marketplace#x402).\n\n## How to use it\n\n1. Set your OpenAI client's base URL to the gateway, `https:///v1`. The API key field is not\n used for payment on the paid routes; x402 settles those.\n2. List the available models with `GET /v1/models`. This route is free and needs no payment. The `id` it\n returns is what you pass back as the `model` field.\n3. For a chat completion, send `POST /v1/chat/completions` with the standard OpenAI body. The first attempt\n without a payment header returns an HTTP `402` with a payment challenge.\n4. Sign the challenge and resend. The plain OpenAI client does not produce a payment header, so use the\n [Marketplace SDK `X402Client`](/sdks/marketplace#x402), which catches the `402`, signs, and retries for\n you.\n5. To see what you have spent, call `GET /v1/usage` with your API key as a bearer token.\n\n### OpenAI-compatible client pattern\n\nBecause the routes match the OpenAI shape, the official SDKs work by overriding the base URL. The free\nroutes work straight away; the paid routes need the x402 client.\n\n```python\nfrom openai import OpenAI\n\nclient = OpenAI(base_url=\"https:///v1\", api_key=\"not-used-for-x402\")\nprint(client.models.list()) # GET /v1/models, free, no payment\n```\n\n```typescript\nimport OpenAI from \"openai\";\nconst client = new OpenAI({ baseURL: \"https:///v1\", apiKey: \"unused\" });\nawait client.models.list(); // GET /v1/models, free\n```\n\n## Reference\n\n### REST routes (`API-GW-rest`)\n\nHandlers live under `gateway/src/`. The gateway listens on `127.0.0.1:9800` by default\n(`CITRATE_GATEWAY_LISTEN_ADDR`), and production runs behind a TLS reverse proxy.\n\n| Route | Method | Handler | Auth |\n|---|---|---|---|\n| `/v1/chat/completions` | POST | `gateway/src/chat.rs` | x402, paid |\n| `/v1/batch` | POST | `gateway/src/batch.rs` | x402, paid |\n| `/v1/batch/{id}` | GET | `gateway/src/batch.rs` | submitter-bound read |\n| `/v1/batch/{id}/output` | GET | `gateway/src/batch.rs` | submitter-bound read |\n| `/v1/models` | GET | `gateway/src/models.rs` | free |\n| `/v1/usage` | GET | `gateway/src/usage.rs` | API-key bearer |\n| `/health` | GET | `gateway/src/health.rs` | free liveness |\n| `/metrics` | GET | `gateway/src/metrics.rs` | Prometheus exposition |\n\n`POST /v1/chat/completions` (`gateway/src/chat.rs`) takes the OpenAI `ChatCompletionRequest`,\n`{ model, messages, max_tokens?, stream? }`, and returns a `ChatCompletionResponse` with `choices` and a\n`usage` block. It streams over server-sent events when `stream` is true. The handler resolves the model,\nquotes the cost on chain, dispatches to a selected provider with up to three failover attempts, and\nverifies the provider's signed result before returning it. `max_tokens` is clamped to a ceiling,\n`CITRATE_GATEWAY_MAX_TOKENS`, default 8192; an unset value falls back to a per-request default of 512.\n\n`POST /v1/batch` (`gateway/src/batch.rs`) takes `{ requests: [ChatCompletionRequest, ...] }` up to a\nmaximum of 1000 and returns a batch record with a `batch_id` and a `status` in the set\n`submitted | running | completed | partial_failure | failed`. A detached processor walks the batch through\nits states, dispatches each request, and settles the escrow at the end, releasing the cost of completed\nslots and refunding the cost of failed ones. The status and output reads are bound to the submitter.\n\n`GET /v1/models` (`gateway/src/models.rs`) reads the on-chain `ModelRegistry` and the compute pools and\nreturns `{ object: \"list\", data: [...] }`. It includes individual active models and pools, with pool ids\nprefixed `pool-`. Inactive models are filtered out.\n\n`GET /v1/usage` (`gateway/src/usage.rs`) requires an API-key bearer token and returns totals and a daily\nbreakdown of requests, tokens, and SALT spent. The accounting is held in process memory at this SHA;\ndurable storage of usage is planned for a later slice. Status for durable usage: Specified.\n\n### Model name resolution, a current limit\n\nThe gateway resolves a model identifier to an on-chain hash in `gateway/src/queries.rs`. A caller may pass\na pinned 32-byte hash directly, which always works. A caller may also pass a friendly name, which the\ngateway resolves by enumerating the registry, since the on-chain `ModelRegistry` does not expose a\nname-to-hash view, the hash being derived from creator, name, timestamp, and a nonce. A dedicated\n`getModelByName` view, or an off-chain name registry, is the intended way to close this gap. Status for the\nname view: Specified.\n\n### x402 payment (`API-GW-x402`)\n\nPaid routes sit behind the `X402Layer` middleware (`crates/x402-axum/src/layer.rs`). The handshake is\nimplemented end to end; settlement is a real on-chain transaction, not a mock.\n\n1. The client `POST`s without an `x-payment` header.\n2. The gateway returns HTTP `402` with a payment challenge, `{ version, facilitator, token (the wSALT\n address), chain_id (40204), amount (wei as a decimal string), nonce, valid_after, valid_before,\n recipient, digest }`. The challenge has a limited lifetime, and the nonce is single-use, minted and\n tracked by this gateway.\n3. The client signs the EIP-712 `transferWithAuthorization` digest, builds a payment payload, encodes it as\n URL-safe base64 without padding, and resends with the `x-payment` header.\n4. The gateway verifies the signature and the nonce, checks the recipient binds to the configured treasury,\n settles `X402Facilitator.settlePayment` on chain, waits for the receipt, and runs the handler, returning\n the OpenAI-shaped response.\n\nThis is the server side of the [Marketplace SDK x402 codec](/sdks/marketplace#x402). Use that client rather\nthan re-implementing the codec.\n\n### Configuration, environment variable names only\n\nThe gateway reads its configuration from environment variables. Names and purposes follow; no values are\nshown. `CITRATE_GATEWAY_MODE` (`marketplace` or `local-proxy`), `CITRATE_GATEWAY_CHAIN_ID` (default 40204),\n`CITRATE_GATEWAY_RPC_URL`, `CITRATE_GATEWAY_LISTEN_ADDR` (default `127.0.0.1:9800`),\n`CITRATE_GATEWAY_MODEL_REGISTRY`, `CITRATE_GATEWAY_PRICING_ORACLE`, `CITRATE_GATEWAY_INFERENCE_ROUTER`\n(contract addresses), `CITRATE_GATEWAY_KEYSTORE_PATH` (the durable store, required in production),\n`CITRATE_GATEWAY_DEV_MODE`, `CITRATE_GATEWAY_OPEN_CHAT`, `CITRATE_GATEWAY_MAX_TOKENS`, and the operator\nsigner family (`CITRATE_GATEWAY_OPERATOR_KEYSTORE*`, `CITRATE_GATEWAY_KMS_KEY_ID`, and the spend-cap\nvariables). For local-proxy mode, `CITRATE_GATEWAY_UPSTREAM_URL` lists one or more upstreams, tried in\norder. Observability uses `LOG_FORMAT` and `RUST_LOG`. The repository `gateway/RUNBOOK.md` is the full\noperator reference.\n\n### Examples\n\n```bash\n# Free: list models, no payment.\ncurl -s https:///v1/models\n```\n\n```bash\n# Paid route without payment returns a 402 challenge.\n# Then sign and retry; use the SDK X402Client.\ncurl -s -X POST https:///v1/chat/completions \\\n -H 'content-type: application/json' \\\n -d '{\"model\":\"\",\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}]}'\n# HTTP 402 with a payment challenge body\n```\n\n## Design rationale\n\nThe gateway speaks the OpenAI shape on purpose. A developer who already has working code should be able to\nmove to Citrate Market by changing a base URL, not by learning a new client, and the OpenAI compatibility\nis a deliberate public good. The payment surface is the part that differs, and it is kept behind one\nmiddleware so the rest of the service reads as an ordinary inference proxy. Settlement happens per request\nrather than against a held balance, so a caller pays for the work it asks for and nothing is escrowed\nbeyond a single request or batch.\n\n## Failure modes\n\nThe paid surface is where the gateway is security relevant, and it fails closed.\n\n- **No payment.** A paid route without a valid `x-payment` header returns `402`, never the work.\n- **Bad or replayed payment.** The gateway verifies the signature, checks the nonce is one it minted and\n has not seen, checks the payment window, and binds the recipient to the configured treasury. A nonce is\n single-use, so a replayed payment is rejected before it touches the chain.\n- **Settlement revert.** If the on-chain settlement transaction reverts, the gateway returns `402` with the\n transaction hash rather than running the handler.\n- **Oversized request.** `max_tokens` is clamped to the ceiling before pricing or dispatch, and a batch\n larger than 1000 requests is rejected, so a caller cannot price a request small and then demand a large\n response.\n- **Open chat in production.** The unauthenticated chat path is gated behind two development flags,\n `CITRATE_GATEWAY_OPEN_CHAT` and `CITRATE_GATEWAY_DEV_MODE`, and the gateway refuses to enable it on a\n non-loopback bind, so the open path cannot be left exposed by accident.\n- **Provider result.** A provider result is accepted only if it is non-empty and carries a valid binding\n signature; a failed verification moves to the next provider in the failover loop.\n\n### Known limits at this SHA\n\nSome pieces are designed and partly built but not yet complete. Pool dispatch is selected by the scoring\nlogic but returns a `503` when a pool wins, the per-provider path being the one that runs today; pool\ndispatch is a later slice. Durable storage of usage accounting is in process memory at this SHA. On-chain\njob posting per request, rather than direct dispatch to the provider, and streaming directly from a\nprovider, are not yet wired. Status for these pieces: Specified for pool dispatch and durable usage,\nTheoretical for on-chain per-request posting and provider streaming.\n\n## Access and canon\n\nThe REST surface, `API-GW-rest`, is public; it is the open API a developer needs to integrate, and the\nOpenAI compatibility is a deliberate public good. The x402 handshake, `API-GW-x402`, is commercial,\npayment-integration depth shared with contracted builders and gated from anonymous scraping.\n\nNo API keys or secrets appear here. Examples use a placeholder host and an unused key value, since x402\nsettles payment rather than a bearer key. Operator account material is loaded from a keystore or KMS and is\nnever documented; the repository source contains no hardcoded credentials at the audited SHA.\n\n## Source and verification\n\n- Source: `citrate-inference-gateway`. Routes and handlers in `gateway/src/` (`chat.rs`, `batch.rs`,\n `models.rs`, `usage.rs`, `queries.rs`, `health.rs`, `metrics.rs`, `config.rs`, `main.rs`, `lib.rs`); the\n payment middleware in `crates/x402-axum/src/` (`layer.rs`, `challenge.rs`, `client.rs`). Operator detail\n in `gateway/RUNBOOK.md`.\n- Audited against SHA: `603fe92`.\n- Status: Implemented (pre-audit). The REST routes, on-chain read queries, provider dispatch with failover,\n and the full x402 settlement path exist and run; this slice has not had an external audit. Specified:\n pool dispatch and durable usage accounting. Theoretical: on-chain per-request job posting and streaming\n from a provider.\n"},"/sdks/js":{"slug":"/sdks/js","title":"Citrate JavaScript SDK","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-sdk-js/src/index.ts","syncedSha":"2f8da46","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"Client, `src/client/CitrateClient.ts`","anchor":"client-srcclientcitrateclientts"},{"depth":3,"text":"Account abstraction, `src/aa/`","anchor":"account-abstraction-srcaa"},{"depth":3,"text":"Cryptography, `src/crypto/`","anchor":"cryptography-srccrypto"},{"depth":3,"text":"React hooks, `src/react/hooks.ts`","anchor":"react-hooks-srcreacthooksts"},{"depth":3,"text":"Constants and errors","anchor":"constants-and-errors"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"`@citratelabs/sdk` is the canonical TypeScript SDK for building on Citrate Network. It wraps the chain's JSON-RPC,\nthe model and inference operations, and the account-abstraction stack into a typed API, so a Node or browser\napp can talk to Citrate without hand-rolling calldata. This page is for the developer writing that app.\n\n## What it is\n\nThe SDK is the typed front door to Citrate Network from JavaScript and TypeScript. The package is named\n`@citratelabs/sdk` (formerly `@citratelabs/citrate-js`, retained as a deprecated alias) and the version of record is\n`0.2.0` in `package.json`. It is the canonical SDK; the other language SDKs follow it and stay non-canonical\nuntil a pilot integrator needs parity.\n\nSeveral surfaces sit behind one import, and most apps only ever touch the first:\n\n- The client, `CitrateClient` and `WebSocketClient`, for reading chain state, deploying models, and running\n inference against the chain (`src/client/`).\n- The account-abstraction helpers, exported under the `aa` namespace, for Citrate Keyring accounts built on\n Kernel v3 and ERC-4337 v0.7: counterfactual address derivation, passkey and EOA signing, UserOperation\n building, guardian recovery, and a bundler client (`src/aa/`).\n- The **identity spine and embedded Keyring account**, exported under the `identity` namespace: OIDC PKCE and\n SIWE sign-in, hardened ID-token verification, and smart-account address prediction verified against the\n on-chain factory. See [identity and the embedded Keyring account](/sdks/identity).\n- The **entitlement capabilities**, exported under the `entitlements` namespace: the canonical `normalizeTier`\n and capability map. See [entitlements](/sdks/entitlements).\n- The **inference gateway client**, exported under the `gateway` namespace: an OpenAI-compatible client for\n `infer.citrate.ai`.\n- The **memory client**, exported under the `memory` namespace: a typed client for a `citrate-memories`\n gateway (`src/memory/client.ts`), with `MemoryClient` over the OIDC REST surface (recall, search, neighbors,\n verify, review, assert) and `ByomMemoryClient` for the bring-your-own-model MCP path.\n- The cryptography utilities, `CryptoManager`, `KeyManager`, and Shamir secret sharing (`src/crypto/`).\n- The optional React hooks (`src/react/hooks.ts`), which are not re-exported from the package root.\n\nThe mental model is two layers. Reach for `CitrateClient` when you hold a key and want to read or write the\nchain directly. Reach for `aa` when you want a Citrate Keyring account that a passkey controls and a paymaster\ncan sponsor, instead of a raw key-pair account. Passkeys are covered under [passkeys](/aa/passkeys) and\nsponsorship under [the paymaster](/aa/paymaster).\n\nThe `aa` module is labeled EW-S1 WP-7 in source and points at infrastructure that is live but still moving\n(`bundler.citrate.ai`, `auth.citrate.ai`). Treat it as in development. The exported `VERSION` constant now\nreads `0.2.0` (`src/index.ts`), matching `package.json`.\n\n## How to use it\n\nThe package targets Node 16 and newer (`engines.node`), and runs in modern browsers through the Web Crypto\nAPI.\n\n1. Install the package.\n\n ```bash\n npm install @citratelabs/sdk\n ```\n\n The runtime dependencies are `ethers ^6.17`, `axios ^1.20`, and `eventemitter3 ^5.0`. The React hooks need\n `react >=16.8` and `react-dom >=16.8`, which are optional peer dependencies; install them only if you use\n the hooks.\n\n2. Construct a client. The defaults for testnet, chain id `40204`, live in `src/utils/constants.ts`.\n\n ```typescript\n import { CitrateClient, CHAIN_IDS, DEFAULT_RPC_URLS } from '@citratelabs/sdk';\n\n const client = new CitrateClient({\n // DEFAULT_RPC_URLS[40204] is ['https://rpc.citrate.ai']. An array\n // enables fallback across endpoints; a bare string is the single-RPC form.\n rpcUrl: DEFAULT_RPC_URLS[CHAIN_IDS.TESTNET],\n // privateKey: process.env.CITRATE_PRIVATE_KEY, // optional; from env, never inline\n });\n ```\n\n The constructor validates every RPC URL and any private key before it builds a provider, so a typo fails\n immediately with a `ValidationError` rather than at first use.\n\n3. Read chain state. Read methods need no key.\n\n ```typescript\n console.log(await client.getChainId()); // 40204\n console.log((await client.getBalance('0xYourAddress'))); // bigint, wei\n ```\n\n4. Write to the chain. Methods that send a transaction, `deployModel`, `inference`, and `batchInference`,\n require a `privateKey` in the config. Without one they throw. To send a sponsored write through a Citrate\n Keyring account instead of a raw key, build a UserOperation with the `aa` helpers and submit it through the\n bundler client; the [first-app tutorial](/sdks/tutorials/first-app-with-sdk-js) walks the whole path.\n\nFor a step-by-step build, follow [build your first app](/sdks/tutorials/first-app-with-sdk-js).\n\n## Reference\n\nEach item names its export and source path in `citrate-sdk-js` so a reader can check it against the code.\n\n### Client, `src/client/CitrateClient.ts`\n\nThe constructor takes a `CitrateClientConfig`: `rpcUrl` as a string or string array, and optional\n`privateKey`, `timeout`, `retries`, `headers`, and `ipfsApiUrl`. Exported from `src/index.ts` alongside\n`WebSocketClient` (`src/client/WebSocketClient.ts`) for streaming and subscriptions.\n\n| Method | Signature | What it does |\n|---|---|---|\n| `getRpcUrls()` | `(): readonly string[]` | The current fallback list, primary first. |\n| `getChainId()` | `(): Promise` | The chain id reported by the provider. |\n| `getBalance(address?)` | `(): Promise` | Native balance in wei. |\n| `getNonce(address?)` | `(): Promise` | Pending transaction count. |\n| `getAddress()` | `(): string \\| undefined` | The configured account address, if a key was given. |\n| `deployModel(modelData, config)` | `(): Promise` | Deploy a model artifact. Needs a key. |\n| `inference(request)` | `(): Promise` | One inference call. Needs a key. |\n| `batchInference(request)` | `(): Promise` | Batched inference. Needs a key. |\n| `getModelInfo(modelId)` | `(): Promise` | Model metadata, via `citrate_getModel`. |\n| `listModels(owner?, limit=100)` | `(): Promise` | List models, via `citrate_listModels`. |\n| `purchaseModelAccess(modelId, amount)` | `(): Promise` | Disabled, fails closed. See failure modes. |\n\n### Account abstraction, `src/aa/`\n\nImported as a namespace, `import { aa } from '@citratelabs/sdk'`; the module index is `src/aa/index.ts`. The flow\nit documents is: derive a userId, predict the address, enroll a validator, then build, sign, and send a\nUserOperation. The market side of this is covered in [the marketplace SDK](/sdks/marketplace).\n\n- Address derivation (`src/aa/address.ts`): `uuidToUserId`, `accountIdToAaUserId`, `predictWalletAddress`,\n `erc1967MinimalInitCodeHash`, and `AddressPredictionError`. The derivation is the cross-surface seam that\n gives one user the same account address on every device. `uuidToUserId` is `keccak256(utf8(lowercase\n uuid))`; `predictWalletAddress(factory, implementation, userId)` returns the CREATE2 address the factory\n deploys the Kernel proxy to.\n- Kernel v3 encoding (`src/aa/kernel.ts`): nonce helpers `rootValidatorNonce`, `validatorNonceKey`, and\n `composeNonce`; `encodeExecuteSingle` and `encodeExecuteBatch`; module management\n `encodeInstallModule`, `encodeUninstallModule`, and `encodeChangeRootValidator`; and the install-data\n builders `kernelInitializeCalldata`, `webauthnInstallData`, `ecdsaInstallData`, and `guardianInstallData`.\n The guardian builder enforces a count in [2, 7] and a threshold in [1, count].\n- UserOperation building (`src/aa/userop.ts`): `buildPackedUserOp`, `getUserOpHash`, `encodeDeployFor`,\n `packInitCode`, `packCitratePaymasterAndData`, `toRpcUserOperation`, and the gas-packing helpers\n `packAccountGasLimits` and `packGasFees`. `getUserOpHash` is verified against the live EntryPoint v0.7 on\n chain 40204.\n- Passkey signing (`src/aa/webauthn.ts`): `signUserOpWithPasskey` drives the browser's\n `navigator.credentials.get()`; the pure encoders `encodeWebauthnValidatorSignature`,\n `parseDerEcdsaSignature`, `normalizeP256S`, and `base64UrlEncode` are testable without a browser. The\n validator rejects high-s signatures, so `normalizeP256S` folds `s` into the lower half of the curve order.\n- EOA signing (`src/aa/eoa.ts`): `signUserOpWithEoa(signer, userOpHash)` signs with any ethers `Signer` and\n produces the EIP-191 shape the ECDSA validator accepts.\n- Guardian recovery (`src/aa/recovery.ts`): `guardianRecoveryDigest`, `packGuardianSignatures`,\n `buildRotateSignerCall`, and `RecoveryError`. The recovery op rotates the account's root validator toward a\n fresh passkey, validated by M-of-N guardian signatures bound to the account.\n- Bundler client (`src/aa/bundler.ts`): `BundlerClient` with `sendUserOperation`, `estimateUserOperationGas`,\n `getUserOperationReceipt`, `waitForUserOperationReceipt`, `supportedEntryPoints`, and `chainId`. The default\n endpoint is `CITRATE_BUNDLER_URL`, `https://bundler.citrate.ai/rpc`, a public RPC, not a secret. Errors\n surface the bundler's `AAxx` codes through `BundlerRpcError`.\n- Types (`src/aa/types.ts`): `PackedUserOperation`, `RpcUserOperation`, `UserOperationReceipt`,\n `CitrateAaConfig`, and the `PaymasterCategory` enum (`Standard`, `Recovery`, `FirstOp`).\n\n### Cryptography, `src/crypto/`\n\n- `CryptoManager` (`src/crypto/CryptoManager.ts`): SHA-256 hashing, AES-256-GCM, and PBKDF2 over the Web\n Crypto API. The default work factor is `PBKDF2_DEFAULT_ITERATIONS`, 600,000 iterations.\n- `KeyManager` (`src/crypto/KeyManager.ts`): key handling and model encryption, returning\n `EncryptedModelResult`. Encryption ECDH-wraps the symmetric key to the recipient. Options include\n `accessControl` (default `true`) and threshold key sharing (`thresholdShares`, `totalShares`).\n- Shamir secret sharing (`src/crypto/FiniteField.ts`): `splitSecretBytes`, `reconstructSecretBytes`, `GF256`,\n and `ShamirSecretSharing`. Share coefficients are drawn from a cryptographic RNG and fail closed if none is\n available.\n\n### React hooks, `src/react/hooks.ts`\n\nThe hooks are not re-exported from the package root, because React is an optional peer dependency. Import them\nfrom the build path; each hook throws if React is not installed.\n\n```typescript\nimport { useCitrateClient } from '@citratelabs/sdk/react/hooks';\n```\n\nThe hooks are `useCitrateClient`, `useModelDeployment`, `useInference`, `useModelInfo`, and `useModelList`.\n\n### Constants and errors\n\n- `src/utils/constants.ts`: `CHAIN_IDS` (`TESTNET: 40204`; releases before 0.2.3 also carried `MAINNET: 1`, which is Ethereum mainnet's chain id, not Citrate's. It was removed in 0.2.3. Citrate's network is 40204, and mainnet keeps that id), `DEFAULT_RPC_URLS`,\n `DEFAULT_WS_URLS`, `PRECOMPILE_ADDRESSES`, `GAS_LIMITS`, `TIMEOUTS`, `MODEL_LIMITS`, `ENCRYPTION`, `EVENTS`,\n and `API_ENDPOINTS`.\n- `src/errors/CitrateError.ts`: `CitrateError`, `ModelNotFoundError`, `InsufficientFundsError`, and\n `ValidationError`.\n\n## Design rationale\n\nThe client holds the RPC fallback list and rotates through it on transport errors rather than wrapping\nethers' automatic failover, which would double the connection budget. The trade is a little manual rotation\nin exchange for a predictable connection count. The `aa` helpers are written as pure functions: chain reads,\nnonces and deploy status, are passed in as arguments, so the same code that runs in a browser against\n`auth.citrate.ai` and the bundler also runs against pinned test vectors. Address derivation is duplicated\nbyte-for-byte across the JavaScript, identity-TS, and Rust implementations on purpose, because the address a\nuser controls must be identical no matter which surface computes it.\n\n## Failure modes\n\nThis is the surface where a mistake costs money or leaks data, so several methods fail closed.\n\n- A write method called without a configured key throws rather than silently doing nothing. `deployModel`,\n `inference`, and `batchInference` all require `privateKey` in the config.\n- When a model deployment or inference asks for encryption but no key manager is configured, the call throws\n rather than uploading in plaintext.\n- `purchaseModelAccess` is disabled and throws. The canonical precompile table has no access-purchase\n operation; the earlier implementation routed buyer funds to the verification precompile, where access was\n never granted and value was never credited. It stays closed until a node-confirmed precompile exists.\n- Shamir share generation throws if no cryptographic RNG is present, because the scheme's secrecy depends on\n unpredictable coefficients.\n- A bundler rejection surfaces as a `BundlerRpcError` carrying the `AAxx` code verbatim, for example `AA21`\n for an unfunded prefund or `AA31` for a paymaster deposit too low, so the caller can act on the real cause.\n\n## Access and canon\n\nPublic. This is open SDK reference a developer needs to build on Citrate Network, and it carries no secrets.\nPrivate keys, mnemonics, and bundler API keys are never inline; they come from the caller's environment\n(`config.privateKey`, `BundlerClientOptions.apiKey`). The hostnames named here, `rpc.citrate.ai`,\n`bundler.citrate.ai`, and `auth.citrate.ai`, are public production endpoints already shipped as defaults in\nthe source, not credentials. The SDK holds no identity data.\n\n## Source and verification\n\n- Source repo: `citrate-sdk-js`, package `@citratelabs/sdk@0.2.0` (`package.json`; `@citratelabs/citrate-js` retained as a deprecated alias).\n- Audited against SHA: `2f8da46`.\n- Audited paths: `src/index.ts`, `src/client/CitrateClient.ts`, `src/client/WebSocketClient.ts`,\n `src/aa/{index,address,kernel,userop,webauthn,eoa,recovery,bundler,types}.ts`,\n `src/crypto/{CryptoManager,KeyManager,FiniteField}.ts`, `src/identity/`, `src/entitlements/capabilities.ts`,\n `src/gateway/client.ts`, `src/memory/client.ts`, `src/react/hooks.ts`, `src/utils/constants.ts`, and\n `src/errors/CitrateError.ts`.\n- Status: Implemented (pre-audit). The client and cryptography surfaces are built and run against testnet 40204.\n The `aa` module is Implemented but in development (EW-S1 WP-7) and depends on still-moving bundler and auth\n infrastructure. The `identity`, `entitlements`, `gateway`, and `memory` namespaces are Implemented and\n unit-tested; account-address prediction is verified byte-for-byte against the on-chain factory.\n"},"/sdks/marketplace":{"slug":"/sdks/marketplace","title":"Marketplace SDK","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-sdk-marketplace/src/index.ts","syncedSha":"5cc1f39","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"MarketplaceClient","anchor":"marketplaceclient"},{"depth":3,"text":"X402Client","anchor":"x402client"},{"depth":3,"text":"Account integration","anchor":"account-integration"},{"depth":3,"text":"ABIs and calldata builders","anchor":"abis-and-calldata-builders"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The TypeScript client for Citrate Market. It reads marketplace state, pays per inference request over the\nx402 path, signs from a Citrate Keyring or a browser-injected account, and builds the calldata for posting\ninference and training jobs and buying compute credits, all on chain id `40204`. For integrators and app\nbuilders working buyer-side.\n\n## What it is\n\n`@citratelabs/marketplace-sdk` (version `0.1.0`) wraps the on-chain compute marketplace into a typed API\nso a buyer-side app can list providers, estimate cost, pay for inference, and post jobs without hand-rolling\ncalldata. It is built on [viem](https://viem.sh) (`^2.56.0`, a direct dependency) and presents four surfaces:\n\n- `MarketplaceClient`, read-only marketplace queries (`src/client.ts`).\n- `X402Client`, an auto-pay-on-402 HTTP client and the x402 payment codec (`src/x402.ts`).\n- `CitrateWallet` and `InjectedSigner`, the account integration: a native passphrase keystore or a\n browser-injected signer (`src/wallet/`). In prose we call this the Citrate Keyring integration; the code\n symbols keep their historical names.\n- ABIs and calldata builders for jobs, credits, and training (`src/contracts.ts`, with `src/jobs.ts`,\n `src/credits.ts`, `src/training.ts`).\n\nThe mental model: read state with `MarketplaceClient`; pay for an inference request with `X402Client`; sign\nwith a `CitrateWallet` (key held in the browser) or an `InjectedSigner` (an extension such as MetaMask or\nRabby); and for on-chain actions, posting a job, buying credits, requesting training, build calldata with the\nbuilder functions and send it through your signer and viem. The compute marketplace it talks to is described\nunder [Citrate Market](/compute/pool) and [the compute contracts](/contracts/compute); the per-request payment\nhandshake is the [x402 contract path](/contracts/x402); account recovery and passkeys are covered under\n[accounts](/aa/identity).\n\nThe package classifies itself Tier 1 in `AUDIT_TIER.md`: a full external audit of its cryptographic\nprimitives, key handling, and supply chain is required before any `v1.0.0` stable release, and no stable\nrelease ships without written attestation against an exact commit. At `0.1.0` it is pre-audit. Treat every\nsurface as experimental, and note three known gaps for a later slice: model-name resolution (slice 1 accepts\npinned hashes only), event subscription on a posted job, and key export from the keystore.\n\n## How to use it\n\n1. Install the package and its peer. It is published to the public npm registry\n (`https://registry.npmjs.org/`, per `publishConfig` in `package.json`) and needs Node 20 or newer.\n\n ```bash\n npm install @citratelabs/marketplace-sdk viem\n ```\n\n2. Build a read-only client. Contract addresses for testnet (`40204`) are vendored, so `defaultAddresses()`\n returns a working set.\n\n ```ts\n import { createPublicClient, http } from \"viem\";\n import { MarketplaceClient, defaultAddresses } from \"@citratelabs/marketplace-sdk\";\n\n const publicClient = createPublicClient({ transport: http(\"\") });\n const market = new MarketplaceClient({ publicClient, addresses: defaultAddresses() });\n ```\n\n3. To pay per request, construct an `X402Client` with a signer and a hard cap. There is no uncapped mode;\n `maxPayWei` is required and the client signs at most once per request.\n\n ```ts\n import { X402Client, unlockWallet } from \"@citratelabs/marketplace-sdk\";\n\n const account = await unlockWallet(userPassphrase); // key stays in the browser\n const x402 = new X402Client({\n signer: account,\n chainId: 40204,\n maxPayWei: 10n ** 16n, // 0.01 SALT cap per request\n allowedTokens: [wsaltAddress],\n });\n\n const res = await x402.send(`${gatewayBaseUrl}/v1/chat/completions`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ model: \"\", messages: [{ role: \"user\", content: \"hi\" }] }),\n });\n ```\n\n4. To post a job, build calldata and send it with your signer. The end-to-end version is in\n [post a marketplace job](/sdks/tutorials/post-a-marketplace-job).\n\n## Reference\n\nVerified against `citrate-sdk-marketplace` at `5cc1f39`. The public surface is re-exported from\n`src/index.ts`.\n\n### MarketplaceClient\n\n`src/client.ts`. Constructed with `new MarketplaceClient(opts)` where `opts` is\n`{ publicClient: PublicClient; addresses?: MarketplaceAddresses }`. Read-only; it never holds keys, and\nexposes `publicClient` and `addresses` as readonly properties.\n\n| Method | Signature | Purpose |\n|---|---|---|\n| `resolveModelHash` | `(input: string) => Hex` | Validate a pinned `0x`+64-hex model hash. Slice 1 requires a full hash; there is no name lookup. |\n| `listProviders` | `(modelHash: Hex) => Promise` | Active providers for a model from `InferenceRouter`, with endpoint, stake, load, and lifetime inference count. |\n| `estimateCost` | `({ modelHash, inputTokens, outputTokens, tier }) => Promise` | Cost in grains via `ComputePricingOracle.estimateJobCost`. |\n| `getCreditBalance` | `(institution: Address) => Promise` | Compute-credit balance from `BulkComputeGateway` (18 decimals); `0n` if the gateway is unset. |\n| `fetchCreditsLogs` | `(institution, fromBlock, toBlock) => Promise` | Raw `CreditsPurchased` and `CreditsSpent` logs for a history view; parse them with `parseCreditsEvents`. |\n| `erc20Allowance` | `(token, owner, spender) => Promise` | ERC-20 allowance; `0n` on read failure. |\n| `erc20BalanceOf` | `(token, account) => Promise` | ERC-20 balance for form validation; `0n` on read failure. |\n| `minPurchaseUsd` | `() => Promise` | `MIN_PURCHASE_USD`, defaulting to `10_000_000` (= $10 at 6 decimals). |\n| `getProviderProfile` | `(address: Address) => Promise` | Full provider profile; `null` if unregistered. |\n\n`ProviderInfo` carries `address`, `endpoint`, `stake`, `currentLoad`, `totalInferences`, and `isActive`.\n`ProviderProfile` adds `isRegistered`, `totalJobsCompleted`, `totalJobsFailed`, `reputationScore` (basis\npoints), `currentActiveJobs`, and `maxConcurrentJobs`.\n\n### X402Client\n\n`src/x402.ts`. Constructed with `new X402Client(opts)`:\n\n```ts\nnew X402Client({\n signer, // Signer (required)\n maxPayWei, // bigint hard cap per request (required; no uncapped mode)\n chainId, // number (required)\n allowedTokens, // Address[] (required, at least one)\n allowedRecipients, // Address[] (optional payee pin)\n fetch, // optional fetch override, for tests\n});\n```\n\n`send(input, init?) => Promise` makes the request and, on an HTTP `402` carrying a valid challenge,\nsigns and retries exactly once. Policy is checked before signing: `chain_id` must match `chainId`; the token\nmust be in `allowedTokens`; the recipient must be in `allowedRecipients` if that list is set; the amount must\nbe at most `maxPayWei`; and `valid_before` must still be in the future. Any failure returns the unsigned\n`402` instead of paying.\n\nThe wire types and codec live in the same file: `PaymentChallenge` (the server's `402` body),\n`PaymentPayload` (the client's signed reply), the `Signer` and `TxSigner` interfaces, `signChallenge`,\n`encodePaymentHeader` and `decodePaymentHeader` (the `x-payment` header, URL-safe base64),\n`paymentToBytes` and `bytesToPayment` (a fixed 233-byte form, `PAYLOAD_BYTES`), and the EIP-712 digest\nhelpers `wsaltDomainSeparator`, `transferWithAuthorizationStructHash`, and `eip712Digest`. The payment is a\n`transferWithAuthorization` on the wrapped-SALT token; browser accounts must sign it via `signEip712`\n(`eth_signTypedData_v4`), not `personal_sign`, because the EIP-191 prefix `personal_sign` adds would break\nrecovery. The server side of this handshake is the [x402 contract path](/contracts/x402).\n\n### Account integration\n\n`src/wallet/`. The directory keeps its historical name; in prose this is the Citrate Keyring integration.\n\n- `CitrateWallet` (`src/wallet/citrate.ts`), a native keystore account. Factories: `createWallet(passphrase)`\n generates a 32-byte key, encrypts it under the passphrase, and persists it to `localStorage` (the\n passphrase must be at least 12 characters); `unlockWallet(passphrase)` reopens it. Instance methods:\n `sign({ hash })`, `sendTransaction(tx)` (which needs `connect(chain, rpcUrl?)` first), `lock()`, and the\n `unlocked` getter. Helpers: `peekKeystoreAddress()`, `hasStoredKeystore()`, and `clearKeystore()` (which\n is unrecoverable).\n- `InjectedSigner` (`src/wallet/injected.ts`), a browser-extension adapter. `InjectedSigner.connect(opts?)`\n requests accounts and asserts or switches the chain. It implements `sign` (`personal_sign`), `signEip712`\n (`eth_signTypedData_v4`, required for x402), and `sendTransaction`, and `hasInjectedProvider()` detects an\n injected provider. It re-checks the chain before every sign and send, closing a time-of-check window.\n- Keystore (`src/wallet/keystore.ts`), Web3 Secret Storage v3: `encryptKeystore` and `decryptKeystore`,\n AES-128-CTR with PBKDF2-SHA256 at 262144 iterations and a constant-time MAC check. The format is portable\n to geth and to other v3 readers.\n\n### ABIs and calldata builders\n\n`src/contracts.ts`, `src/jobs.ts`, `src/credits.ts`, `src/training.ts`. Exported ABIs:\n`computeMarketplaceAbi`, `inferenceRouterAbi`, `computePricingOracleAbi`, `bulkComputeGatewayAbi`,\n`computePoolTrainingAbi`, `erc20Abi`. Addresses and config: `CITRATE_TESTNET_CHAIN_ID` (`40204`),\n`TESTNET_ADDRESSES`, `defaultAddresses()`, and the `MarketplaceAddresses` interface. Enums: `PaymentMethod`\n(`SALT` = 0, `BulkCredits` = 1) and `VerificationTier` (`Commitment` = 0, `ZKProof` = 1 at 1.5×, `TEE` = 2\nat 2.0×). Error type: `MarketplaceError`.\n\n| Builder | Signature | Notes |\n|---|---|---|\n| `postJobCalldata` | `(args: PostJobArgs) => { data: Hex; inputHash: Hex }` | Inference job. `SALT` sends `value = maxPriceGrains`; `BulkCredits` sends `value = 0n`. |\n| `erc20ApproveCalldata` | `(args) => { data: Hex }` | Approve a stablecoin for the credits flow. |\n| `purchaseComputeCreditsCalldata` | `(args) => { data: Hex }` | Buy compute credits; `amount` must be at least `MIN_PURCHASE_USD`. |\n| `requestTrainingJobCalldata` | `(spec) => { data: Hex; requiredEscrow: bigint }` | `requiredEscrow = perEpochBudget × epochCount`. |\n| `joinTrainingJobCalldata`, `closeRecruitmentCalldata`, `commitEpochCalldata`, `challengeStepCalldata`, `reassignCoordinatorCalldata`, `voteChallengeCalldata`, `finalizeTrainingJobCalldata` | various | Training lifecycle calldata. |\n| `parseJobEvents`, `parseCreditsEvents`, `parseTrainingEvents` | `(logs: Log[]) => Event[]` | Decode receipt logs into typed events. |\n| `grainsToSalt`, `grainsToSaltDisplay` | `(grains: bigint) => string` | Display formatters; SALT has 18 decimals, and \"grains\" are its wei. |\n\n`PostJobArgs` carries `modelHash`, `input` (the raw input bytes, which are keccak256-hashed, or a `Hex` that is\nalready the 32-byte keccak256 of the input; providers only bid when `inputHash` is `keccak256(input)`, so a CID\nor other pre-hash is refused), `maxPriceGrains`,\n`tier`, `bidWindowBlocks`, `execWindowBlocks`, and an optional `paymentMethod`.\n\n## Design rationale\n\nThe split between calldata builders and a signer is deliberate. The builders are pure: they take typed\narguments and return `data` plus, where it matters, a derived value such as `inputHash` or `requiredEscrow`,\nand they never touch a key or a network. Signing and sending stay with the account integration, so a key\nlives in exactly one place. `X402Client` is built to be hard to misuse: `maxPayWei` is required so there is\nno path to an uncapped auto-pay, every policy field is checked before a signature exists, and the client\nretries a paid request at most once. The trade is verbosity. You assemble calldata and send it yourself\nrather than calling a single do-everything method, and in return the dangerous step is small, explicit, and\nauditable.\n\n## Failure modes\n\n- `X402Client` refuses to sign and returns the unsigned `402` if the challenge fails any policy check: wrong\n chain, an unallowed token or recipient, an amount over `maxPayWei`, or an expired `valid_before`.\n- An uncapped `X402Client` is not constructible; a missing or malformed `maxPayWei` throws at construction.\n- A browser account that signs an x402 payment with `personal_sign` produces an unrecoverable signature; use\n `signEip712`. The SDK's `signChallenge` already prefers it.\n- `InjectedSigner` re-checks the chain before every sign and send and throws if the provider has switched\n underneath it.\n- `decryptKeystore` compares the MAC in constant time, so a wrong passphrase fails closed without leaking how\n many bytes matched.\n- `CitrateWallet.sendTransaction` throws if `connect(chain, rpcUrl?)` has not been called, and any signing\n call throws once the account is locked.\n\n## Access and canon\n\nCommercial. This is buyer-side integration depth, job, credit, and training calldata, the x402 codec, and\nprovider selection, the implementation work that benefits a contracted integrator and that we gate from\nanonymous scraping. It is not secret: every symbol resolves to public on-chain ABIs and an open package. No\nkeys or mnemonics appear here, and none are hardcoded in the source at the audited SHA. A `CitrateWallet`\nholds only a passphrase-encrypted v3 keystore in `localStorage`; an `InjectedSigner` delegates to the\nextension. Do not paste a key, passphrase, or mnemonic into any example.\n\n## Source and verification\n\n- Source repo: `citrate-sdk-marketplace`.\n- Paths: `src/index.ts` (public surface), `src/client.ts`, `src/x402.ts`, `src/wallet/`, `src/contracts.ts`,\n `src/jobs.ts`, `src/credits.ts`, `src/training.ts`, `src/types.ts`, `src/format.ts`, `AUDIT_TIER.md`.\n- Audited against SHA: `5cc1f39`.\n- Status: Implemented, pre-audit (Tier 1; a full external audit is required before any `v1.0.0` stable\n release).\n"},"/sdks/overview":{"slug":"/sdks/overview","title":"SDKs overview","tier":"public","orgId":null,"sourceKind":"authored","source":"npm @citratelabs + PyPI citrate-labs-sdk (published packages)","syncedSha":"2f8da46","toc":[{"depth":2,"text":"The packages","anchor":"the-packages"},{"depth":2,"text":"What each SDK gives you","anchor":"what-each-sdk-gives-you"},{"depth":2,"text":"Where KYC is required, and where it is not","anchor":"where-kyc-is-required-and-where-it-is-not"},{"depth":2,"text":"Status","anchor":"status"}],"body":"The Citrate SDKs are published and installable today. They run against testnet chain 40204 and are\npre-audit. This page is the honest map of what exists, what each one is for, and exactly where identity\nverification (KYC) is required and where it is not.\n\n## The packages\n\n| Package | Registry | What it is |\n|---|---|---|\n| **`@citratelabs/sdk`** | npm | The canonical TypeScript/JavaScript SDK: the client, the account-abstraction Keyring account, identity/OIDC, entitlements, a memory client, and the inference-gateway client. Start here. |\n| **`@citratelabs/marketplace-sdk`** | npm | The compute-marketplace SDK: contract bindings, ABI decoders, and the x402 payment client. |\n| **`citrate-labs-sdk`** | PyPI | The Python SDK. Non-canonical and opt-in; it may lag the TypeScript SDK. |\n\n`@citratelabs/citrate-js` is **deprecated**. It was renamed to `@citratelabs/sdk` and only re-exports it for\none migration cycle. Use `@citratelabs/sdk`.\n\n```bash\nnpm i @citratelabs/sdk # TypeScript / JavaScript\nnpm i @citratelabs/marketplace-sdk # marketplace + x402\npip install citrate-labs-sdk # Python\n```\n\n## What each SDK gives you\n\n- **`@citratelabs/sdk`**: a `CitrateClient` over the 40204 RPC; the account-abstraction surface (predict a\n counterfactual ERC-4337 Keyring account from an identity, verify it on-chain, request a factory deploy\n permit, no key custody); the OIDC/SIWE identity client; the entitlement capability map; a memory client;\n and an OpenAI-shaped inference-gateway client.\n- **`@citratelabs/marketplace-sdk`**: post and watch marketplace jobs, decode receipts, and pay metered\n endpoints over x402 with a spend-capped client.\n- **`citrate-labs-sdk`**: a Python surface over the same chain and gateway for teams that live in Python.\n\n## Where KYC is required, and where it is not\n\nWe are explicit about this because it is the first thing an integrator needs to know. Identity verification\non Citrate is **in-house (VERI)**, keyed to a real human or institutional account through the authorization\nspine; Citrate keeps a status and two dates, never the documents.\n\n**No KYC required**, build and read freely:\n\n- Installing any SDK and reading the chain (blocks, transactions, receipts, contract state).\n- Predicting and verifying an account-abstraction Keyring account address.\n- Signing in with OIDC or SIWE at the `public` tier.\n- Calling public inference-gateway routes and the public x402 sandbox.\n\n**KYC required** (the `commercial.kyc` entitlement tier), actions that touch money, regulated compute, or\ngated artifacts:\n\n- Selling compute on the marketplace (operator-side).\n- Paid membership grants and validator staking.\n- Downloading tier-gated artifacts from the Commissary, and reading `commercial.kyc` docs.\n- Requesting scoped access to the not-yet-public repositories.\n\nThe gate is an entitlement claim the authority mints from the account's verified status; the SDK reads it and\nfails closed. A call that needs `commercial.kyc` on an unverified account is refused with a clear reason, not\na silent partial success.\n\n## Status\n\nPublished and pre-audit, running against testnet 40204. External audits gate the stable and mainnet releases.\nSee [the JS SDK](/sdks/js), [the Python SDK](/sdks/python), [the marketplace SDK](/sdks/marketplace),\n[identity](/sdks/identity), and [entitlements](/sdks/entitlements) for the per-surface detail.\n"},"/sdks/python":{"slug":"/sdks/python","title":"Python SDK","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-sdk-python/citrate_sdk/","syncedSha":"869694b","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":3,"text":"CitrateClient","anchor":"citrateclient"},{"depth":3,"text":"Economic and education managers","anchor":"economic-and-education-managers"},{"depth":3,"text":"The citrate console script","anchor":"the-citrate-console-script"},{"depth":2,"text":"Identity, entitlements, and gateway","anchor":"identity-entitlements-and-gateway"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"The Python client for Citrate, for data and ML teams who already live in Python. It reads account state,\ndeploys models, runs inference, and drives the economic and education managers against a Citrate node. It\nis a secondary client. The canonical, fullest SDK is the JavaScript one at [JavaScript SDK](/sdks/js); read\nthis page when Python is where your work already is, and expect it to lag.\n\n## What it is\n\n`citrate-labs-sdk` is a thin Python layer over a Citrate node's JSON-RPC. You create one `CitrateClient`,\nbound to an RPC endpoint and, for writes, a private key. The client speaks JSON-RPC to the node and exposes\nthe model, inference, and account methods directly. The economic and education surfaces, learning, staking,\nclassroom, compute, treasury, and farming, are separate manager classes you construct yourself, passing the\nclient's RPC callable and the relevant contract addresses.\n\nTwo facts about maturity belong up front, because the package states them about itself. The SDK is\nnon-canonical: its own `pyproject.toml` description and its `NON_CANONICAL.md` say the canonical SDK is the\nJavaScript `@citratelabs/sdk`, that features land there first, and that Python may lag by an unbounded amount. And\nit is early: the `pyproject.toml` classifier is `Development Status :: 2 - Pre-Alpha`. Treat every surface\nhere as pre-audit and subject to change. New work should start on the [JavaScript SDK](/sdks/js); reach for\nPython when a Python codebase is the reason you are here.\n\n## How to use it\n\n1. Install the package. The distribution is `citrate-labs-sdk`; the import name is `citrate_sdk`.\n\n ```bash\n pip install citrate-labs-sdk\n ```\n\n2. Point the client at a node. Without a private key the client is read-only, which is all you need for\n balances, nonces, and model listings.\n\n ```python\n from citrate_sdk import CitrateClient\n\n client = CitrateClient(rpc_url=\"https://rpc.example\")\n print(\"chain id:\", client.get_chain_id())\n ```\n\n3. Supply a key for writes. Pass it through the environment, never in source. A remote `http://` endpoint\n raises a cleartext-transport warning, because a signed transaction would cross the wire in the clear;\n `localhost` http is allowed silently. Pass `allow_insecure_http=True` only when you mean plaintext to a\n remote host.\n\n ```python\n import os\n from citrate_sdk import CitrateClient\n\n client = CitrateClient(\n rpc_url=os.environ[\"CITRATE_RPC_URL\"],\n private_key=os.environ[\"CITRATE_PRIVATE_KEY\"],\n )\n ```\n\n4. Use a manager when you need an economic or education surface. Managers are not attributes of the client;\n you construct them with the client's `_rpc_call` callable and the addresses they act on.\n\n ```python\n from citrate_sdk import FarmingManager\n\n farming = FarmingManager(\n client._rpc_call,\n contract_addresses={\"farming\": \"0xFarmingContract\"},\n )\n for row in farming.get_leaderboard(count=10):\n print(row)\n ```\n\nThe full install-to-inference walkthrough is in [Python quickstart](/sdks/python/tutorials/python-quickstart).\n\n## Reference\n\nThe surface below is verified against `citrate-sdk-python` at `869694b`. Distribution name `citrate-labs-sdk`,\nversion `0.6.1`, `requires-python >= 3.10`. Runtime dependencies, from `pyproject.toml`: `requests~=2.33`,\n`cryptography>=48.0.1,<51`, `eth-account~=0.9`, `web3~=7.15`, `numpy~=2.0`, `typing-extensions~=4.0`. Optional\nextras: `dev`, `docs`. Configuration reads `CITRATE_RPC_URL`, `CITRATE_CHAIN_ID`, and `CITRATE_PRIVATE_KEY`\nin the examples and tests.\n\n### CitrateClient\n\nSource: `citrate_sdk/client.py` (class `CitrateClient`), exported from `citrate_sdk/__init__.py`.\n\n| Method | Signature | Notes |\n|---|---|---|\n| `__init__` | `(rpc_url=\"http://localhost:8545\", private_key=None, allow_insecure_http=False, timeout=30.0, chain_id=None)` | `client.py`. Read-only without a key. `timeout` is the per-request timeout in seconds; `chain_id` pins the chain every signature is bound to (default 40204). |\n| `get_chain_id()` | `-> int` | `eth_chainId`, `client.py`. |\n| `get_balance(address)` | `-> int` | wei, `eth_getBalance`, `client.py`. |\n| `get_nonce(address)` | `-> int` | pending nonce, `eth_getTransactionCount`, `client.py`. |\n| `deploy_model(model_path, config)` | `-> ModelDeployment` | needs a key; hashes, optionally encrypts, uploads to IPFS, deploys via precompile `0x...0100`, `client.py`. |\n| `inference(model_id, input_data, encrypted=False, max_gas=1000000, recipient_public_key=None)` | `-> InferenceResult` | precompile `0x...0101`; the encrypted path fails closed without `recipient_public_key`, `client.py`. |\n| `get_model_info(model_id)` | `-> Dict` | `citrate_getModel`, raises `ModelNotFoundError`, `client.py`. |\n| `list_models(owner=None, limit=100)` | `-> List[Dict]` | `citrate_listModels`, `client.py`. |\n| `purchase_model_access(model_id, payment_amount)` | `-> str` | Fails closed: always raises `CitrateError` and moves no funds, because no on-chain access-purchase route exists yet. `client.py`. |\n\nSigning binds `chainId` under EIP-155 (`_eip155_chain_id` in `client.py`) so a signature cannot be replayed\non another network. IPFS upload fails closed rather than fabricating a fallback CID (`_upload_to_ipfs` in `client.py`). A\nprivate key creates a `KeyManager` on `client.key_manager` (`citrate_sdk/crypto.py`), which exposes\n`get_address()`, `get_private_key()`, and the ECDH helpers used by encrypted inference.\n\nModel-key threshold sharing uses `KeyManager.encrypt_model_with_key_shares(data, config)`, where `config` is an\n`EncryptionConfig` with `threshold_shares`, `total_shares` and one distinct `share_holder_public_keys` entry per\nshare. Each share is wrapped to its holder and returned for off-chain delivery; `deploy_model` returns them as\n`deployment.key_share_envelopes`, and nothing share-related is written on-chain. Holders open their share with\n`unwrap_key_share(share_record, owner_public_key)` (the first argument is the whole record from\n`key_share_envelopes`, with its `x`, `threshold`, `holder_public_key` and `envelope` fields) and rebuild the key with\n`reconstruct_key_from_shares(shares, threshold)`. `threshold_shares=1` requires\n`allow_single_holder_recovery=True`. IPFS downloads require `expected_sha256` for content addresses that cannot\nverify themselves, unless you pass `verify=False` explicitly.\n\n### Economic and education managers\n\nThese are separate classes, not attributes of `CitrateClient`. Each takes the `_rpc_call` callable, an\noptional `default_account` (required for writes), `gas_limit`, `gas_price`, and the addresses it acts on.\nMost take a `contract_addresses` dict; `StakingManager` and `ClassroomManager` instead take a single\n`staking_address` or `classroom_address`. Writes raise `ConfigurationError` when `default_account` is unset;\nread methods are `eth_call`-only and need no account. Every write first checks `eth_chainId` against the pinned\nchain (40204 by default; pass `chain_id=` to target another Citrate network) and refuses to send on a mismatch.\nUnknown `access`, `tier` or `mode` strings raise `ValueError`. A classroom invite is a key pair:\n`ClassroomManager.create` (and `rotate_invite_code`) registers the invite key's commitment and returns the invite\nsecret in `last_invite_code`. Share that secret with students out of band. A student calls\n`enroll_with_invite(secret)`, which signs an enrolment proof bound to the student's account, and only the invite\nkey and that proof go on-chain. `enroll` is deprecated.\n\n| Manager | Source | Selected methods |\n|---|---|---|\n| `LearningManager` | `learning.py` | `list_pools`, `join_pool`, `leave_pool`, `create_pool`, `get_cycle_status`, `register_for_cycle`, `claim_cycle_reward`, `get_contributions`, `claim_contribution_rewards` |\n| `StakingManager` | `learning.py` | `deposit`, `withdraw`, `claim_withdrawal`, `get_info`, `preview_deposit`, `preview_withdraw`, `get_withdrawal` |\n| `ClassroomManager` | `learning.py` | `create`, `enroll_with_invite`, `unenroll`, `deploy_model`, `remove_model`, `rotate_invite_code`, `get_classroom`, `can_student_access_model`, `get_student_teacher` |\n| `ComputeManager` | `compute.py` | `post_job`, `bid_on_job`, `get_job`, `list_jobs`, `submit_result`, `register_provider`, `get_provider_info`, `heartbeat`, `create_pool`, `join_pool`, `leave_pool`, `get_pools`, `dispute_result`, `get_dispute` |\n| `TreasuryManager` | `treasury.py` | `deposit_stablecoin`, `purchase_compute_credits`, `get_credit_balance`, `estimate_calls_remaining`, `get_treasury_value`, `get_epoch_revenue`, `get_current_epoch`, `get_stablecoin_balance`, `get_total_distributed`, `get_credit_price_usd` |\n| `FarmingManager` | `farming.py` | `get_my_score`, `get_my_share`, `get_leaderboard`, `claim`, `has_claimed`, `get_distribution_info`, `is_in_snapshot`, `get_claimed_amount` |\n\nAll six classes are re-exported from `citrate_sdk/__init__.py`. Shared data types (`LearningPool`,\n`CycleStatus`, `ComputeJob`, `ProviderInfo`, `StakingInfo`, and the rest) live in `citrate_sdk/types.py`;\nmodel types (`ModelConfig`, `ModelDeployment`, `InferenceResult`, `ModelType`, `AccessType`) live in\n`citrate_sdk/models.py`.\n\n### The citrate console script\n\n`pyproject.toml` declares a console script under `[project.scripts]`, `citrate = \"citrate_sdk.cli:main\"`,\nand it now resolves. `citrate_sdk/cli.py` implements `main()` over the standard-library `argparse`, with four\ncommand groups, each reading the generated federation contract so addresses and endpoints are never hand-typed:\n\n- `citrate contract [--section all|chain|aa|identity|gateway|entitlements]`, print the canonical table.\n- `citrate wallet predict (--user-id 0x… | --uuid ) [--verify]`, the embedded smart-account address;\n `--verify` checks it against the on-chain factory.\n- `citrate entitlement capabilities|normalize --tier `, the canonical capability set for a tier.\n- `citrate gateway models|health|chat --model M --message TEXT [--api-key-file PATH]`, the inference gateway.\n The gateway key is read from `$CITRATE_GATEWAY_API_KEY` or `--api-key-file` (a path, or `-` for stdin); it\n is never accepted as a value on `argv`, so it cannot leak through `ps` or shell history (SPY-B-012).\n\nFor example, `citrate wallet predict --user-id 0x4242…4242` prints the same address the on-chain factory\ndeploys. Status for this surface: Implemented (`citrate_sdk/cli.py`).\n\n## Identity, entitlements, and gateway\n\nBeyond the on-chain client, the Python SDK ships the same identity spine, embedded Keyring account, entitlement\ncapabilities, and inference-gateway client as the JavaScript SDK, at full parity. `citrate_sdk.identity`\ncovers OIDC PKCE and SIWE sign-in, hardened ID-token verification, and smart-account address prediction that\nmatches the on-chain factory byte-for-byte; `citrate_sdk.entitlements` is the canonical `normalize_tier` and\ncapability map; `citrate_sdk.gateway` is an OpenAI-compatible client for `infer.citrate.ai`. These use only\nexisting dependencies (`cryptography`, `eth_utils`, `requests`). See [identity and the embedded Keyring account](/sdks/identity)\nand [entitlements](/sdks/entitlements) for the shared reference; the examples there include Python.\n\nThe SDK also ships a memory client for a `citrate-memories` gateway. `MemoryClient` (OIDC REST: recall,\nsearch, neighbors, verify, review, assert) and `ByomMemoryClient` (the bring-your-own-model MCP path), with\n`MemoryError`, live in `citrate_sdk/memory.py` and are re-exported from `citrate_sdk/__init__.py`.\n\n## Design rationale\n\nThe managers are constructed separately, rather than hung off the client, because each binds to a contract\naddress that varies by deployment and that the client has no business knowing by default. Passing\n`_rpc_call` keeps a single transport and a single signing path while letting a caller wire up only the\nsurfaces they use. The harder edges, EIP-155 chain binding on every signature and a fail-closed IPFS upload,\nare there so a transaction signed for Citrate cannot be replayed elsewhere and so a deploy never reports a\nfabricated content hash. The cost of being a secondary client is real: this SDK trails the JavaScript one,\nand we say so rather than paper over it.\n\n## Failure modes\n\n- Encrypted inference without `recipient_public_key` fails closed (`inference` in `client.py`). The symmetric key is\n ECDH-wrapped to the recipient and is never shipped in cleartext on public calldata.\n- A signed transaction binds `chainId` via EIP-155, so it cannot be replayed on a different network.\n- IPFS upload failures propagate; `deploy_model` never invents a fallback CID (`_upload_to_ipfs` in `client.py`).\n- A manager write without `default_account` raises `ConfigurationError`. Reads are `eth_call`-only and need\n no account.\n- A remote `http://` RPC endpoint raises a cleartext-transport warning. Use `https://`, or set\n `allow_insecure_http=True` only when you intend plaintext to a remote host.\n- The `citrate gateway` command refuses a key passed as a plain `argv` value. Supply it through\n `$CITRATE_GATEWAY_API_KEY` or `--api-key-file`, or the command reads no key at all (SPY-B-012).\n\n## Access and canon\n\nPublic. This is open SDK reference a developer needs to build on Citrate, so no tier gate applies. No keys,\nmnemonics, or private endpoints appear here; private keys are supplied at runtime through `private_key=` or\n`CITRATE_PRIVATE_KEY` and must never be committed. The SDK holds no identity data.\n\n## Source and verification\n\n- Source repo: `citrate-sdk-python`.\n- Paths: `citrate_sdk/client.py`, `citrate_sdk/learning.py`, `citrate_sdk/compute.py`,\n `citrate_sdk/treasury.py`, `citrate_sdk/farming.py`, `citrate_sdk/crypto.py`, `citrate_sdk/cli.py`,\n `citrate_sdk/memory.py`, `citrate_sdk/identity/`, `citrate_sdk/entitlements.py`, `citrate_sdk/gateway.py`,\n `citrate_sdk/types.py`, `citrate_sdk/models.py`, `citrate_sdk/__init__.py`, `pyproject.toml`, `examples/`,\n `NON_CANONICAL.md`.\n- Audited against SHA: `869694b`.\n- Status: Implemented, pre-audit, non-canonical (the canonical SDK is the [JavaScript SDK](/sdks/js)). The\n `citrate` console script is Implemented (`citrate_sdk/cli.py`).\n"},"/sdks/tutorials/first-app-with-sdk-js":{"slug":"/sdks/tutorials/first-app-with-sdk-js","title":"Build your first app with the JS SDK","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-sdk-js/src/index.ts","syncedSha":"2f8da46","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Step 1, set up the project","anchor":"step-1-set-up-the-project"},{"depth":3,"text":"Step 2, connect to testnet","anchor":"step-2-connect-to-testnet"},{"depth":3,"text":"Step 3, read chain state","anchor":"step-3-read-chain-state"},{"depth":3,"text":"Step 4, derive your Citrate Keyring account address","anchor":"step-4-derive-your-citrate-keyring-account-address"},{"depth":3,"text":"Step 5, assemble one sponsored write","anchor":"step-5-assemble-one-sponsored-write"},{"depth":3,"text":"Step 6, run it","anchor":"step-6-run-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"A short, end-to-end first project with `@citratelabs/sdk`. You will install the SDK, connect to Citrate Network on\ntestnet, derive a Citrate Keyring account address, read state off the chain, and assemble one sponsored write\nas a UserOperation. It takes about fifteen minutes, and every symbol used is verified against `citrate-sdk-js` at\nSHA `2f8da46`.\n\n## What it is\n\nA small Node or TypeScript script that talks to Citrate testnet, chain id `40204`. The reads need no key and\nno account. The write uses a Citrate Keyring account, a smart-contract account built on Kernel v3, and routes\nthrough the paymaster so the account does not need a funded balance to send its first operation. Where a step\nneeds a value that lives in the network's deployment config rather than the SDK, the page says so plainly\ninstead of inventing one.\n\n## How to use it\n\nYou will need Node 16 or newer (the SDK's declared `engines.node`) and npm.\n\n### Step 1, set up the project\n\n```bash\nmkdir citrate-first-app && cd citrate-first-app\nnpm init -y\nnpm pkg set type=module\nnpm install @citratelabs/sdk\nnpm install -D typescript tsx @types/node\n```\n\nKeep secrets out of source. The read steps need no key. If you later add a key for direct chain writes, put\nit in an environment variable such as `CITRATE_PRIVATE_KEY`, never inline.\n\n### Step 2, connect to testnet\n\nCreate `index.ts`:\n\n```typescript\nimport { CitrateClient, CHAIN_IDS, DEFAULT_RPC_URLS } from '@citratelabs/sdk';\n\nconst client = new CitrateClient({\n // DEFAULT_RPC_URLS[40204] resolves to ['https://rpc.citrate.ai'].\n rpcUrl: DEFAULT_RPC_URLS[CHAIN_IDS.TESTNET],\n});\n\nconsole.log('RPC endpoints:', client.getRpcUrls());\n```\n\nThe constructor validates each RPC URL as it builds, so a typo fails immediately with a `ValidationError`\n(`src/client/CitrateClient.ts`).\n\n### Step 3, read chain state\n\n```typescript\nconst chainId = await client.getChainId(); // -> 40204\nconsole.log('chainId:', chainId);\n\nconst addr = '0x0000000000000000000000000000000000000000'; // any address you want to inspect\nconsole.log('balance (wei):', (await client.getBalance(addr)).toString());\nconsole.log('nonce:', await client.getNonce(addr));\n```\n\nIf `getChainId` returns anything other than `40204`, you are pointed at a different network. These map to\n`getChainId`, `getBalance`, and `getNonce` in `src/client/CitrateClient.ts`. To confirm the node directly,\nsee [the JSON-RPC reference](/chain/rpc).\n\n### Step 4, derive your Citrate Keyring account address\n\nA Citrate Keyring account has the same address on every device, because the address is derived\ndeterministically from the user's id. You can compute it offline, before the account is ever deployed.\n\n```typescript\nimport { aa } from '@citratelabs/sdk';\n\n// The 32-byte AA userId is keccak256(utf8(lowercase uuid)).\nconst userId = aa.uuidToUserId('3f2504e0-4f89-41d3-9a0c-0305e82c3301');\n\n// factory and walletImpl are the deployed AA-stack addresses for chain 40204.\n// They live in the network's deployment config (served by auth.citrate.ai),\n// not in the SDK. Fetch them from your AA config; the shape is aa.CitrateAaConfig.\nconst account = aa.predictWalletAddress(factory, walletImpl, userId);\nconsole.log('account address:', account);\n```\n\n`uuidToUserId` and `predictWalletAddress` are pure functions in `src/aa/address.ts`. The address is the\nCREATE2 address the factory will deploy the Kernel proxy to. Reading state for this address works the same as\nany other: `await client.getBalance(account)`.\n\n### Step 5, assemble one sponsored write\n\nA write through a Citrate Keyring account is a UserOperation: encode the call, build the packed op, hash it,\nsign it, and submit it to the bundler. The paymaster sponsors the gas, so the account needs no balance for its\nfirst op. The pieces below are all real `aa` exports; the deployment addresses (`factory`, `walletImpl`,\n`paymaster`, `entryPoint`, `webauthnValidator`) come from your `aa.CitrateAaConfig`, and the gas figures come\nfrom a bundler estimate.\n\n```typescript\nimport { aa } from '@citratelabs/sdk';\n\n// 1. Encode the call this account should make (target, value, calldata).\nconst callData = aa.encodeExecuteSingle({\n to: '0xTargetContract',\n value: 0n,\n data: '0x', // your function calldata\n});\n\n// 2. Mark the op for paymaster sponsorship. FirstOp is the one-per-account\n// deploy category; Standard counts against the per-user daily cap.\nconst paymasterAndData = aa.packCitratePaymasterAndData({\n paymaster,\n paymasterVerificationGasLimit: 80_000n,\n paymasterPostOpGasLimit: 40_000n,\n category: aa.PaymasterCategory.FirstOp,\n});\n\n// 3. Build the packed UserOperation. The nonce comes from\n// EntryPoint.getNonce(account, key); gas limits come from a bundler estimate.\nconst op = aa.buildPackedUserOp({\n sender: account,\n nonce, // see aa.composeNonce / EntryPoint.getNonce\n initCode, // aa.packInitCode(factory, aa.encodeDeployFor(...)) on first op, else '0x'\n callData,\n callGasLimit: 200_000n,\n verificationGasLimit: 300_000n,\n preVerificationGas: 60_000n,\n maxFeePerGas,\n maxPriorityFeePerGas,\n paymasterAndData,\n});\n\n// 4. Hash and sign. Pick the signer the account is enrolled with.\nconst userOpHash = aa.getUserOpHash(op, entryPoint, BigInt(chainId));\nop.signature = await aa.signUserOpWithPasskey(userOpHash); // passkey path\n// or: op.signature = await aa.signUserOpWithEoa(signer, userOpHash);\n\n// 5. Submit through the bundler and wait for the receipt.\nconst bundler = new aa.BundlerClient(); // defaults to https://bundler.citrate.ai/rpc\nconst hash = await bundler.sendUserOperation(op, entryPoint);\nconst receipt = await bundler.waitForUserOperationReceipt(hash);\nconsole.log('mined:', receipt.success, receipt.receipt.transactionHash);\n```\n\nTwo steps depend on values outside the SDK and are described, not hard-coded. The `nonce` comes from\n`EntryPoint.getNonce(account, key)` composed with `aa.composeNonce`; build the key with\n`aa.validatorNonceKey` for an installed validator or `aa.rootValidatorNonce` for the root. The first op also\nneeds an `initCode` from `aa.packInitCode(factory, aa.encodeDeployFor(...))`, where `encodeDeployFor` carries\na permit signed by `auth.citrate.ai`. Passkey enrollment and signing are covered under\n[passkeys](/aa/passkeys); sponsorship categories and caps under [the paymaster](/aa/paymaster).\n\n### Step 6, run it\n\n```bash\nnpx tsx index.ts\n```\n\nExpect the RPC list, `chainId: 40204`, a balance and nonce for the address you inspected, and your derived\naccount address. The write step runs once you supply the deployment config and a signer.\n\n## Reference\n\nThe symbols this tutorial uses, with their source files in `citrate-sdk-js`.\n\n| Symbol | Source |\n|---|---|\n| `CitrateClient`, `getRpcUrls`, `getChainId`, `getBalance`, `getNonce` | `src/client/CitrateClient.ts` |\n| `CHAIN_IDS`, `DEFAULT_RPC_URLS` | `src/utils/constants.ts` |\n| `aa.uuidToUserId`, `aa.predictWalletAddress` | `src/aa/address.ts` |\n| `aa.encodeExecuteSingle` | `src/aa/kernel.ts` |\n| `aa.buildPackedUserOp`, `aa.getUserOpHash`, `aa.packCitratePaymasterAndData`, `aa.packInitCode`, `aa.encodeDeployFor` | `src/aa/userop.ts` |\n| `aa.signUserOpWithPasskey` | `src/aa/webauthn.ts` |\n| `aa.signUserOpWithEoa` | `src/aa/eoa.ts` |\n| `aa.BundlerClient`, `aa.PaymasterCategory` | `src/aa/bundler.ts`, `src/aa/types.ts` |\n\n## Design rationale\n\nThe reads come first because they need nothing: no key, no account, no funds. That lets a reader confirm they\nare on Citrate Network before they touch anything that costs. The write is shown as a UserOperation rather\nthan a raw signed transaction because that is how a Citrate Keyring account moves, and because the paymaster\ncan cover the first op so a new account is usable immediately. The deployment addresses are deliberately left\nas values you fetch, because they are per-network config and pinning a wrong literal in a tutorial is worse\nthan naming the source.\n\n## Failure modes\n\n- A `chainId` other than `40204` means the client is pointed at a different network. Check `rpcUrl`.\n- `predictWalletAddress` throws if the factory or implementation is the zero address, or if the userId is not\n a 32-byte hex string, so a bad config fails before any chain call.\n- A bundler rejection arrives as a `BundlerRpcError` carrying the `AAxx` code, for example `AA21` for an\n unfunded prefund or `AA31` for a paymaster deposit too low. Read the code; it names the real cause.\n- `signUserOpWithPasskey` throws outside a browser with WebAuthn available. In Node, use the EOA path with\n `signUserOpWithEoa` and an ethers `Signer`.\n\n## Access and canon\n\nPublic. The read steps need no key or credentials and write no state. The write step sources its key from a\npasskey or an environment-held signer, never inline, and the hostnames named (`rpc.citrate.ai`,\n`bundler.citrate.ai`, `auth.citrate.ai`) are public production defaults shipped in the SDK.\n\n## Source and verification\n\n- Source repo: `citrate-sdk-js`, package `@citratelabs/sdk@0.2.0` (`@citratelabs/citrate-js` retained as a deprecated alias).\n- Audited against SHA: `2f8da46`.\n- Symbols verified in `src/client/CitrateClient.ts`, `src/utils/constants.ts`, and `src/aa/{address, kernel,\n userop, webauthn, eoa, bundler, types}.ts`.\n- Status: Implemented (pre-audit). The client reads run against testnet 40204; the `aa` write path is\n Implemented but in development (EW-S1 WP-7) and depends on live bundler and auth infrastructure.\n"},"/sdks/tutorials/post-a-marketplace-job":{"slug":"/sdks/tutorials/post-a-marketplace-job","title":"Post a marketplace job","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-sdk-marketplace/src/index.ts","syncedSha":"5cc1f39","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Step 1, set up clients","anchor":"step-1-set-up-clients"},{"depth":3,"text":"Step 2, pick a model and inspect providers","anchor":"step-2-pick-a-model-and-inspect-providers"},{"depth":3,"text":"Step 3, estimate cost","anchor":"step-3-estimate-cost"},{"depth":3,"text":"Step 4, load an account","anchor":"step-4-load-an-account"},{"depth":3,"text":"Step 5, build job calldata and submit","anchor":"step-5-build-job-calldata-and-submit"},{"depth":3,"text":"Step 6, read back the result events","anchor":"step-6-read-back-the-result-events"},{"depth":3,"text":"Step 7, pay per inference over the gateway (optional)","anchor":"step-7-pay-per-inference-over-the-gateway-optional"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"A runnable, end-to-end walkthrough. You will pick a model, estimate its cost, build job calldata, and submit\nit to Citrate Market on chain id `40204` with the [Marketplace SDK](/sdks/marketplace), then optionally pay\nfor a single inference over the x402 path instead. For integrators building buyer-side. Every API used here\nexists in `citrate-sdk-marketplace` at `5cc1f39`.\n\n## What it is\n\nA short Node and TypeScript script that reads marketplace state with `MarketplaceClient`, loads an account\nwith `CitrateWallet`, builds `postJob` calldata with `postJobCalldata` and submits it, reads back the result\nevents, and, as an alternative, calls a paid gateway route over x402 with `X402Client`. The SDK is `0.1.0`\nand Tier 1, so it is pre-audit; run against testnet only, and use a throwaway account. Never paste a real\nkey, passphrase, or mnemonic. The compute marketplace and its contracts are described under\n[Citrate Market](/compute/pool) and [the compute contracts](/contracts/compute); the per-request payment path is\nthe [x402 contract path](/contracts/x402).\n\n## How to use it\n\nYou will need Node 20 or newer, a Citrate testnet RPC URL, and a small SALT balance on a test account. Then\ninstall the SDK and viem:\n\n```bash\nnpm install @citratelabs/marketplace-sdk viem\n```\n\n### Step 1, set up clients\n\n```ts\nimport { createPublicClient, http } from \"viem\";\nimport { MarketplaceClient, defaultAddresses, CITRATE_TESTNET_CHAIN_ID } from \"@citratelabs/marketplace-sdk\";\n\nconst RPC_URL = process.env.CITRATE_RPC_URL!; // never hardcode\nconst publicClient = createPublicClient({ transport: http(RPC_URL) });\nconst market = new MarketplaceClient({ publicClient, addresses: defaultAddresses() });\n\nconsole.log(\"chainId:\", CITRATE_TESTNET_CHAIN_ID); // 40204\n```\n\n### Step 2, pick a model and inspect providers\n\nSlice 1 requires a fully pinned model hash (`0x` plus 64 hex). Validate it, then list active providers:\n\n```ts\nconst modelHash = market.resolveModelHash(process.env.MODEL_HASH!); // throws if not a pinned hash\nconst providers = await market.listProviders(modelHash);\nconsole.log(`${providers.length} active providers`, providers.map(p => p.endpoint));\n```\n\n### Step 3, estimate cost\n\n```ts\nimport { VerificationTier, grainsToSaltDisplay } from \"@citratelabs/marketplace-sdk\";\n\nconst cost = await market.estimateCost({\n modelHash,\n inputTokens: 1200n,\n outputTokens: 800n,\n tier: VerificationTier.Commitment, // 0 is cheapest; ZKProof is 1.5x, TEE is 2.0x\n});\nconsole.log(\"estimated cost:\", grainsToSaltDisplay(cost)); // e.g. \"0.0123 SALT\"\n```\n\n### Step 4, load an account\n\nUse a passphrase-encrypted keystore that stays in the local key store. The passphrase comes from the\nenvironment, never from source. This is the Citrate Keyring integration; the code symbol is `CitrateWallet`.\n\n```ts\nimport { CitrateWallet } from \"@citratelabs/marketplace-sdk\";\nimport { defineChain } from \"viem\";\n\nconst citrate = defineChain({\n id: 40204,\n name: \"Citrate Testnet\",\n nativeCurrency: { name: \"SALT\", symbol: \"SALT\", decimals: 18 },\n rpcUrls: { default: { http: [RPC_URL] } },\n});\n\n// First run: CitrateWallet.createWallet(process.env.WALLET_PASSPHRASE!) to generate and persist a key.\nconst account = (await CitrateWallet.unlockWallet(process.env.WALLET_PASSPHRASE!))\n .connect(citrate, RPC_URL); // connect() is required before sendTransaction\n```\n\n### Step 5, build job calldata and submit\n\n```ts\nimport { postJobCalldata, PaymentMethod } from \"@citratelabs/marketplace-sdk\";\n\nconst { data, inputHash } = postJobCalldata({\n modelHash,\n input: new TextEncoder().encode(\"Summarize the Citrate whitepaper.\"),\n maxPriceGrains: cost * 2n, // headroom over the estimate\n tier: VerificationTier.Commitment,\n bidWindowBlocks: 20n,\n execWindowBlocks: 200n,\n paymentMethod: PaymentMethod.SALT, // SALT sends value = maxPriceGrains\n});\n\nconst txHash = await account.sendTransaction({\n to: market.addresses.computeMarketplace,\n data,\n value: cost * 2n, // BulkCredits would send value: 0n instead\n});\nconsole.log(\"posted job:\", txHash, \"inputHash:\", inputHash);\n```\n\n### Step 6, read back the result events\n\n```ts\nconst receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });\nconst { parseJobEvents } = await import(\"@citratelabs/marketplace-sdk\");\nconsole.log(parseJobEvents(receipt.logs));\n```\n\n`parseJobEvents` returns typed events: `JobPosted`, `JobAssigned`, and `JobCompleted`, each carrying the job\nid and the relevant addresses.\n\n### Step 7, pay per inference over the gateway (optional)\n\nInstead of posting an on-chain job, you can call a paid gateway route and settle a single request over x402:\n\n```ts\nimport { X402Client } from \"@citratelabs/marketplace-sdk\";\n\nconst x402 = new X402Client({\n signer: account,\n chainId: 40204,\n maxPayWei: cost * 2n, // hard per-request cap\n allowedTokens: [process.env.WSALT_ADDRESS as `0x${string}`],\n});\n\nconst res = await x402.send(`${process.env.GATEWAY_URL}/v1/chat/completions`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ model: process.env.MODEL_ID, messages: [{ role: \"user\", content: \"hi\" }] }),\n});\nconsole.log(await res.json()); // X402Client checks policy, signs the 402 challenge, retries once\n```\n\nThe server side of this handshake is the [x402 contract path](/contracts/x402).\n\n## Reference\n\nThe API used in each step, with its source in `citrate-sdk-marketplace`:\n\n| Step | API | Source |\n|---|---|---|\n| 1 | `MarketplaceClient`, `defaultAddresses`, `CITRATE_TESTNET_CHAIN_ID` | `src/client.ts`, `src/contracts.ts` |\n| 2 | `resolveModelHash`, `listProviders` | `src/client.ts` |\n| 3 | `estimateCost`, `VerificationTier`, `grainsToSaltDisplay` | `src/client.ts`, `src/types.ts`, `src/format.ts` |\n| 4 | `CitrateWallet.unlockWallet`, `connect` | `src/wallet/citrate.ts` |\n| 5 | `postJobCalldata`, `PaymentMethod` | `src/jobs.ts`, `src/types.ts` |\n| 6 | `parseJobEvents` | `src/jobs.ts` |\n| 7 | `X402Client` | `src/x402.ts` |\n\nThe full surface is on the [Marketplace SDK](/sdks/marketplace) reference.\n\n## Failure modes\n\n- `resolveModelHash` throws on anything that is not a pinned `0x`+64-hex hash; slice 1 has no name lookup.\n- `sendTransaction` throws if you have not called `connect(chain, rpcUrl?)` first, or if the account is\n locked.\n- For the `SALT` payment method, `value` must equal `maxPriceGrains`; for `BulkCredits`, `value` must be\n `0n`, or the marketplace rejects the mixed payment.\n- `X402Client` returns the unsigned `402` rather than paying if the challenge is for the wrong chain, an\n unallowed token or recipient, an amount over `maxPayWei`, or an expired window.\n\n## Access and canon\n\nCommercial. This walks buyer-side integration depth, which we gate from anonymous scraping. Every credential,\n`CITRATE_RPC_URL`, `WALLET_PASSPHRASE`, `MODEL_HASH`, `WSALT_ADDRESS`, and `GATEWAY_URL`, is read from the\nenvironment; never hardcode a key, passphrase, or mnemonic. The account key stays encrypted in the local\nWeb3 v3 keystore. Run against testnet `40204` only.\n\n## Source and verification\n\n- Source repo: `citrate-sdk-marketplace`.\n- Built against `src/index.ts`, `src/client.ts`, `src/jobs.ts`, `src/x402.ts`, `src/wallet/`,\n `src/contracts.ts`, `src/types.ts`, `src/format.ts`.\n- Audited against SHA: `5cc1f39`.\n- Status: Implemented, pre-audit (Tier 1). Run on testnet only with a throwaway account.\n"},"/sdks/python/tutorials/python-quickstart":{"slug":"/sdks/python/tutorials/python-quickstart","title":"Python quickstart","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-sdk-python/examples/basic_usage.py","syncedSha":"869694b","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Step 1, install","anchor":"step-1-install"},{"depth":3,"text":"Step 2, set the environment","anchor":"step-2-set-the-environment"},{"depth":3,"text":"Step 3, connect","anchor":"step-3-connect"},{"depth":3,"text":"Step 4, read account state","anchor":"step-4-read-account-state"},{"depth":3,"text":"Step 5, deploy a model","anchor":"step-5-deploy-a-model"},{"depth":3,"text":"Step 6, run inference","anchor":"step-6-run-inference"},{"depth":3,"text":"Step 7, discover models","anchor":"step-7-discover-models"},{"depth":3,"text":"Step 8, use a manager (optional)","anchor":"step-8-use-a-manager-optional"},{"depth":3,"text":"Step 9, clean up","anchor":"step-9-clean-up"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Install the Python SDK, connect to a Citrate node, read account state, then deploy a model and run inference,\nin a few minutes. For Python developers meeting Citrate for the first time. Every call below exists in\n`citrate_sdk/client.py` at `869694b`, and the flow mirrors `examples/basic_usage.py`, which you can run\nas-is from the repo.\n\n## What it is\n\nA copy-paste tour of the working Python surface. The read steps need no key. The write steps, deploy and\ninference, need a funded account. The Python SDK is non-canonical and Pre-Alpha; the canonical SDK is the\n[JavaScript SDK](/sdks/js). A `citrate` console script also ships (`citrate_sdk/cli.py`, with `contract`,\n`wallet`, `entitlement`, and `gateway` commands); this tutorial uses the `CitrateClient` API directly rather\nthan the CLI.\n\n## How to use it\n\nYou will need Python 3.10 or newer (`pyproject.toml` sets `requires-python = \">=3.10\"`), a reachable Citrate\nRPC endpoint, and, for the write steps, a funded account's private key supplied through the environment.\n\n### Step 1, install\n\n```bash\npython -m venv .venv && source .venv/bin/activate\npip install citrate-labs-sdk\n```\n\nThe distribution is `citrate-labs-sdk`; the import name is `citrate_sdk`.\n\n### Step 2, set the environment\n\n```bash\nexport CITRATE_RPC_URL=\"https://rpc.example\" # your node's RPC endpoint\nexport CITRATE_PRIVATE_KEY=\"0x...\" # only needed for writes\n```\n\nKeep any key out of version control. If you have none yet, Step 3 generates one for local experimentation.\n\n### Step 3, connect\n\n```python\nimport os\nfrom citrate_sdk import CitrateClient\nfrom citrate_sdk.crypto import KeyManager\n\nrpc_url = os.getenv(\"CITRATE_RPC_URL\", \"http://localhost:8545\")\nprivate_key = os.getenv(\"CITRATE_PRIVATE_KEY\")\n\n# No key yet? Generate one for local experimentation, then store it securely.\nif not private_key:\n km = KeyManager()\n private_key = km.get_private_key()\n print(\"Generated address:\", km.get_address())\n\nclient = CitrateClient(rpc_url=rpc_url, private_key=private_key)\nprint(\"Connected to chain id:\", client.get_chain_id())\n```\n\n`CitrateClient` warns if you point it at a remote `http://` endpoint, since a signed transaction would cross\nthe wire in the clear. Use `https://`, or pass `allow_insecure_http=True` only when you mean it.\n\n### Step 4, read account state\n\n```python\naddress = client.key_manager.get_address()\nbalance_wei = client.get_balance(address)\nnonce = client.get_nonce(address)\n\nprint(f\"Address: {address}\")\nprint(f\"Balance: {balance_wei / 10**18:.4f} (native units)\")\nprint(f\"Nonce: {nonce}\")\n```\n\nThese three calls, `get_balance`, `get_nonce`, and `get_chain_id`, are read-only and work without a key.\n\n### Step 5, deploy a model\n\n```python\nimport json\nfrom pathlib import Path\nfrom citrate_sdk import ModelConfig, ModelType, AccessType\n\n# A small stand-in model file for the demo.\nmodel_path = Path(\"demo_model.json\")\nmodel_path.write_text(json.dumps({\"type\": \"demo\", \"version\": \"1.0\"}))\n\nconfig = ModelConfig(\n name=\"Demo Classifier\",\n description=\"A simple demo classifier model\",\n model_type=ModelType.CUSTOM,\n access_type=AccessType.PUBLIC,\n encrypted=False,\n)\n\ndeployment = client.deploy_model(model_path, config)\nprint(\"Model ID:\", deployment.model_id)\nprint(\"Tx hash: \", deployment.tx_hash)\nprint(\"IPFS CID:\", deployment.ipfs_hash)\n```\n\n`deploy_model` hashes the file, uploads it to IPFS (failing closed if the upload fails, no fabricated CID),\nthen deploys through the model-deployment precompile. It requires a key.\n\n### Step 6, run inference\n\n```python\nresult = client.inference(\n model_id=deployment.model_id,\n input_data={\"data\": [0.5] * 10, \"format\": \"array\"},\n)\nprint(\"Output: \", result.output_data)\nprint(\"Gas used:\", result.gas_used)\n```\n\nFor encrypted inference, set `encrypted=True` and pass `recipient_public_key=...`. Without it the call fails\nclosed rather than shipping a symmetric key in cleartext on public calldata.\n\n### Step 7, discover models\n\n```python\nfor m in client.list_models(limit=5):\n print(m.get(\"name\", \"Unnamed\"), \"->\", m.get(\"model_id\"))\n```\n\n### Step 8, use a manager (optional)\n\nThe economic and education surfaces are separate classes. Construct one with the client's `_rpc_call`\ncallable, your account, and the addresses it acts on:\n\n```python\nfrom citrate_sdk import StakingManager\n\nstaking = StakingManager(\n client._rpc_call,\n default_account=address,\n staking_address=\"0xStakingContract\",\n)\nprint(\"Staking info:\", staking.get_info(address))\n```\n\nRead methods such as `get_info` and `preview_deposit` need no account; writes such as `deposit` and\n`withdraw` require `default_account`, or they raise `ConfigurationError`.\n\n### Step 9, clean up\n\n```python\nmodel_path.unlink(missing_ok=True)\n```\n\n## Reference\n\nThe calls used above, with their source in `citrate-sdk-python`:\n\n| Call | What it does | Source |\n|---|---|---|\n| `CitrateClient(...)` | bind to an RPC endpoint, optionally a key | `citrate_sdk/client.py:31` |\n| `get_chain_id()` | confirm the network | `citrate_sdk/client.py:103` |\n| `get_balance` / `get_nonce` | read account state | `citrate_sdk/client.py:107`, `:112` |\n| `deploy_model` | hash, IPFS-upload, deploy | `citrate_sdk/client.py:117` |\n| `inference` | run a model call | `citrate_sdk/client.py:192` |\n| `list_models` | list deployed models | `citrate_sdk/client.py:277` |\n| `KeyManager` | generate or load a key | `citrate_sdk/crypto.py` |\n| `StakingManager` | a representative manager | `citrate_sdk/learning.py:461` |\n\n## Failure modes\n\n- A remote `http://` endpoint warns about plaintext transport. Use `https://`, or set\n `allow_insecure_http=True` deliberately.\n- Encrypted inference without `recipient_public_key` fails closed.\n- An IPFS upload failure during `deploy_model` propagates; no fallback CID is invented.\n- A manager write without `default_account` raises `ConfigurationError`.\n- The `citrate gateway` command never takes its key as an `argv` value; supply it through\n `$CITRATE_GATEWAY_API_KEY` or `--api-key-file` (SPY-B-012).\n\n## Access and canon\n\nPublic. The write steps touch state and need a funded account; the read steps do not. No keys appear here:\nthey come from `CITRATE_PRIVATE_KEY` at runtime, or from a locally generated `KeyManager`. Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity.\n\n## Source and verification\n\n- Source repo: `citrate-sdk-python`.\n- Mirrors `examples/basic_usage.py`; APIs in `citrate_sdk/client.py`, `citrate_sdk/crypto.py`,\n `citrate_sdk/learning.py`.\n- Audited against SHA: `869694b`.\n- Status: Implemented, pre-audit, non-canonical (the canonical SDK is the [JavaScript SDK](/sdks/js)). See\n the full surface on the [Python SDK](/sdks/python) reference.\n"},"/security/posture":{"slug":"/security/posture","title":"Security Posture & Audit History","tier":"public","orgId":null,"sourceKind":"authored","source":"internal security control plane; GitHub Security Advisories","toc":[{"depth":2,"text":"Report a vulnerability","anchor":"report-a-vulnerability"},{"depth":2,"text":"How we audit","anchor":"how-we-audit"},{"depth":2,"text":"Prior known issues","anchor":"prior-known-issues"},{"depth":2,"text":"What this page does not contain","anchor":"what-this-page-does-not-contain"}],"body":"Citrate secures real value - a Layer-1, on-chain settlement, embedded-account key custody, and\nmetered compute. This page is the public, standing summary of how we keep it safe: how we\naudit, how to report a vulnerability, and where to read the record of issues we've already\nfixed.\n\n## Report a vulnerability\n\n**Never open a public issue for a security problem.** Report privately:\n\n- **GitHub Private Vulnerability Reporting** - on the affected repository's **Security** tab → *Report a vulnerability* (encrypted, no key exchange).\n- **Email** [security@citrate.ai](mailto:security@citrate.ai). We do not publish a PGP key yet, so send sensitive details through private vulnerability reporting.\n\nFull policy: [`SECURITY.md`](https://github.com/CitrateNetwork/.github/blob/main/SECURITY.md). Bounty scope,\nsafe-harbor rules and testing limits: coming soon.\nWe acknowledge within **72 hours**, triage within **5 business days**, and follow a\n**90-day coordinated disclosure** window.\n\n## How we audit\n\nSecurity is continuous, not a one-time gate. We run an internal **adversarial audit\nprogram** (the Agentile-Audit standard) across the federation on every meaningful change,\nwith a per-repository **tier** that sets the bar a change must clear. This is the same table as the\norg [`SECURITY.md`](https://github.com/CitrateNetwork/.github/blob/main/SECURITY.md):\n\n| Tier | Repositories | Audit policy | Vulnerability handling |\n|---|---|---|---|\n| **Tier 1**: consensus, value, keys, identity | `citrate-chain` (node, contracts, ZK), `citrate-core`, `citrate-identity`, `citrate-inference-gateway`, `citrate-compute-pool`, `citrate-coop`, `citrate-agent-runtime`, `citrate-sdk-js`, `citrate-sdk-python` | Full adversarial audit before every stable release | Coordinated disclosure; a GitHub Security Advisory (with a CVE request) for fixed High and Critical issues in released code |\n| **Tier 3**: docs and content | `citrate-docs`, `.github`, and other content-only repositories | Content review | Triage as documentation corrections, no CVE |\n\nA repository's own `AUDIT_TIER.md` is authoritative for that repository. A public repository without an\n`AUDIT_TIER.md` is handled as Tier 1 for reports.\n\nSupply-chain hardening is in progress: required review and CI checks on every public repository,\nthird-party GitHub Actions pinned to commit SHAs, and signed releases with SBOMs. Current prereleases are\nunsigned.\n\n> **Independent review.** We welcome external audits. No external-firm audit has been completed\n> yet; completed engagements (firm and scope) will be listed here.\n\n## Prior known issues\n\nResolved, disclosable vulnerabilities will be published as **[GitHub Security Advisories](https://github.com/CitrateNetwork/citrate-chain/security/advisories)**\non the affected repository (Security → Advisories), with affected and patched versions. None\nhave been published yet; the first will follow the fixes from the 2026-09 pre-bounty audit.\nSubscribe to a repo's advisories to be notified.\n\nAt a high level, the classes of issue we've found and fixed to date include node **sync\nrobustness** (deep-sync and restart edge cases), **consensus liveness** under adversarial\nload, and hardening from our recurring RM-Q audit passes. **Always run the latest release**\nof node software - older binaries can diverge from the current chain.\n\n## What this page does not contain\n\nTo keep the network safe, we don't publish: unfixed or embargoed vulnerabilities, exploit\ndetails ahead of coordinated disclosure, secrets or credentials, or operational details\n(host addresses, keys, internal topology). Our internal audit trail is kept private for that\nreason; this page and the advisories are its public, secret-free derivative.\n\n---\n\n*Questions about our security program: [security@citrate.ai](mailto:security@citrate.ai).*\n"},"/start/agentile":{"slug":"/start/agentile","title":"A primer on Agentile","tier":"public","orgId":null,"sourceKind":"linked","source":"AGENTILE.md","syncedSha":"a43a354","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Agentile is how the Citrate federation keeps its repositories coherent and auditable. This is a short\norientation; the canonical document is `AGENTILE.md` at the federation root, and where this page and that\nfile differ, the file wins.\n\n## What it is\n\nAgentile is the methodology we use to keep planning, governance, audit posture, and cross-repo state\ncoherent across the federation. It is three things working together:\n\n- a small set of **14 rules**, numbered 0 through 13, that constrain what can ship;\n- a **sprint-driven workflow** that constrains when and how things ship;\n- a **single-source-of-truth** convention: every document is dated and branch-stamped, each topic lives in\n exactly one place, and the agents that do the work follow the same rules and leave the same file-based\n trail a person would.\n\nIt is not Scrum, and it needs no tooling beyond git and Markdown. It exists because the codebase grew from\none repository into many, which opens two failure modes Agentile is built to close: drift between repos,\nand context that lives in someone's head instead of on disk.\n\n## How to use it\n\nIf you are building on Citrate, Agentile is the reason the docs you read are dated, traceable, and\nconsistent with the code. If you are contributing, it is the protocol you follow: read the entry point\nfirst, write the sprint file before the code, keep the test count climbing, and link rather than copy. If\nyou are auditing, it is why the evidence sits on disk rather than in memory. The two pages under\n[methodology](/methodology/rules) give the full statement and the sprint lifecycle.\n\n## Reference\n\nThe 14 rules, numbered 0 through 13, in brief. The full statement is on [the rules page](/methodology/rules).\n\n| # | Rule |\n|---|---|\n| 0 | Read before writing. Start with the entry point and the owners file. |\n| 1 | No mocks, stubs, or TODOs in production paths. |\n| 2 | Test count only goes up within a sprint. |\n| 3 | Audits are immutable; errata go in a follow-up, never an edit. |\n| 4 | The sprint file is the truth, not chat and not memory. |\n| 5 | Rule-12 frontmatter on every document (created, branch, author, status). |\n| 6 | Daily benchmark on the chain-core crates. |\n| 7 | Trace the data source before implementing any endpoint. |\n| 8 | Zero `.unwrap()` in production paths. |\n| 9 | One source of truth per topic. Link, do not copy. |\n| 10 | Authorization before destruction. Force-push, delete, or rotate needs a human's sign-off. |\n| 11 | The federation manifest is canonical. |\n| 12 | Cross-repo dependencies follow the drift map: the drift entry first, then the dependency. |\n| 13 | Visibility flips need sign-off (private to public on a Tier-1 repo). |\n\nThe sprint lifecycle, in brief: work lives in dated files that move from `active/` to `completed/`. A\nsprint opens with a goal, scope, and plan under Rule-12 frontmatter, takes daily updates, turns decisions\ninto ADRs, and bumps the manifest when a change crosses repos. Closing it means moving the file and writing\nthe close note. Completed sprints are immutable. The full choreography is on\n[the workflow page](/methodology/workflow).\n\n## Access and canon\n\nPublic. This is an overview that links to the canonical methodology documents in the federation. No\nsecrets. Internal-only procedures, such as incident response and access review, are gated; see\n[SOPs](/methodology/sops).\n\n## Source and verification\n\nLinked page. The canonical sources are `AGENTILE.md`, `docs/AGENTILE_RULES.md`, and\n`docs/AGENTILE_WORKFLOW.md` at the federation root, at SHA `a43a354`. Per Rule 9, this page summarizes and\nthe canonical files govern. Status: Implemented.\n"},"/start/local-workspace":{"slug":"/start/local-workspace","title":"Set up the federation locally","tier":"public","orgId":null,"sourceKind":"authored","source":".github/setup.sh, .github/AGENTS.md","toc":[{"depth":2,"text":"One command","anchor":"one-command"},{"depth":2,"text":"What you get","anchor":"what-you-get"},{"depth":2,"text":"Build and audit","anchor":"build-and-audit"},{"depth":2,"text":"For AI agents","anchor":"for-ai-agents"},{"depth":2,"text":"Licensing and contributing","anchor":"licensing-and-contributing"}],"body":"Get every public Citrate repository onto your machine in one command, in a single workspace\nyour IDE can open all at once. This is the same folder layout the maintainers use, so git\nintegration, cross-repo references, and the reusable CI all line up.\n\n## One command\n\nYou need the [GitHub CLI](https://cli.github.com) (`gh`), authenticated with `gh auth login`,\nand `git`. Then:\n\n```sh\nmkdir -p citrate-labs && cd citrate-labs\ngh repo clone CitrateNetwork/.github\nbash .github/setup.sh\n```\n\nThat clones and stars every public repository into `citrate-labs/`. To contribute (fork each\nrepo to your account, clone your fork, and set the `upstream` remote) run `bash .github/setup.sh fork`\ninstead. Set `NO_STAR=1` to skip starring, or `SHALLOW=1` for faster history-light clones.\n\nThe script reads the live list of public repositories, so it always matches what is published\nand never touches private ones.\n\n## What you get\n\n```\ncitrate-labs/\n .github/ org profile, reusable CI, AGENTS.md\n citrate-chain/ the L1: GhostDAG consensus, EVM/LVM, contracts, ZK (chain 40204)\n citrate-core/ the desktop node app\n citrate-sdk-js/ citrate-sdk-python/ citrate-sdk-marketplace/\n citrate-docs/ this handbook, plus LOCAL_STACK.md\n ... every other public repo\n```\n\nOpen the `citrate-labs/` folder in your IDE and each repository is its own git root.\n\n## Build and audit\n\n- Bring the stack up with `citrate-docs/LOCAL_STACK.md`; a local devnet is `citrate devnet`.\n- Per repo: read its `README.md` and `AUDIT_TIER.md`, then run its tests: `cargo test` (Rust),\n `npm test` (TypeScript), `forge test` (Solidity), `pytest` (Python).\n\n## For AI agents\n\n`.github/AGENTS.md` is the agent-facing brief: it carries this setup, the open-core licensing\nrules, the DCO sign-off requirement, and how to build and audit each repo. Point your agent at\nit, or at this page, and it can set the whole workspace up and start reviewing code.\n\n## Licensing and contributing\n\nCitrate is open-core: the chain, SDKs, docs, explorer, and agent runtime are Apache-2.0; the\ndesktop app and monetized services are source-available under BUSL-1.1. Sign every commit\n(`git commit -s`), and a merged, qualified contribution earns a free or refunded membership.\nSee [`CONTRIBUTING.md`](https://github.com/CitrateNetwork/.github/blob/main/CONTRIBUTING.md).\n"},"/start/open-source":{"slug":"/start/open-source","title":"Open source and access","tier":"public","orgId":null,"sourceKind":"authored","source":"Citrate open-source policy (owner decision, 2026-07-27)","syncedSha":"~","toc":[{"depth":2,"text":"How the licensing works","anchor":"how-the-licensing-works"},{"depth":2,"text":"What is public","anchor":"what-is-public"},{"depth":2,"text":"What stays private","anchor":"what-stays-private"}],"body":"Citrate is open-core, and the code is public today at\n[github.com/CitrateNetwork](https://github.com/CitrateNetwork). The chain and its application layer are\nalready open - you can read the source, build against it, and reproduce the results now, ahead of the Q2\n2027 mainnet. There is no waiting list and no gate on reading the code.\n\n## How the licensing works\n\nThe repositories ship under a two-tier open-core model, with **Citrate Inc.** as the licensor:\n\n- **Infrastructure is Apache-2.0** - permissively licensed, use it however you like. This is the chain, the\n federated-types crate, the node agent, the bundler, NAT, the cooperative contracts, the agent runtime,\n the JavaScript / Python / marketplace SDKs, the docs, and the explorer.\n- **The application layer is BUSL-1.1** - source-available today (you can read, build, and self-host it for\n non-production use), and it converts to Apache-2.0 on its Change Date. This is the inference gateway, the\n compute pool, the cluster, Citrate Core, Comms, Quorum, Identity, Memories, Citrate Native, the\n air-gapped agent, and Studio.\n\nPublishing the source in the open is the stronger position - for the network and for the people who build\non it - than holding it back. The design is public, the audits land against public code, and the\nBUSL Change Date puts the whole application layer on a path to fully permissive licensing.\n\n## What is public\n\nEverything that ships is public at [github.com/CitrateNetwork](https://github.com/CitrateNetwork). A few\nstarting points:\n\n- **NAT** is the model architecture. Memory-safe Rust, formally specified, Apache-2.0. Read the source and\n reproduce the results.\n- **American Learning Federation (ALF)** is the cooperative that trains NAT through federated learning.\n- **agentile-skills** is the engineering methodology, installable by anyone.\n- **The chain, SDKs, and explorer** are Apache-2.0; **Core, the gateway, and the rest of the app layer**\n are BUSL-1.1 and source-available.\n\n## What stays private\n\nA small set of repositories are deliberately closed, and none of them are the network itself:\n\n- **Client and enterprise repositories** - per-customer and on-premise (Citrate Ground / Homestead) work,\n closed for the customers' sake, not ours.\n- **Security and internal repositories** - the security program's private tracker (its history carries\n material that must not be public) and internal federation tooling.\n\nIf you are building on Citrate, start with the public repositories - you do not need to request access to\nread or build the code.\n\n- Contact: [citrate.ai/contact](https://citrate.ai/contact), or email `hello@citrate.ai`.\n- Already building: the chain, SDKs, NAT, and ALF are public now. Start there.\n"},"/start/primer":{"slug":"/start/primer","title":"A primer on the mental models","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain consensus + economics + keyring","syncedSha":"9d5959e","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":3,"text":"Blue score, not height","anchor":"blue-score-not-height"},{"depth":3,"text":"Confirmation by depth","anchor":"confirmation-by-depth"},{"depth":3,"text":"Merge parents","anchor":"merge-parents"},{"depth":3,"text":"Accounts without a seed phrase","anchor":"accounts-without-a-seed-phrase"},{"depth":3,"text":"SALT, the unit you count in","anchor":"salt-the-unit-you-count-in"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Five ideas make the rest of Almanac click. If your intuition comes from a single-chain world, these are the\nplaces it needs to bend. Read [what Citrate is](/start/what-is-citrate) first if you have not.\n\n## What it is\n\nA short tour of the concepts the deeper pages assume. Each one ends with a pointer to where it is treated\nin full.\n\n### Blue score, not height\n\nOn a single-parent chain, \"block N\" is unambiguous. On a BlockDAG, a block can name several parents, so\nheight alone cannot order the ledger.\n\n- **Height** is roughly how many layers deep a block sits. It is useful, but it is not the ordering key.\n- **Blue score** is the ordering key GhostDAG uses: the cumulative count of a block's blue ancestors, the\n honest-majority-consistent set picked out by the k-cluster rule (k = 18). The tip with the highest blue\n score wins.\n\nBlue score is the DAG's clock. When you read `citrate_getDagStats`, the field that tells you the head is\n`maxBlueScore`, not `height`. Full detail under [Citrate Network](/chain/consensus).\n\n### Confirmation by depth\n\nConfirmation on the testnet is probabilistic: a block gains weight as later blocks build on it, so the\ndeeper a block sits behind the selected tip, the more work a competing branch would need to displace it.\nThere is no protocol finality point today. A finality depth of 100 (`finality_depth`) and a BFT\ncheckpoint committee are specified in the code, but checkpoint finality is not running. The explorer's depth≥100 flag is a display heuristic, not a\nsettlement guarantee. See [consensus, current status](/chain/consensus#current-status).\n\n### Merge parents\n\nA block names one **selected parent**, the place on the chain it builds on, plus zero or more **merge\nparents**, other tips it folds into the order. Merging is how the DAG stays one ledger instead of forking:\na block absorbs its sibling tips rather than orphaning them. Selected parent is where you stand; merge\nparents are the siblings you are folding in. The block's mergeset is then interleaved into the canonical\norder. See [Citrate Network](/chain/consensus).\n\n### Accounts without a seed phrase\n\nCitrate accounts live in **Citrate Keyring**. An account is a smart contract, and you sign in with a\npasskey (WebAuthn over P-256) or an existing key, so there is no seed phrase to lose. Transactions are sent\nas user operations through a bundler, and a sponsor contract can pay the fee, so you can transact with no\nSALT in hand. Recovery is by guardians, two to seven of a set you choose, and Citrate is never one of your\nguardians. The account is a contract, the key is a passkey, and someone else can cover the fee. See\n[Citrate Keyring](/aa/passkeys), [sponsorship](/aa/paymaster), and [guardians](/aa/guardians).\n\n### SALT, the unit you count in\n\nSALT has 18 decimals and a one-trillion supply cap, and it settles fees, block rewards, and staking. Amounts\nin the API are integers in the smallest unit, where 10^18 is one SALT, and supply is minted minus burned,\nheld under the cap. SALT measures the work the network does; it is not a product to hold. See\n[economics](/chain/economics).\n\n## How to use it\n\nPut the five together and a transaction's life reads cleanly. You sign a user operation with a passkey, a\nbundler submits it, the execution layer runs it (often alongside others, in parallel), and it lands in a\nblock that names a selected parent and maybe some merge parents. GhostDAG assigns the block a blue score\nand places it in the total order. Once the block is 100 deep, it is final. The fee and any reward are\ndenominated in SALT. If you can hold that sentence in your head, the rest of Almanac will read easily.\n\n## Reference\n\n| Idea | The key fact | Where it is treated in full |\n|---|---|---|\n| Blue score | ordering key; `maxBlueScore` is the head | [consensus](/chain/consensus) |\n| Finality | probabilistic confirmation; checkpoint finality is specified, not running | [consensus, current status](/chain/consensus#current-status) |\n| Merge parents | one selected parent, many merge parents | [consensus](/chain/consensus) |\n| Citrate Keyring | smart-contract account, passkey, sponsored fees | [Citrate Keyring](/aa/passkeys) |\n| SALT | 18 decimals, 1T cap, settles work | [economics](/chain/economics) |\n\n## Access and canon\n\nPublic. These are conceptual explainers only, with no secrets, keys, or private endpoints. The proofs\nbehind GhostDAG and finality are academic-tier and live on the linked Citrate Network pages.\n\n## Source and verification\n\nThe numbers (k = 18, finality depth 100, SALT 18 decimals and 1T cap, chain id 40204) are verified against\n`citrate-chain` at `9d5959e`: `core/consensus/src/types.rs` and `core/api/src/economics_rpc.rs`, surfaced\nthrough the [consensus](/chain/consensus) and [economics](/chain/economics) pages. Status: Implemented\n(testnet).\n"},"/start/roadmap":{"slug":"/start/roadmap","title":"The roadmap","tier":"public","orgId":null,"sourceKind":"authored","source":"Citrate mission + program plan","syncedSha":"9d5959e","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Where Citrate is today and what comes next. The network is live on testnet now; mainnet is targeted for the\nsecond quarter of 2027. This page is the plan, so it carries a Specified status: the dates are commitments\nwe are working toward, not facts already recorded on a ledger.\n\n## What it is\n\nA plain account of the path from testnet to mainnet, in the order the work lands. We would rather state the\ntimeline and be held to it than imply everything already exists.\n\n## How to use it\n\nRead this to decide when to build, pilot, or wait. If you are writing code, the testnet is ready for you\nnow, and the [tutorials](/start/tutorials/your-first-10-minutes) work against it today. If you run a public\ninstitution, the school program below is the path in. If you are evaluating for production, the mainnet\ntarget is the date to plan around.\n\n## Reference\n\n| Phase | Status | What it means |\n|---|---|---|\n| Testnet, chain id 40204 | Implemented | The public network is live. RPC, the SDKs, contracts, and the model calls in the tutorials all run against it today. |\n| School pilots | Specified, summer 2026 | The first Citrate Schools deployments: US K-12 public schools running on Citrate Ground, free in perpetuity. |\n| Mainnet | Specified, Q2 2027 | The production network. Chain id 40204 carries forward from testnet; it is permanent. |\n\nThe chain id does not change between testnet and mainnet. 40204 is canonical and permanent, so addresses,\ntooling, and integrations you build against testnet carry over.\n\n## Design rationale\n\nWe sequence pilots ahead of mainnet on purpose. Citrate is built for institutions that cannot move their\ndata, and the only honest way to prove on-premise sovereignty works is to run it inside real schools before\nwe ask anyone to depend on the production network. The pilots are the evidence; mainnet is what they earn.\n\n## Access and canon\n\nPublic. This page states the mainnet target (Q2 2027) and the pilot window. Where a phase is still ahead of\nus it is labeled Specified, and where it is live it is labeled Implemented, so the status is never\noverstated.\n\n## Source and verification\n\nThe chain id (40204, permanent) is verified against `citrate-chain` at `9d5959e` (`cli/src/config.rs`). The\ntimeline reflects the program plan and is stated as a target, not a recorded fact. Status: Specified, with\nthe testnet phase Implemented.\n"},"/start/tutorials/your-first-10-minutes":{"slug":"/start/tutorials/your-first-10-minutes","title":"Your first 10 minutes","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain core/api","syncedSha":"e68af83","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":3,"text":"Step 1, confirm you are on Citrate","anchor":"step-1-confirm-you-are-on-citrate"},{"depth":3,"text":"Step 2, read the BlockDAG","anchor":"step-2-read-the-blockdag"},{"depth":3,"text":"Step 3, read the SALT token","anchor":"step-3-read-the-salt-token"},{"depth":3,"text":"Step 4, run a model call on the chain","anchor":"step-4-run-a-model-call-on-the-chain"},{"depth":3,"text":"Step 5, pick your next five minutes","anchor":"step-5-pick-your-next-five-minutes"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Failure modes","anchor":"failure-modes"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"A short, copy-paste tour. You will confirm you are on Citrate, read the BlockDAG, look at the SALT token,\nand run a model call on the chain, then point yourself at the right next page. Every method here exists in\n`citrate-chain`. The read-only steps need no account, no SALT, and no signup.\n\n## What it is\n\nA ten-minute orientation against a live node. Nothing here writes state, so you can run it against any\nCitrate endpoint you can reach without risk.\n\n## How to use it\n\nYou will need a reachable Citrate JSON-RPC endpoint. A local node serves `http://127.0.0.1:8545`. If you do\nnot have one, use the [RPC sandbox](/sandboxes/rpc) instead: same methods, in the browser. You will also\nwant `curl`, and `jq` for readable output.\n\nSet up a small helper so the steps stay short:\n\n```bash\nexport RPC=http://127.0.0.1:8545\n\nrpc () {\n curl -s \"$RPC\" -H 'content-type: application/json' \\\n -d \"{\\\"jsonrpc\\\":\\\"2.0\\\",\\\"id\\\":1,\\\"method\\\":\\\"$1\\\",\\\"params\\\":${2:-[]}}\"\n}\n```\n\n### Step 1, confirm you are on Citrate\n\n```bash\nrpc eth_chainId\n# {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x9d0c\"}\nprintf '%d\\n' 0x9d0c # 40204\n```\n\n`0x9d0c` is 40204, and 40204 is Citrate. Anything else means you are pointed at a different network.\n\n### Step 2, read the BlockDAG\n\n```bash\nrpc citrate_getDagStats | jq\n```\n\n```json\n{\n \"tipsCount\": 3,\n \"maxBlueScore\": 11800,\n \"currentTips\": [\"0x...\", \"0x...\", \"0x...\"],\n \"height\": 12345,\n \"ghostdagParams\": { \"k\": 18, \"maxParents\": 10, \"finalityDepth\": 100 }\n}\n```\n\nTwo things to notice. `maxBlueScore` is the DAG's ordering clock, not `height`. And `tipsCount` above one\nis normal: several tips can exist at once on a BlockDAG, and GhostDAG merges them into a single order. If\nthose words are new, read the [primer](/start/primer).\n\n### Step 3, read the SALT token\n\n```bash\nrpc citrate_getToken | jq\n# { \"name\": \"Citrate\", \"symbol\": \"SALT\", \"decimals\": 18, \"totalSupply\": \"0x...\", \"totalMinted\": \"0x...\" }\n```\n\nSALT has 18 decimals and a one-trillion cap (`TOTAL_SUPPLY` in `core/economics/src/lib.rs`). It is the\nunit fees and rewards are counted in. Detail: [economics](/chain/economics).\n\n### Step 4, run a model call on the chain\n\nCitrate runs inference as a chain operation, not as an outside service you trust. Generate an embedding with\nthe genesis model:\n\n```bash\nrpc citrate_getTextEmbedding '[\"the quick brown fox\"]' | jq '.result | length'\n# 1024\n```\n\nOr rank a short corpus by meaning:\n\n```bash\nrpc citrate_semanticSearch \\\n '[\"best network for AI compute\", [\"a payments network\",\"a substrate for AI compute\",\"a meme coin\"], 1]' | jq\n# [{ \"index\": 1, \"score\": 0.82, \"text\": \"a substrate for AI compute\" }]\n```\n\nA single call accepts at most 256 inputs, which is a denial-of-service guard (`MAX_EMBEDDING_INPUTS`).\n\n### Step 5, pick your next five minutes\n\n| If you want to | Go to |\n|---|---|\n| Understand the words you just saw | [the primer](/start/primer) |\n| See every RPC method | [JSON-RPC reference](/chain/rpc) |\n| Go deeper on these same calls | [call the Citrate RPC](/chain/tutorials/call-citrate-rpc) |\n| Deploy a contract | [deploy with the CLI](/chain/tutorials/deploy-a-contract-with-the-cli) |\n| Get an account with no seed phrase | [sign in with a passkey](/aa/tutorials/sign-in-with-a-passkey) |\n| Learn how the project is run | [the Agentile primer](/start/agentile) |\n\n## Reference\n\nThe methods used above, with their source files in `citrate-chain`:\n\n| Method | What it returns | Source |\n|---|---|---|\n| `eth_chainId` | the chain id, `0x9d0c` | `core/api/src/eth_rpc.rs` |\n| `citrate_getDagStats` | tips, blue score, GhostDAG params | `core/api/src/eth_rpc.rs` |\n| `citrate_getToken` | SALT name, decimals, supply | `core/api/src/economics_rpc.rs` |\n| `citrate_getTextEmbedding` | an embedding vector | `core/api/src/ai_rpc.rs` |\n| `citrate_semanticSearch` | corpus entries ranked by meaning | `core/api/src/ai_rpc.rs` |\n\n## Failure modes\n\n- **`Connection refused`** means no node is listening on `$RPC` (the default is `127.0.0.1:8545`). Use the\n [RPC sandbox](/sandboxes/rpc) instead.\n- **`-32601 Method not found`** is a typo or a method the node does not serve. The chain, DAG, and model\n methods above do not require an economics manager to be configured.\n- **A chain id other than `0x9d0c`** means you are not on Citrate.\n\n## Access and canon\n\nPublic and read-only. No keys or credentials are needed, and nothing here writes state. The example outputs\nare illustrative; exact values depend on the node's current state.\n\n## Source and verification\n\nMethods verified against `citrate-chain` at `e68af83` (`core/api/src/ai_rpc.rs`, `economics_rpc.rs`,\n`eth_rpc.rs`), and surfaced in full on the [JSON-RPC reference](/chain/rpc). Status: Implemented\n(testnet).\n"},"/start/what-is-citrate":{"slug":"/start/what-is-citrate","title":"What Citrate is","tier":"public","orgId":null,"sourceKind":"authored","source":"citrate-chain + Citrate mission","syncedSha":"9d5959e","toc":[{"depth":2,"text":"What it is","anchor":"what-it-is"},{"depth":2,"text":"How to use it","anchor":"how-to-use-it"},{"depth":2,"text":"Reference","anchor":"reference"},{"depth":2,"text":"Design rationale","anchor":"design-rationale"},{"depth":2,"text":"Access and canon","anchor":"access-and-canon"},{"depth":2,"text":"Source and verification","anchor":"source-and-verification"}],"body":"Citrate is a substrate for AI compute. It is the ground that models run on, not a model itself. You bring\nthe data and the weights; Citrate gives them somewhere verifiable to run, on hardware you control, with a\npublic record of the work that anyone you authorize can check.\n\n## What it is\n\nThe network has two halves that work together. The **Citrate Network** is a public ledger: a BlockDAG\nwritten in Rust, live on chain id 40204 in testnet today. **Citrate Ground** is the private half: a school,\na hospital, or a contractor runs Citrate on their own machines, and their data and models stay there. The\npublic ledger only ever sees what an operator chooses to publish.\n\nThree properties hold across the whole network, and the rest of Almanac assumes them:\n\n- **On-premise by default.** Your data and your models stay on your hardware. Publishing anything to the\n public ledger is a deliberate step, taken inside the compliance envelope you set.\n- **Verified participation.** Membership includes identity verification through VERI, Citrate's in-house verification; node and consensus code do not check it.\n Citrate keeps the verification result, not the personal data behind it.\n- **Work, not speculation.** SALT settles the work the network performs. It pays for compute and rewards\n contribution. It is the unit you count in, not a product to hold, and Almanac does not treat it as one.\n\nUnderneath, the Citrate Network is EVM-compatible: existing Solidity, tooling, and signing libraries work\nagainst it. What makes it a substrate for AI rather than a general ledger is that inference, embeddings,\nand verifiable model calls are first-class operations on the chain, not an outside service you have to\ntrust. The consensus that orders all of it is GhostDAG, which is covered in the [primer](/start/primer)\nand in full under [Citrate Network](/chain/consensus).\n\n## How to use it\n\nPick the path that matches why you are here.\n\n1. **You write code.** Read the [primer](/start/primer), then [chain RPC](/chain/rpc) and the\n [JavaScript SDK](/sdks/js). Confirm you are pointed at Citrate, then read the DAG and make your first\n model call in [your first 10 minutes](/start/tutorials/your-first-10-minutes).\n2. **You operate hardware.** Read [run a node](/operators/run-a-node) and [sell compute](/operators/sell-compute).\n A node is how idle GPUs earn SALT on Citrate Market.\n3. **You run models on your own data.** Read [Citrate Ground](/enterprise/federal) and\n [federated learning](/research/learning), where models train across nodes without the data leaving them.\n4. **You are evaluating the network.** Read the [roadmap](/start/roadmap) for the path to mainnet, then the\n [Gradient Papers](/research/gradient-papers) for the research the design rests on.\n\n## Reference\n\nThe surfaces you will meet across Almanac, named once here so the names are familiar later.\n\n| Surface | What it is |\n|---|---|\n| Citrate Network | the public ledger, the BlockDAG and its contracts |\n| Citrate Ground | a private, on-premise instance behind your own firewall |\n| Citrate Market | where compute is bought and sold |\n| Citrate Orchard | the federated-learning surface, where models train across nodes |\n| Citrate Node | the daemon an operator runs to contribute compute |\n| Citrate Keyring | your account, your keys, and account recovery |\n| Citrate Schools | the program giving US K-12 public schools free access in perpetuity |\n\nTo confirm you are talking to Citrate and not another network, ask the node for its chain id:\n\n```bash\ncurl -s http://127.0.0.1:8545 -H 'content-type: application/json' \\\n -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_chainId\",\"params\":[]}'\n# {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x9d0c\"} # 0x9d0c is 40204\n```\n\n## Design rationale\n\nMost networks ask you to move your data to where the compute is. For a hospital or a school district, that\nis a non-starter, and for good reason. Citrate inverts it: the compute is verified and the work is\nrecorded, but the data stays put. That is why Ground is the default and the public ledger is opt-in, and it\nis why participation is identity-checked rather than anonymous. The cost is that joining takes a real-world\nverification step. We think that is the right trade for the institutions Citrate is built to serve.\n\n## Access and canon\n\nPublic. This is the front door, the concepts you need to decide whether to build on Citrate. No secrets,\nkeys, or private endpoints appear here. Deeper pages carry implementation detail and are tiered to the\naudience that needs them.\n\n## Source and verification\n\nChain facts verified against `citrate-chain` at `9d5959e`: chain id 40204 (`eth_chainId` returns `0x9d0c`,\nsee `cli/src/config.rs` and `cli/src/commands/advanced.rs`), GhostDAG parameters in\n`core/consensus/src/types.rs`, SALT supply in `core/api/src/economics_rpc.rs`. The network is live on\ntestnet; mainnet is targeted for Q2 2027, with school pilots the prior summer. Status: Implemented\n(testnet).\n"},"/sdks/api-reference":{"slug":"/sdks/api-reference","title":"SDK API reference (generated)","tier":"public","orgId":null,"sourceKind":"transcluded","source":"scripts/gen-api-refs.mjs","toc":[{"depth":2,"text":"JavaScript, @citratelabs/sdk","anchor":"javascript-citratelabssdk"},{"depth":3,"text":"identity","anchor":"identity"},{"depth":3,"text":"entitlements","anchor":"entitlements"},{"depth":2,"text":"Python, citrate-labs-sdk","anchor":"python-citrate-labs-sdk"},{"depth":3,"text":"citrate_sdk.identity","anchor":"citrate_sdkidentity"},{"depth":3,"text":"citrate_sdk.entitlements","anchor":"citrate_sdkentitlements"},{"depth":3,"text":"citrate_sdk.gateway","anchor":"citrate_sdkgateway"},{"depth":2,"text":"Command line","anchor":"command-line"},{"depth":3,"text":"citrate (Python)","anchor":"citrate-python"}],"body":"This page is generated by `scripts/gen-api-refs.mjs` from the SDK sources on each build, so it\nstays in sync with the code. It is the exported public surface; the narrative reference with\nexamples lives in [identity](/sdks/identity), [entitlements](/sdks/entitlements), and the\n[JavaScript](/sdks/js) and [Python](/sdks/python) pages.\n\nSources: `citrate-sdk-js@328bdea`, `citrate-sdk-python@850b3c1`.\n\n## JavaScript, @citratelabs/sdk\n\n### identity\n\n| Symbol | Kind | Summary |\n|---|---|---|\n| `UserId` | type | A 0x-prefixed 32-byte hex user id (the raw stable identifier the factory salts with) |\n| `uuidToUserId` | function | Derive the raw 32-byte AA userId from an OIDC subject UUID |\n| `addressToUserId` | function | Left-pad a 20-byte EOA address to a 32-byte AA userId (SIWE-keyed principals) |\n| `predictWalletAddress` | function | Predict the counterfactual smart-wallet address for a userId. Pure + offline |\n| `verifyWalletAddressOnChain` | function | Verify the locally-predicted address against the on-chain factory (ground truth) |\n| `generateVerifier` | function | A 43-char base64url verifier (256 bits of entropy) |\n| `challengeFromVerifier` | function | S256 challenge for a verifier |\n| `verifyIdToken` | function | Verify an OIDC ID token and return its (now-trusted) claims. Throws IdTokenError on any failure |\n| `DeployPermit` | interface | A factory deploy permit signed by the authority's identity-signer |\n\n### entitlements\n\n| Symbol | Kind | Summary |\n|---|---|---|\n| `TIERS` | const | The five tiers the authority mints (mirrors citrate-identity `TIERS`) |\n| `CapabilitySet` | interface | What a principal may do. Explicit set membership — never derived from an ordering |\n| `DEFAULT_CAPABILITIES` | const | The canonical default tier→capability map |\n| `normalizeTier` | function | Normalize an entitlement tier value at the trust boundary. Unknown/garbage collapses to |\n| `EntitlementClaimLike` | interface | The minimal shape of the entitlement claim this module reads |\n| `capabilities` | function | Capabilities for a raw tier value (normalized first) |\n| `can` | function | Whether a claim grants a capability. Applies the same fail-safe + role-bypass semantics as |\n\n## Python, citrate-labs-sdk\n\n### citrate_sdk.identity\n\n| Symbol | Kind | Summary |\n|---|---|---|\n| `WalletPredictionError` | class | |\n| `uuid_to_user_id` | def | keccak256(utf8(lowercase(uuid))) — matches the authority's wallet-claims.ts |\n| `address_to_user_id` | def | Left-pad a 20-byte EOA to a 32-byte AA userId (SIWE-keyed principals) |\n| `generate_verifier` | def | A 43-char base64url verifier (256 bits of entropy) |\n| `challenge_from_verifier` | def | |\n| `Pkce` | class | |\n| `create_pkce` | def | |\n| `IdTokenError` | class | |\n| `IdentityError` | class | |\n| `TokenSet` | class | |\n| `UserInfo` | class | |\n| `IdentityClient` | class | |\n\n### citrate_sdk.entitlements\n\n| Symbol | Kind | Summary |\n|---|---|---|\n| `CapabilitySet` | class | |\n| `normalize_tier` | def | Fail-safe: unknown/garbage/non-str collapses to ``public``. Never escalates |\n| `capabilities` | def | |\n\n### citrate_sdk.gateway\n\n| Symbol | Kind | Summary |\n|---|---|---|\n| `GatewayError` | class | |\n| `GatewayClient` | class | |\n\n## Command line\n\n### citrate (Python)\n\n```text\nusage: citrate [-h] {contract,wallet,entitlement,gateway} ...\n\nCitrate SDK command line\n\npositional arguments:\n {contract,wallet,entitlement,gateway}\n contract Print the federation contract table\n wallet Embedded smart-account wallet helpers\n entitlement Entitlement capabilities\n gateway Inference gateway\n\noptional arguments:\n -h, --help show this help message and exit\n```\n"},"/chain/addresses":{"slug":"/chain/addresses","title":"Contract addresses","tier":"public","orgId":null,"sourceKind":"transcluded","source":"citrate-chain/contracts/addresses/40204.json","syncedSha":"0aab474b","toc":[{"depth":2,"text":"Core contracts","anchor":"core-contracts"},{"depth":2,"text":"Membership","anchor":"membership"},{"depth":2,"text":"Account abstraction","anchor":"account-abstraction"},{"depth":2,"text":"Precompiles","anchor":"precompiles"}],"body":"This is the canonical list of contract addresses on chain 40204 (Citrate Network). It is generated\nfrom the federation address book (`citrate-chain/contracts/addresses/40204.json`), the single source of truth\nevery application reads from, and is regenerated after each re-roll or address fan-out. As of the book at\ncommit `0aab474b`, deployed 2026-09-30 00:21:18UTC.\n\nNot every entry in the book is deployed. At block 6929 (2026-09-30T02:15:56Z), 99 of the 99\napplication and account-abstraction entries have code on chain; rows marked **not deployed** have none. A call to a\nnot-deployed address returns empty data, and a value transfer to one succeeds and strands the value, so check the\nstatus column before you send anything. Re-check any address yourself with\n`cast code
--rpc-url https://rpc.citrate.ai`.\n\nThe core and account-abstraction addresses are deterministic (CREATE2 through the genesis factory), so a\nre-roll moves them together and this page moves with them. The membership contracts are the exception (see below). The RPC endpoint is `https://rpc.citrate.ai` and the deployer is `0xa3512bE80ABe86439525a3e5a185884aB0ccb87a`.\n\n## Core contracts\n\n| Contract | Address | Status |\n|---|---|---|\n| `ValidatorRegistry` | `0xBa4aBd4f3fcA5365b2451b4E9662e4Cfd22b3ad5` | deployed |\n| `ModelRegistry` | `0x807cB7eE477Ae58C321cAEd980CEB11D78048e84` | deployed |\n| `WrappedSALT` | `0xAa918302B94a4B0E75E01e019cc6b819B4F7c906` | deployed |\n| `AgentDecisionRegistry` | `0x94A204DaC83C99F5ce2C8ac19fc101C07D8b9A41` | deployed |\n| `SpecRegistry` | `0xf38D10dFb550EE3Bce1332A887adC0D2C75EB642` | deployed |\n| `IPFSIncentives` | `0xc37aB44b145a31E8c458996437080326Fd19e129` | deployed |\n| `X402Facilitator` | `0x7F7b6e8D9Ad0A8b4e6152Df6167E49E48AFB3463` | deployed |\n| `X402Paywall` | `0x13e50000FFFc95D910D42Cd8D679c28F6265a970` | deployed |\n| `LiquidStakingPool` | `0x68Aa320Be609A073fC8ebB7dBCD237270A5DEEb0` | deployed |\n| `ContributionAccounting` | `0x52a47cAF8902D8214d1e246E74aA3Ad099DA47D1` | deployed |\n| `NematocystSlashing` | `0xee9501285F7b3c8Bb99B8aF70F95b402F4D2468b` | deployed |\n| `MarketMakerAllocation` | `0xb5dDD7c5146c240D53Ce6c7e87E5aCB59E4f7351` | deployed |\n| `ModelMarketplace` | `0x5517A9fDD70d503a57898c86eFeaaeEF8FE06413` | deployed |\n| `InferenceRouter` | `0xe1A717f0656b000e33A78B97507Cd0440Ad62570` | deployed |\n| `LoRAFactory` | `0x985036F3441258B8Ff6DDa8a43EA40EEEb1D02A6` | deployed |\n| `LearningPool` | `0xBA5C9c886d65a969d02e40d7FBbADe316AD81E66` | deployed |\n| `LearningCycleManager` | `0x4254d5aeb3Fb90A5038e1bC5E10b30d0021990Bd` | deployed |\n| `ClassroomRegistry` | `0xe2b56b2BFcaeB3c8d14400184eAb01BBC980cC05` | deployed |\n| `MentorMatcher` | `0x05d6a67279972273F23124EF95237956Ef923C05` | deployed |\n| `ComputeVerifier` | `0xA483021adE196D642e6500B2D92c5E077186Ee00` | deployed |\n| `ComputeMarketplace` | `0xE4fD2413d19946E7a8e78733E62C1531139430Bb` | deployed |\n| `ComputePool` | `0x47FFB16216a5431dcF852f70Fe534c556cE807eF` | deployed |\n| `HeartbeatMonitor` | `0x85c1A278ed86169C5087616d879013e9337a7013` | deployed |\n| `DisputeResolution` | `0xea0E6716A8A0bA39DF1d30552CFeC0bBab36D602` | deployed |\n| `ComputePricingOracle` | `0xDfaF0b02846Ac33f1fC753ACC2c1D7D0B2F1aE4e` | deployed |\n| `StablecoinTreasury` | `0x6867F82401F2773bf625887cC5b1FA1d6EAfa352` | deployed |\n| `BulkComputeGateway` | `0x9CF7DdBFbba683a14ceF4Eb0e7934f79ec10C586` | deployed |\n| `TestnetFarmingAccounting` | `0xE19aef1A41b883021222aC7596c7ca96A62C6156` | deployed |\n| `TreasuryGovernor` | `0xe0537e5f14C087EC865E152B9D3356d721E5a24F` | deployed |\n| `ModelAccessControl` | `0x66f78C103D6EE077CD2875C71D676540708d5356` | deployed |\n| `TEEAttestationRegistry` | `0x6693b6FcBc5bf3935bEEB7a21654bDfBd90e84b6` | deployed |\n| `ComputePoolTraining` | `0x1D71814BbC78ae994CA5B6eE9c4b9378178b2B36` | deployed |\n| `InstitutionalVault` | `0x7eAb0072153FB71E292A5e272e6Ac66E3f86D094` | deployed |\n| `ClassroomClusterV1` | `0xF2D989FFA09719aa9ee2020Fbf09aD0924ccb486` | deployed |\n| `EduForwarder` | `0x1531224eECc9dFe1CdcCd80Be1BD35804005F181` | deployed |\n| `BudgetAllocation` | `0x365e98100B879a7A54Dc3FED969C977D21be59A0` | deployed |\n| `CashoutRequest` | `0x6E357EDCfc392bAc92b55e1f39f0FA8FC4e8E03A` | deployed |\n| `AIModelRegistryPortable` | `0xdA30A0408b1690AfA739fB63901a6608547F4dA6` | deployed |\n| `AIInferenceRouterPortable` | `0xb8603904aEBeFefa317D9DeDF60B19A120366715` | deployed |\n| `AILearningCycleCorePortable` | `0x42196F4257E5AfAa9fF92735f88aa0c23fBe8b91` | deployed |\n| `CitrateMemberSBT` | `0xA24aa35fbA269f8755C2173779cc3DBC9690c4C9` | deployed |\n| `MemberBond` | `0x7D6B92757e928ab4207Be3B54166Ecd2C491Aa92` | deployed |\n| `MembershipStakeVaultImpl` | `0x72035977F3Ec295C70e2A734AcbDFfB0C98E6F0b` | deployed |\n| `MembershipStakeVault` | `0x4C0f8b27c509cBA4A32E1Cd2BC5709bBD2699024` | deployed |\n| `SkillRegistry` | `0x2B687899EF4aF05A18F4f36cE1fE9d51c017A97c` | deployed |\n| `InstitutionTreeV1` | `0x028f98faeFeE5FF58cb494E493eD8f636aa3042B` | deployed |\n| `ComplianceRegistry` | `0xa301FA601702B0fb850201182cEF381E312c63ee` | deployed |\n| `AnchorRegistry` | `0x41e0f9A4dCD29C650dc58Ee569BF267fD9ba4817` | deployed |\n| `FacilitySBTImpl` | `0x58ac5816c42a3d293552Fe368C4db53b89edB02c` | deployed |\n| `FacilitySBT` | `0xb266e583A30cb47cFF54d9d7429aD55DC68C5e57` | deployed |\n| `NetworkSBTImpl` | `0x01D34046343a171ec7cd4DB8978adbaB095EF955` | deployed |\n| `NetworkSBT` | `0x823c5031A273a304C5a087a3F414F228907Ad7DB` | deployed |\n| `CitAgentTimelock` | `0xBaC05BC639af6eF107F40fe606f1c4A22b7836A2` | deployed |\n| `OrganizationSBT` | `0xB1Bb65Fc3F2188Ff1209845cBe64eba985461689` | deployed |\n| `AgentSBT` | `0xd16b1ad6e744F3E92223C65F492c35D36ae07c7b` | deployed |\n| `CapsuleRegistry` | `0xb2b1DF947d8064797083CE6024DCe0C64999C79C` | deployed |\n| `CitAgentAnchorRegistry` | `0xB38b0e8b264d828A4e55276033B54800C223De45` | deployed |\n| `BenchmarkRegistry` | `0x84247a5f65370947c792181A3afeD5AC0F452EC8` | deployed |\n| `TenantHierarchy` | `0x7e92a5CbD49659fe594B503b50B26F5BD7060e90` | deployed |\n| `ClassificationRegistry` | `0x37844e433f6E1Df3eBdaa9FdF2cFC44c222f251d` | deployed |\n| `RoleEscalation` | `0xAab258228E85A22C99Cb298915277521eb7049D9` | deployed |\n| `MultiSigEnvelope` | `0x3052Ef8C8d6B71f1fF12703C65b33f29F6627Bbf` | deployed |\n| `AgentDecisionRegistryV2` | `0x678D03b31A77F146b8977E1128c2f57D3e3583F7` | deployed |\n| `ContradictionLedger` | `0xdeB5D07716a20838b1b7287c51bCA00e8d12D20d` | deployed |\n| `QuorumAnchorRegistry` | `0x94Aca73127c7A34d5872A861D0A7C9393030Fa2E` | deployed |\n| `MeetingRegistry` | `0x4B0C7Cf5feF3B6f5A8b042E2E71788450Ec70De4` | deployed |\n| `GovernanceTemplateRegistry` | `0xFF6481c1F532E52aA1EFaaEEF117A29e1F84bC82` | deployed |\n| `GovernanceProtocolFactory` | `0x17c2e4e24e8E041302cBbe8C51A7996949719Df6` | deployed |\n| `PolicyBinding` | `0x2e54Ea789a4EeC4A4e360b9436B04085419a29e9` | deployed |\n| `CapabilityGrant` | `0x1670F43B5eC0cd18088147d4fd7543E51A679d30` | deployed |\n| `VoteAllowance` | `0x2271042A2f4F4783949ee74733A2AbfE0c50018C` | deployed |\n| `Sortition` | `0xB934aE6B6836ad17F2525b6428CcE7A5F7D6F0ec` | deployed |\n| `PartProvenanceRegistry` | `0x60FF23F311E5Cbec702E62aFC99F96F36180347d` | deployed |\n| `SupplierRegistry` | `0x3A40A13EEa4a28Fc3A86E6cc9ab3F4CDF4C85f26` | deployed |\n| `MoqRegistry` | `0x577Fb91D26569820A64bB752d78Da008EF04666d` | deployed |\n| `DefensePrimeFLScopeIndex` | `0x100d4e9Eb591f20Cc39D9A0080F2811ea12CA28E` | deployed |\n| `AppRegistry` | `0xA0C18325Ee5426A26Feff56b3d2F7C6EF8264ad0` | deployed |\n| `CrossOrgIndex` | `0xD62f4A63054e53B1cbE10a1956AA607D65CD2FDd` | deployed |\n| `AuditBundleRegistry` | `0xAc20e8F340c15D990270832650bB76d0805fe473` | deployed |\n| `DefensePrimeComplianceRegistry` | `0xd99dF90C0385ba89be36Bf5FBc42d6bc950920E6` | deployed |\n| `RoleGrantTenantIndex` | `0xC8127F90D5b7B7e88B48Ec7312c2cB5884a3f1D6` | deployed |\n| `EntityRegistry` | `0xE97f4529A0C9e8c81FB32ad72D3a578F92dE4b79` | deployed |\n| `TinaWorkpaperRegistry` | `0xA9E05E9Bd0DDB62CabDcBBe263429a093B21aeC9` | deployed |\n| `CrossOrgEnvelope` | `0x3b2913E078fa859dc10Aa9EA90A6FCF3E1D47480` | deployed |\n| `TripwireRegistry` | `0xaC5e1E599788540a5E97B1258d6bd99c417Ce9eA` | deployed |\n| `SponsorEvidenceRegistry` | `0xc5CA4ae343367995b49570d97759643A289F298D` | deployed |\n| `ReleaseManifestRegistry` | `0x7B955B307c4EbBeA469C40FfeD41AD08a8ECe075` | deployed |\n| `KYCRegistry` | `0x2a45692244dE4A87172061F604b1167D2c7f6C3b` | deployed |\n| `IPFSIncentivesV2` | `0x7747745AA3d78c93993DD1eF6aCD8CC8AFED8aEC` | deployed |\n| `IPFSIncentivesV3` | `0xe016f7655172dCc8039863484F1A8C306553867D` | deployed |\n| `AggregationChallenge` | `0xB5D143dC15dD9C570D198c7156Fe427ccEFc7377` | deployed |\n| `ComputePoolPipeline` | `0xbE60946E62697e99697bdF90b59700D4D76f5E60` | deployed |\n\n## Membership\n\nThe membership soulbound token and stake vault are top-level entries in the book. They are deployed by\nnonce rather than through the CREATE2 factory, so their addresses change at every re-roll; always read them\nfrom the book.\n\n| Contract | Address | Status |\n|---|---|---|\n| `CitrateMemberSBT` | `0xA24aa35fbA269f8755C2173779cc3DBC9690c4C9` | deployed |\n| `MembershipStakeVault` | `0x4C0f8b27c509cBA4A32E1Cd2BC5709bBD2699024` | deployed |\n\n## Account abstraction\n\nThe Citrate Keyring account stack (ERC-4337). See [the Keyring section](/aa/identity) for how these fit\ntogether.\n\n| Contract | Address | Status |\n|---|---|---|\n| `EntryPoint` | `0x97d5391a647429233E202f99231743C53a648f3c` | deployed |\n| `WebAuthnP256Validator` | `0x0f421a99A0b8F6138Dea12F45A523Cb896D09fc7` | deployed |\n| `CitrateECDSAValidator` | `0xD2d35421379Ae5b461e216BFcdD1B7e6a64BBC40` | deployed |\n| `GuardianRecoveryModule` | `0x0A909769160C1945401b8f37a9310d37DbB6a891` | deployed |\n| `CitrateWallet` | `0x2D742B98D867Fc7363F530DD6d756622e4Eb768D` | deployed |\n| `CitrateWalletFactory` | `0x24e2a41E48Fb3d5A054528bF017ebAeC0aC94EFf` | deployed |\n| `CitratePaymaster` | `0x8E65bff91E4c53556E1Cee8b0135ffb09D427E58` | deployed |\n\n## Precompiles\n\nPrecompiles are fixed genesis addresses and do not move across re-rolls.\n\n| Contract | Address | Status |\n|---|---|---|\n| `ModelDeploy` | `0x0000000000000000000000000000000000000100` | precompile (no code by design) |\n| `ModelInference` | `0x0000000000000000000000000000000000000101` | precompile (no code by design) |\n| `BatchInference` | `0x0000000000000000000000000000000000000102` | precompile (no code by design) |\n| `ModelMetadata` | `0x0000000000000000000000000000000000000103` | precompile (no code by design) |\n| `ModelBenchmark` | `0x0000000000000000000000000000000000000105` | precompile (no code by design) |\n| `ModelEncryption` | `0x0000000000000000000000000000000000000106` | precompile (no code by design) |\n| `TensorCommit` | `0x0000000000000000000000000000000000000107` | precompile (no code by design) |\n| `InferenceProofVerify` | `0x0000000000000000000000000000000000000108` | precompile (no code by design) |\n| `MerkleVerifyTensor` | `0x0000000000000000000000000000000000000109` | precompile (no code by design) |\n| `TensorMatmulQ16` | `0x000000000000000000000000000000000000010a` | precompile (no code by design) |\n| `TensorDotQ16` | `0x000000000000000000000000000000000000010b` | precompile (no code by design) |\n| `TensorSoftmaxQ16` | `0x000000000000000000000000000000000000010c` | precompile (no code by design) |\n| `TensorReluQ16` | `0x000000000000000000000000000000000000010d` | precompile (no code by design) |\n| `TensorLinearQ16` | `0x000000000000000000000000000000000000010e` | precompile (no code by design) |\n| `TensorTransposeQ16` | `0x000000000000000000000000000000000000010f` | precompile (no code by design) |\n| `BelnapAggregate` | `0x0000000000000000000000000000000000000110` | precompile (no code by design) |\n| `RoutingInference` | `0x0000000000000000000000000000000000000111` | precompile (no code by design) |\n| `Ed25519Verify` | `0x0000000000000000000000000000000000000120` | precompile (no code by design) |\n| `X402Eip712Verify` | `0x0000000000000000000000000000000000000200` | precompile (no code by design) |\n| `X402TransferAuthVerify` | `0x0000000000000000000000000000000000000201` | precompile (no code by design) |\n| `X402BatchPaymentVerify` | `0x0000000000000000000000000000000000000202` | precompile (no code by design) |\n\n"},"/start/changelog":{"slug":"/start/changelog","title":"Changelog","tier":"public","orgId":null,"sourceKind":"transcluded","source":"mem-gateway memory.recall over the federation memory graph","syncedSha":"live","toc":[],"body":"The changelog draws recent activity from the Citrate memory graph, the same\nsigned, code-anchored knowledge store that powers Ask Almanac. It is regenerated\non every deploy from `memory.recall` across the federation repositories.\n\nLive entries appear here once the docs build can reach the memory gateway. To\nsee current activity in the meantime, ask Ask Almanac what changed recently in a\ngiven area, or browse the source repositories directly.\n"}}; -export const CONTENT_NAV: NavNode[] = [{"id":"grp-start","title":"Start here","tier":"public","kind":"group","children":[{"id":"/start/changelog","title":"Changelog","slug":"/start/changelog","tier":"public","order":90,"kind":"doc"},{"id":"/start/agentile","title":"A primer on Agentile","slug":"/start/agentile","tier":"public","kind":"doc"},{"id":"/start/primer","title":"A primer on the mental models","slug":"/start/primer","tier":"public","kind":"doc"},{"id":"/start/open-source","title":"Open source and access","slug":"/start/open-source","tier":"public","kind":"doc"},{"id":"/start/local-workspace","title":"Set up the federation locally","slug":"/start/local-workspace","tier":"public","kind":"doc"},{"id":"/start/roadmap","title":"The roadmap","slug":"/start/roadmap","tier":"public","kind":"doc"},{"id":"/start/what-is-citrate","title":"What Citrate is","slug":"/start/what-is-citrate","tier":"public","kind":"doc"},{"id":"/start/tutorials/your-first-10-minutes","title":"Your first 10 minutes","slug":"/start/tutorials/your-first-10-minutes","tier":"public","kind":"tutorials"}]},{"id":"grp-core","title":"Citrate Core","tier":"public","kind":"group","children":[{"id":"/core/overview","title":"Citrate Core","slug":"/core/overview","tier":"public","order":1,"kind":"doc"},{"id":"/core/getting-started","title":"Getting started with Citrate Core","slug":"/core/getting-started","tier":"public","order":2,"kind":"doc"},{"id":"/core/tour","title":"A tour of Citrate Core","slug":"/core/tour","tier":"public","order":3,"kind":"doc"},{"id":"/core/run-a-node","title":"Run a node from Citrate Core","slug":"/core/run-a-node","tier":"public","order":4,"kind":"doc"},{"id":"/core/safety","title":"Keys, safety, and safe operation","slug":"/core/safety","tier":"public","order":5,"kind":"doc"},{"id":"/core/for-agents","title":"For agents and headless operators","slug":"/core/for-agents","tier":"public","order":6,"kind":"doc"},{"id":"/core/troubleshooting","title":"Troubleshooting Citrate Core","slug":"/core/troubleshooting","tier":"public","order":7,"kind":"doc"}]},{"id":"grp-chain","title":"Citrate Network","tier":"public","kind":"group","children":[{"id":"/chain/addresses","title":"Contract addresses","slug":"/chain/addresses","tier":"public","order":5,"kind":"doc"},{"id":"/chain/genesis","title":"Chain parameters and genesis","slug":"/chain/genesis","tier":"public","kind":"doc"},{"id":"/chain/consensus","title":"Citrate Consensus, GhostDAG","slug":"/chain/consensus","tier":"public","kind":"doc"},{"id":"/chain/sequencer","title":"Citrate Sequencer, Mempool and Block Building","slug":"/chain/sequencer","tier":"public","kind":"doc"},{"id":"/chain/storage","title":"Citrate Storage, State, RocksDB, Pruning, IPFS Pinning","slug":"/chain/storage","tier":"public","kind":"doc"},{"id":"/chain/bridge","title":"Cross-chain bridge","slug":"/chain/bridge","tier":"public","kind":"doc"},{"id":"/chain/rpc","title":"JSON-RPC reference","slug":"/chain/rpc","tier":"public","kind":"doc"},{"id":"/chain/lvm","title":"LVM, EVM execution and the parallel executor","slug":"/chain/lvm","tier":"public","kind":"doc"},{"id":"/chain/economics","title":"Network economics","slug":"/chain/economics","tier":"public","kind":"doc"},{"id":"/chain/network","title":"Peer-to-peer networking","slug":"/chain/network","tier":"public","kind":"doc"},{"id":"/chain/precompiles","title":"Precompiles, address pages, tensor, x402, q16","slug":"/chain/precompiles","tier":"public","kind":"doc"},{"id":"/chain/cli","title":"The citrate command-line tool","slug":"/chain/cli","tier":"public","kind":"doc"},{"id":"/chain/precompiles-zkp","title":"Verification, inference, and attestation precompiles (summary)","slug":"/chain/precompiles-zkp","tier":"public","kind":"doc"},{"id":"/chain/tutorials/call-citrate-rpc","title":"Call the Citrate RPC","slug":"/chain/tutorials/call-citrate-rpc","tier":"public","kind":"tutorials"},{"id":"/chain/tutorials/deploy-a-contract-with-the-cli","title":"Deploy a contract with the CLI","slug":"/chain/tutorials/deploy-a-contract-with-the-cli","tier":"public","kind":"tutorials"},{"id":"/chain/tutorials/read-the-dag","title":"Read the DAG","slug":"/chain/tutorials/read-the-dag","tier":"public","kind":"tutorials"}]},{"id":"grp-contracts","title":"Smart contracts","tier":"public","kind":"group","children":[{"id":"/contracts/compute","title":"Compute contracts","slug":"/contracts/compute","tier":"public","kind":"doc"},{"id":"/contracts/reference","title":"Contracts reference","slug":"/contracts/reference","tier":"public","kind":"doc"},{"id":"/contracts/economics","title":"Economics contracts","slug":"/contracts/economics","tier":"public","kind":"doc"},{"id":"/contracts/governance","title":"Governance Contracts","slug":"/contracts/governance","tier":"public","kind":"doc"},{"id":"/contracts/models","title":"Model contracts","slug":"/contracts/models","tier":"public","kind":"doc"},{"id":"/contracts/security","title":"Security & Slashing Contracts","slug":"/contracts/security","tier":"public","kind":"doc"},{"id":"/contracts/x402","title":"x402 payment contracts","slug":"/contracts/x402","tier":"public","kind":"doc"},{"id":"/contracts/tutorials/interact-read-only","title":"Interact read-only","slug":"/contracts/tutorials/interact-read-only","tier":"public","kind":"tutorials"},{"id":"/contracts/tutorials/read-a-contract","title":"Read a contract","slug":"/contracts/tutorials/read-a-contract","tier":"public","kind":"tutorials"}]},{"id":"grp-sdks","title":"SDKs & APIs","tier":"public","kind":"group","children":[{"id":"/sdks/bundler","title":"Citrate Bundler","slug":"/sdks/bundler","tier":"public","kind":"doc"},{"id":"/sdks/inference-gateway","title":"Citrate Inference Gateway","slug":"/sdks/inference-gateway","tier":"public","kind":"doc"},{"id":"/sdks/js","title":"Citrate JavaScript SDK","slug":"/sdks/js","tier":"public","kind":"doc"},{"id":"/sdks/entitlements","title":"Entitlements and capabilities","slug":"/sdks/entitlements","tier":"public","kind":"doc"},{"id":"/sdks/identity","title":"Identity and the embedded Keyring account","slug":"/sdks/identity","tier":"public","kind":"doc"},{"id":"/sdks/marketplace","title":"Marketplace SDK","slug":"/sdks/marketplace","tier":"public","kind":"doc"},{"id":"/sdks/python","title":"Python SDK","slug":"/sdks/python","tier":"public","kind":"doc"},{"id":"/sdks/api-reference","title":"SDK API reference (generated)","slug":"/sdks/api-reference","tier":"public","kind":"doc"},{"id":"/sdks/overview","title":"SDKs overview","slug":"/sdks/overview","tier":"public","kind":"doc"},{"id":"/sdks/tutorials/first-app-with-sdk-js","title":"Build your first app with the JS SDK","slug":"/sdks/tutorials/first-app-with-sdk-js","tier":"public","kind":"tutorials"},{"id":"/sdks/tutorials/post-a-marketplace-job","title":"Post a marketplace job","slug":"/sdks/tutorials/post-a-marketplace-job","tier":"public","kind":"tutorials"},{"id":"/sdks/python/tutorials/python-quickstart","title":"Python quickstart","slug":"/sdks/python/tutorials/python-quickstart","tier":"public","kind":"tutorials"}]},{"id":"grp-aa","title":"Citrate Keyring & identity","tier":"public","kind":"group","children":[{"id":"/aa/contracts","title":"Account-abstraction contracts","slug":"/aa/contracts","tier":"public","kind":"doc"},{"id":"/aa/identity","title":"Citrate Identity, the OIDC issuer","slug":"/aa/identity","tier":"public","kind":"doc"},{"id":"/aa/guardians","title":"Guardians and social recovery","slug":"/aa/guardians","tier":"public","kind":"doc"},{"id":"/aa/passkeys","title":"Passkeys and Kernel operations","slug":"/aa/passkeys","tier":"public","kind":"doc"},{"id":"/aa/paymaster","title":"Paymaster and bundler topology","slug":"/aa/paymaster","tier":"public","kind":"doc"},{"id":"/aa/tutorials/sign-in-with-a-passkey","title":"Sign in with a passkey","slug":"/aa/tutorials/sign-in-with-a-passkey","tier":"public","kind":"tutorials"}]},{"id":"grp-compute","title":"Citrate Market","tier":"public","kind":"group","children":[{"id":"/compute/agent-runtime","title":"Citrate Agent Runtime","slug":"/compute/agent-runtime","tier":"public","kind":"doc"},{"id":"/compute/pool","title":"Citrate Compute Pool","slug":"/compute/pool","tier":"public","kind":"doc"},{"id":"/compute/node-agent","title":"Citrate Node","slug":"/compute/node-agent","tier":"public","kind":"doc"},{"id":"/compute/gateway","title":"Deploy the inference gateway","slug":"/compute/gateway","tier":"public","kind":"doc"}]},{"id":"grp-operators","title":"Citrate Node","tier":"public","kind":"group","children":[{"id":"/operators/rewards","title":"Rewards, reputation, and slashing","slug":"/operators/rewards","tier":"public","kind":"doc"},{"id":"/operators/run-a-node","title":"Run a Citrate Node","slug":"/operators/run-a-node","tier":"public","kind":"doc"},{"id":"/operators/sell-compute","title":"Sell compute","slug":"/operators/sell-compute","tier":"public","kind":"doc"},{"id":"/operators/tutorials/become-a-seller","title":"Become a seller","slug":"/operators/tutorials/become-a-seller","tier":"public","kind":"tutorials"}]},{"id":"grp-apps","title":"Apps & dapps","tier":"public","kind":"group","children":[{"id":"/apps/chatbot","title":"Citrate Chat (gasless chat app)","slug":"/apps/chatbot","tier":"public","kind":"doc"},{"id":"/apps/comms","title":"Citrate Comms","slug":"/apps/comms","tier":"public","kind":"doc"},{"id":"/apps/learning-center","title":"Citrate Learning Center","slug":"/apps/learning-center","tier":"public","kind":"doc"},{"id":"/apps/buyer","title":"Citrate Market (buyer)","slug":"/apps/buyer","tier":"public","kind":"doc"},{"id":"/apps/studio","title":"Citrate Studio","slug":"/apps/studio","tier":"public","kind":"doc"},{"id":"/apps/explorer","title":"CitrateScan Explorer","slug":"/apps/explorer","tier":"public","kind":"doc"},{"id":"/apps/dashboard","title":"Learning dashboard","slug":"/apps/dashboard","tier":"public","kind":"doc"},{"id":"/apps/memories","title":"Memrizz (agent-memory DAG and MCP)","slug":"/apps/memories","tier":"public","kind":"doc"},{"id":"/apps/native","title":"The Citrate Keyring desktop app","slug":"/apps/native","tier":"public","kind":"doc"},{"id":"/apps/wallet-extension","title":"The Citrate Keyring extension","slug":"/apps/wallet-extension","tier":"public","kind":"doc"},{"id":"/apps/landing","title":"The Citrate marketing site","slug":"/apps/landing","tier":"public","kind":"doc"},{"id":"/apps/tutorials/install-the-wallet-extension","title":"Install the Citrate Keyring extension","slug":"/apps/tutorials/install-the-wallet-extension","tier":"public","kind":"tutorials"},{"id":"/apps/tutorials/run-the-desktop-wallet","title":"Run the Citrate Keyring desktop app","slug":"/apps/tutorials/run-the-desktop-wallet","tier":"public","kind":"tutorials"},{"id":"/apps/tutorials/explore-a-transaction","title":"Tutorial: Explore a transaction","slug":"/apps/tutorials/explore-a-transaction","tier":"public","kind":"tutorials"}]},{"id":"grp-research","title":"Citrate Orchard","tier":"public","kind":"group","children":[{"id":"/research/atis","title":"ATIS, Analog Token Importance Scoring","slug":"/research/atis","tier":"public","kind":"doc"},{"id":"/research/learning","title":"Citrate Orchard, federated learning cycles","slug":"/research/learning","tier":"public","kind":"doc"},{"id":"/research/paraconsistent","title":"Paraconsistent aggregation, Belnap four-valued logic","slug":"/research/paraconsistent","tier":"public","kind":"doc"},{"id":"/research/bdd","title":"The Gherkin acceptance library","slug":"/research/bdd","tier":"public","kind":"doc"},{"id":"/research/gradient-papers","title":"The Gradient Papers (v3)","slug":"/research/gradient-papers","tier":"public","kind":"doc"},{"id":"/research/mentorship","title":"The Mentorship Protocol","slug":"/research/mentorship","tier":"public","kind":"doc"},{"id":"/research/verifiable-inference","title":"The substrate of verifiable inference","slug":"/research/verifiable-inference","tier":"public","kind":"doc"},{"id":"/research/tla","title":"The TLA+ formal specification corpus","slug":"/research/tla","tier":"public","kind":"doc"},{"id":"/research/tutorials/reproduce-a-learning-round","title":"Reproduce a learning round","slug":"/research/tutorials/reproduce-a-learning-round","tier":"public","kind":"tutorials"}]},{"id":"grp-enterprise","title":"Enterprise & Citrate Ground","tier":"public","kind":"group","children":[{"id":"/enterprise/compliance-full","title":"Compliance Posture, Full Package (Gated)","slug":"/enterprise/compliance-full","tier":"public","kind":"doc"},{"id":"/enterprise/compliance","title":"Compliance posture, public and sanitized","slug":"/enterprise/compliance","tier":"public","kind":"doc"},{"id":"/enterprise/dpa","title":"Data Processing Agreement (Gated)","slug":"/enterprise/dpa","tier":"public","kind":"doc"},{"id":"/enterprise/federal","title":"Federal and On-Prem Isolation (Gated)","slug":"/enterprise/federal","tier":"public","kind":"doc"},{"id":"/enterprise/procurement","title":"Procurement, how to buy Citrate","slug":"/enterprise/procurement","tier":"public","kind":"doc"},{"id":"/enterprise/questionnaires","title":"Security Questionnaires, SIG and CAIQ (Gated)","slug":"/enterprise/questionnaires","tier":"public","kind":"doc"}]},{"id":"grp-methodology","title":"Methodology","tier":"public","kind":"group","children":[{"id":"/methodology/sops","title":"Standard operating procedures","slug":"/methodology/sops","tier":"public","kind":"doc"},{"id":"/methodology/rules","title":"The 13 Agentile rules","slug":"/methodology/rules","tier":"public","kind":"doc"},{"id":"/methodology/workflow","title":"The Agentile sprint workflow","slug":"/methodology/workflow","tier":"public","kind":"doc"}]},{"id":"grp-security","title":"security","tier":"public","kind":"group","children":[{"id":"/security/posture","title":"Security Posture & Audit History","slug":"/security/posture","tier":"public","kind":"doc"}]}]; +export const CONTENT_NAV: NavNode[] = [{"id":"grp-start","title":"Start here","tier":"public","kind":"group","children":[{"id":"/start/changelog","title":"Changelog","slug":"/start/changelog","tier":"public","order":90,"kind":"doc"},{"id":"/start/agentile","title":"A primer on Agentile","slug":"/start/agentile","tier":"public","kind":"doc"},{"id":"/start/primer","title":"A primer on the mental models","slug":"/start/primer","tier":"public","kind":"doc"},{"id":"/start/open-source","title":"Open source and access","slug":"/start/open-source","tier":"public","kind":"doc"},{"id":"/start/local-workspace","title":"Set up the federation locally","slug":"/start/local-workspace","tier":"public","kind":"doc"},{"id":"/start/roadmap","title":"The roadmap","slug":"/start/roadmap","tier":"public","kind":"doc"},{"id":"/start/what-is-citrate","title":"What Citrate is","slug":"/start/what-is-citrate","tier":"public","kind":"doc"},{"id":"/start/tutorials/your-first-10-minutes","title":"Your first 10 minutes","slug":"/start/tutorials/your-first-10-minutes","tier":"public","kind":"tutorials"}]},{"id":"grp-core","title":"Citrate Core","tier":"public","kind":"group","children":[{"id":"/core/overview","title":"Citrate Core","slug":"/core/overview","tier":"public","order":1,"kind":"doc"},{"id":"/core/getting-started","title":"Getting started with Citrate Core","slug":"/core/getting-started","tier":"public","order":2,"kind":"doc"},{"id":"/core/tour","title":"A tour of Citrate Core","slug":"/core/tour","tier":"public","order":3,"kind":"doc"},{"id":"/core/run-a-node","title":"Run a node from Citrate Core","slug":"/core/run-a-node","tier":"public","order":4,"kind":"doc"},{"id":"/core/safety","title":"Keys, safety, and safe operation","slug":"/core/safety","tier":"public","order":5,"kind":"doc"},{"id":"/core/for-agents","title":"For agents and headless operators","slug":"/core/for-agents","tier":"public","order":6,"kind":"doc"},{"id":"/core/troubleshooting","title":"Troubleshooting Citrate Core","slug":"/core/troubleshooting","tier":"public","order":7,"kind":"doc"},{"id":"/core/hermes","title":"Hermes, the agent in Citrate Core","slug":"/core/hermes","tier":"public","order":8,"kind":"doc"},{"id":"/core/node-mcp","title":"Use your node from another agent (node MCP server)","slug":"/core/node-mcp","tier":"public","order":9,"kind":"doc"},{"id":"/core/skills","title":"Hermes skills, and how Hermes learns","slug":"/core/skills","tier":"public","order":10,"kind":"doc"},{"id":"/core/personas","title":"Hermes personas and tracks","slug":"/core/personas","tier":"public","order":11,"kind":"doc"},{"id":"/core/fleet-wizard","title":"Connect your machines (fleet wizard)","slug":"/core/fleet-wizard","tier":"public","order":12,"kind":"doc"}]},{"id":"grp-chain","title":"Citrate Network","tier":"public","kind":"group","children":[{"id":"/chain/addresses","title":"Contract addresses","slug":"/chain/addresses","tier":"public","order":5,"kind":"doc"},{"id":"/chain/genesis","title":"Chain parameters and genesis","slug":"/chain/genesis","tier":"public","kind":"doc"},{"id":"/chain/consensus","title":"Citrate Consensus, GhostDAG","slug":"/chain/consensus","tier":"public","kind":"doc"},{"id":"/chain/sequencer","title":"Citrate Sequencer, Mempool and Block Building","slug":"/chain/sequencer","tier":"public","kind":"doc"},{"id":"/chain/storage","title":"Citrate Storage, State, RocksDB, Pruning, IPFS Pinning","slug":"/chain/storage","tier":"public","kind":"doc"},{"id":"/chain/bridge","title":"Cross-chain bridge","slug":"/chain/bridge","tier":"public","kind":"doc"},{"id":"/chain/rpc","title":"JSON-RPC reference","slug":"/chain/rpc","tier":"public","kind":"doc"},{"id":"/chain/lvm","title":"LVM, EVM execution and the parallel executor","slug":"/chain/lvm","tier":"public","kind":"doc"},{"id":"/chain/economics","title":"Network economics","slug":"/chain/economics","tier":"public","kind":"doc"},{"id":"/chain/network","title":"Peer-to-peer networking","slug":"/chain/network","tier":"public","kind":"doc"},{"id":"/chain/precompiles","title":"Precompiles, address pages, tensor, x402, q16","slug":"/chain/precompiles","tier":"public","kind":"doc"},{"id":"/chain/cli","title":"The citrate command-line tool","slug":"/chain/cli","tier":"public","kind":"doc"},{"id":"/chain/precompiles-zkp","title":"Verification, inference, and attestation precompiles (summary)","slug":"/chain/precompiles-zkp","tier":"public","kind":"doc"},{"id":"/chain/tutorials/call-citrate-rpc","title":"Call the Citrate RPC","slug":"/chain/tutorials/call-citrate-rpc","tier":"public","kind":"tutorials"},{"id":"/chain/tutorials/deploy-a-contract-with-the-cli","title":"Deploy a contract with the CLI","slug":"/chain/tutorials/deploy-a-contract-with-the-cli","tier":"public","kind":"tutorials"},{"id":"/chain/tutorials/read-the-dag","title":"Read the DAG","slug":"/chain/tutorials/read-the-dag","tier":"public","kind":"tutorials"}]},{"id":"grp-contracts","title":"Smart contracts","tier":"public","kind":"group","children":[{"id":"/contracts/compute","title":"Compute contracts","slug":"/contracts/compute","tier":"public","kind":"doc"},{"id":"/contracts/reference","title":"Contracts reference","slug":"/contracts/reference","tier":"public","kind":"doc"},{"id":"/contracts/economics","title":"Economics contracts","slug":"/contracts/economics","tier":"public","kind":"doc"},{"id":"/contracts/governance","title":"Governance Contracts","slug":"/contracts/governance","tier":"public","kind":"doc"},{"id":"/contracts/models","title":"Model contracts","slug":"/contracts/models","tier":"public","kind":"doc"},{"id":"/contracts/security","title":"Security & Slashing Contracts","slug":"/contracts/security","tier":"public","kind":"doc"},{"id":"/contracts/x402","title":"x402 payment contracts","slug":"/contracts/x402","tier":"public","kind":"doc"},{"id":"/contracts/tutorials/interact-read-only","title":"Interact read-only","slug":"/contracts/tutorials/interact-read-only","tier":"public","kind":"tutorials"},{"id":"/contracts/tutorials/read-a-contract","title":"Read a contract","slug":"/contracts/tutorials/read-a-contract","tier":"public","kind":"tutorials"}]},{"id":"grp-sdks","title":"SDKs & APIs","tier":"public","kind":"group","children":[{"id":"/sdks/bundler","title":"Citrate Bundler","slug":"/sdks/bundler","tier":"public","kind":"doc"},{"id":"/sdks/inference-gateway","title":"Citrate Inference Gateway","slug":"/sdks/inference-gateway","tier":"public","kind":"doc"},{"id":"/sdks/js","title":"Citrate JavaScript SDK","slug":"/sdks/js","tier":"public","kind":"doc"},{"id":"/sdks/entitlements","title":"Entitlements and capabilities","slug":"/sdks/entitlements","tier":"public","kind":"doc"},{"id":"/sdks/identity","title":"Identity and the embedded Keyring account","slug":"/sdks/identity","tier":"public","kind":"doc"},{"id":"/sdks/marketplace","title":"Marketplace SDK","slug":"/sdks/marketplace","tier":"public","kind":"doc"},{"id":"/sdks/python","title":"Python SDK","slug":"/sdks/python","tier":"public","kind":"doc"},{"id":"/sdks/api-reference","title":"SDK API reference (generated)","slug":"/sdks/api-reference","tier":"public","kind":"doc"},{"id":"/sdks/overview","title":"SDKs overview","slug":"/sdks/overview","tier":"public","kind":"doc"},{"id":"/sdks/tutorials/first-app-with-sdk-js","title":"Build your first app with the JS SDK","slug":"/sdks/tutorials/first-app-with-sdk-js","tier":"public","kind":"tutorials"},{"id":"/sdks/tutorials/post-a-marketplace-job","title":"Post a marketplace job","slug":"/sdks/tutorials/post-a-marketplace-job","tier":"public","kind":"tutorials"},{"id":"/sdks/python/tutorials/python-quickstart","title":"Python quickstart","slug":"/sdks/python/tutorials/python-quickstart","tier":"public","kind":"tutorials"}]},{"id":"grp-aa","title":"Citrate Keyring & identity","tier":"public","kind":"group","children":[{"id":"/aa/contracts","title":"Account-abstraction contracts","slug":"/aa/contracts","tier":"public","kind":"doc"},{"id":"/aa/identity","title":"Citrate Identity, the OIDC issuer","slug":"/aa/identity","tier":"public","kind":"doc"},{"id":"/aa/guardians","title":"Guardians and social recovery","slug":"/aa/guardians","tier":"public","kind":"doc"},{"id":"/aa/passkeys","title":"Passkeys and Kernel operations","slug":"/aa/passkeys","tier":"public","kind":"doc"},{"id":"/aa/paymaster","title":"Paymaster and bundler topology","slug":"/aa/paymaster","tier":"public","kind":"doc"},{"id":"/aa/tutorials/sign-in-with-a-passkey","title":"Sign in with a passkey","slug":"/aa/tutorials/sign-in-with-a-passkey","tier":"public","kind":"tutorials"}]},{"id":"grp-compute","title":"Citrate Market","tier":"public","kind":"group","children":[{"id":"/compute/agent-runtime","title":"Citrate Agent Runtime","slug":"/compute/agent-runtime","tier":"public","kind":"doc"},{"id":"/compute/pool","title":"Citrate Compute Pool","slug":"/compute/pool","tier":"public","kind":"doc"},{"id":"/compute/node-agent","title":"Citrate Node","slug":"/compute/node-agent","tier":"public","kind":"doc"},{"id":"/compute/gateway","title":"Deploy the inference gateway","slug":"/compute/gateway","tier":"public","kind":"doc"}]},{"id":"grp-operators","title":"Citrate Node","tier":"public","kind":"group","children":[{"id":"/operators/rewards","title":"Rewards, reputation, and slashing","slug":"/operators/rewards","tier":"public","kind":"doc"},{"id":"/operators/run-a-node","title":"Run a Citrate Node","slug":"/operators/run-a-node","tier":"public","kind":"doc"},{"id":"/operators/sell-compute","title":"Sell compute","slug":"/operators/sell-compute","tier":"public","kind":"doc"},{"id":"/operators/tutorials/become-a-seller","title":"Become a seller","slug":"/operators/tutorials/become-a-seller","tier":"public","kind":"tutorials"}]},{"id":"grp-apps","title":"Apps & dapps","tier":"public","kind":"group","children":[{"id":"/apps/chatbot","title":"Citrate Chat (gasless chat app)","slug":"/apps/chatbot","tier":"public","kind":"doc"},{"id":"/apps/comms","title":"Citrate Comms","slug":"/apps/comms","tier":"public","kind":"doc"},{"id":"/apps/learning-center","title":"Citrate Learning Center","slug":"/apps/learning-center","tier":"public","kind":"doc"},{"id":"/apps/buyer","title":"Citrate Market (buyer)","slug":"/apps/buyer","tier":"public","kind":"doc"},{"id":"/apps/studio","title":"Citrate Studio","slug":"/apps/studio","tier":"public","kind":"doc"},{"id":"/apps/explorer","title":"CitrateScan Explorer","slug":"/apps/explorer","tier":"public","kind":"doc"},{"id":"/apps/dashboard","title":"Learning dashboard","slug":"/apps/dashboard","tier":"public","kind":"doc"},{"id":"/apps/memories","title":"Memrizz (agent-memory DAG and MCP)","slug":"/apps/memories","tier":"public","kind":"doc"},{"id":"/apps/native","title":"The Citrate Keyring desktop app","slug":"/apps/native","tier":"public","kind":"doc"},{"id":"/apps/wallet-extension","title":"The Citrate Keyring extension","slug":"/apps/wallet-extension","tier":"public","kind":"doc"},{"id":"/apps/landing","title":"The Citrate marketing site","slug":"/apps/landing","tier":"public","kind":"doc"},{"id":"/apps/tutorials/install-the-wallet-extension","title":"Install the Citrate Keyring extension","slug":"/apps/tutorials/install-the-wallet-extension","tier":"public","kind":"tutorials"},{"id":"/apps/tutorials/run-the-desktop-wallet","title":"Run the Citrate Keyring desktop app","slug":"/apps/tutorials/run-the-desktop-wallet","tier":"public","kind":"tutorials"},{"id":"/apps/tutorials/explore-a-transaction","title":"Tutorial: Explore a transaction","slug":"/apps/tutorials/explore-a-transaction","tier":"public","kind":"tutorials"}]},{"id":"grp-research","title":"Citrate Orchard","tier":"public","kind":"group","children":[{"id":"/research/atis","title":"ATIS, Analog Token Importance Scoring","slug":"/research/atis","tier":"public","kind":"doc"},{"id":"/research/learning","title":"Citrate Orchard, federated learning cycles","slug":"/research/learning","tier":"public","kind":"doc"},{"id":"/research/paraconsistent","title":"Paraconsistent aggregation, Belnap four-valued logic","slug":"/research/paraconsistent","tier":"public","kind":"doc"},{"id":"/research/bdd","title":"The Gherkin acceptance library","slug":"/research/bdd","tier":"public","kind":"doc"},{"id":"/research/gradient-papers","title":"The Gradient Papers (v3)","slug":"/research/gradient-papers","tier":"public","kind":"doc"},{"id":"/research/mentorship","title":"The Mentorship Protocol","slug":"/research/mentorship","tier":"public","kind":"doc"},{"id":"/research/verifiable-inference","title":"The substrate of verifiable inference","slug":"/research/verifiable-inference","tier":"public","kind":"doc"},{"id":"/research/tla","title":"The TLA+ formal specification corpus","slug":"/research/tla","tier":"public","kind":"doc"},{"id":"/research/tutorials/reproduce-a-learning-round","title":"Reproduce a learning round","slug":"/research/tutorials/reproduce-a-learning-round","tier":"public","kind":"tutorials"}]},{"id":"grp-enterprise","title":"Enterprise & Citrate Ground","tier":"public","kind":"group","children":[{"id":"/enterprise/compliance-full","title":"Compliance Posture, Full Package (Gated)","slug":"/enterprise/compliance-full","tier":"public","kind":"doc"},{"id":"/enterprise/compliance","title":"Compliance posture, public and sanitized","slug":"/enterprise/compliance","tier":"public","kind":"doc"},{"id":"/enterprise/dpa","title":"Data Processing Agreement (Gated)","slug":"/enterprise/dpa","tier":"public","kind":"doc"},{"id":"/enterprise/federal","title":"Federal and On-Prem Isolation (Gated)","slug":"/enterprise/federal","tier":"public","kind":"doc"},{"id":"/enterprise/procurement","title":"Procurement, how to buy Citrate","slug":"/enterprise/procurement","tier":"public","kind":"doc"},{"id":"/enterprise/questionnaires","title":"Security Questionnaires, SIG and CAIQ (Gated)","slug":"/enterprise/questionnaires","tier":"public","kind":"doc"}]},{"id":"grp-methodology","title":"Methodology","tier":"public","kind":"group","children":[{"id":"/methodology/sops","title":"Standard operating procedures","slug":"/methodology/sops","tier":"public","kind":"doc"},{"id":"/methodology/rules","title":"The 13 Agentile rules","slug":"/methodology/rules","tier":"public","kind":"doc"},{"id":"/methodology/workflow","title":"The Agentile sprint workflow","slug":"/methodology/workflow","tier":"public","kind":"doc"}]},{"id":"grp-security","title":"security","tier":"public","kind":"group","children":[{"id":"/security/posture","title":"Security Posture & Audit History","slug":"/security/posture","tier":"public","kind":"doc"}]}]; diff --git a/content/chain/_generated/addresses.md b/content/chain/_generated/addresses.md index b0b262ad..3ceccb4d 100644 --- a/content/chain/_generated/addresses.md +++ b/content/chain/_generated/addresses.md @@ -6,8 +6,8 @@ org_scope: ~ source_kind: transcluded source: citrate-chain/contracts/addresses/40204.json surfaces: [CHAIN-addresses] -audited_against_sha: de518be8 -book_deployed_at: 2026-09-12T14:16:34Z +audited_against_sha: 0aab474b +book_deployed_at: 2026-09-30T00:21:18Z status: Implemented created: 2026-09-07T00:00:00Z author: Citrate team @@ -17,90 +17,113 @@ nav_order: 5 This is the canonical list of contract addresses on chain 40204 (Citrate Network). It is generated from the federation address book (`citrate-chain/contracts/addresses/40204.json`), the single source of truth every application reads from, and is regenerated after each re-roll or address fan-out. As of the book at -commit `de518be8`, deployed 2026-09-12 14:16:34UTC. +commit `0aab474b`, deployed 2026-09-30 00:21:18UTC. -Not every entry in the book is deployed. At block 178426 (2026-09-25T05:28:37Z), 57 of the 76 +Not every entry in the book is deployed. At block 6929 (2026-09-30T02:15:56Z), 99 of the 99 application and account-abstraction entries have code on chain; rows marked **not deployed** have none. A call to a not-deployed address returns empty data, and a value transfer to one succeeds and strands the value, so check the status column before you send anything. Re-check any address yourself with `cast code
--rpc-url https://rpc.citrate.ai`. The core and account-abstraction addresses are deterministic (CREATE2 through the genesis factory), so a -re-roll moves them together and this page moves with them. The membership contracts are the exception (see below). The RPC endpoint is `https://rpc.citrate.ai` and the deployer is `0x4fAB35c8c5033c80b3a0452A873B81e6ED4ED732`. +re-roll moves them together and this page moves with them. The membership contracts are the exception (see below). The RPC endpoint is `https://rpc.citrate.ai` and the deployer is `0xa3512bE80ABe86439525a3e5a185884aB0ccb87a`. ## Core contracts | Contract | Address | Status | |---|---|---| -| `ModelRegistry` | `0xba36fa0da9327030bd14351db968c8c43c5a67e4` | deployed | +| `ValidatorRegistry` | `0xBa4aBd4f3fcA5365b2451b4E9662e4Cfd22b3ad5` | deployed | +| `ModelRegistry` | `0x807cB7eE477Ae58C321cAEd980CEB11D78048e84` | deployed | +| `WrappedSALT` | `0xAa918302B94a4B0E75E01e019cc6b819B4F7c906` | deployed | +| `AgentDecisionRegistry` | `0x94A204DaC83C99F5ce2C8ac19fc101C07D8b9A41` | deployed | +| `SpecRegistry` | `0xf38D10dFb550EE3Bce1332A887adC0D2C75EB642` | deployed | +| `IPFSIncentives` | `0xc37aB44b145a31E8c458996437080326Fd19e129` | deployed | +| `X402Facilitator` | `0x7F7b6e8D9Ad0A8b4e6152Df6167E49E48AFB3463` | deployed | +| `X402Paywall` | `0x13e50000FFFc95D910D42Cd8D679c28F6265a970` | deployed | +| `LiquidStakingPool` | `0x68Aa320Be609A073fC8ebB7dBCD237270A5DEEb0` | deployed | +| `ContributionAccounting` | `0x52a47cAF8902D8214d1e246E74aA3Ad099DA47D1` | deployed | +| `NematocystSlashing` | `0xee9501285F7b3c8Bb99B8aF70F95b402F4D2468b` | deployed | +| `MarketMakerAllocation` | `0xb5dDD7c5146c240D53Ce6c7e87E5aCB59E4f7351` | deployed | +| `ModelMarketplace` | `0x5517A9fDD70d503a57898c86eFeaaeEF8FE06413` | deployed | +| `InferenceRouter` | `0xe1A717f0656b000e33A78B97507Cd0440Ad62570` | deployed | +| `LoRAFactory` | `0x985036F3441258B8Ff6DDa8a43EA40EEEb1D02A6` | deployed | +| `LearningPool` | `0xBA5C9c886d65a969d02e40d7FBbADe316AD81E66` | deployed | +| `LearningCycleManager` | `0x4254d5aeb3Fb90A5038e1bC5E10b30d0021990Bd` | deployed | +| `ClassroomRegistry` | `0xe2b56b2BFcaeB3c8d14400184eAb01BBC980cC05` | deployed | +| `MentorMatcher` | `0x05d6a67279972273F23124EF95237956Ef923C05` | deployed | +| `ComputeVerifier` | `0xA483021adE196D642e6500B2D92c5E077186Ee00` | deployed | +| `ComputeMarketplace` | `0xE4fD2413d19946E7a8e78733E62C1531139430Bb` | deployed | +| `ComputePool` | `0x47FFB16216a5431dcF852f70Fe534c556cE807eF` | deployed | +| `HeartbeatMonitor` | `0x85c1A278ed86169C5087616d879013e9337a7013` | deployed | +| `DisputeResolution` | `0xea0E6716A8A0bA39DF1d30552CFeC0bBab36D602` | deployed | +| `ComputePricingOracle` | `0xDfaF0b02846Ac33f1fC753ACC2c1D7D0B2F1aE4e` | deployed | +| `StablecoinTreasury` | `0x6867F82401F2773bf625887cC5b1FA1d6EAfa352` | deployed | +| `BulkComputeGateway` | `0x9CF7DdBFbba683a14ceF4Eb0e7934f79ec10C586` | deployed | +| `TestnetFarmingAccounting` | `0xE19aef1A41b883021222aC7596c7ca96A62C6156` | deployed | +| `TreasuryGovernor` | `0xe0537e5f14C087EC865E152B9D3356d721E5a24F` | deployed | +| `ModelAccessControl` | `0x66f78C103D6EE077CD2875C71D676540708d5356` | deployed | +| `TEEAttestationRegistry` | `0x6693b6FcBc5bf3935bEEB7a21654bDfBd90e84b6` | deployed | +| `ComputePoolTraining` | `0x1D71814BbC78ae994CA5B6eE9c4b9378178b2B36` | deployed | +| `InstitutionalVault` | `0x7eAb0072153FB71E292A5e272e6Ac66E3f86D094` | deployed | +| `ClassroomClusterV1` | `0xF2D989FFA09719aa9ee2020Fbf09aD0924ccb486` | deployed | +| `EduForwarder` | `0x1531224eECc9dFe1CdcCd80Be1BD35804005F181` | deployed | +| `BudgetAllocation` | `0x365e98100B879a7A54Dc3FED969C977D21be59A0` | deployed | +| `CashoutRequest` | `0x6E357EDCfc392bAc92b55e1f39f0FA8FC4e8E03A` | deployed | +| `AIModelRegistryPortable` | `0xdA30A0408b1690AfA739fB63901a6608547F4dA6` | deployed | +| `AIInferenceRouterPortable` | `0xb8603904aEBeFefa317D9DeDF60B19A120366715` | deployed | +| `AILearningCycleCorePortable` | `0x42196F4257E5AfAa9fF92735f88aa0c23fBe8b91` | deployed | +| `CitrateMemberSBT` | `0xA24aa35fbA269f8755C2173779cc3DBC9690c4C9` | deployed | +| `MemberBond` | `0x7D6B92757e928ab4207Be3B54166Ecd2C491Aa92` | deployed | +| `MembershipStakeVaultImpl` | `0x72035977F3Ec295C70e2A734AcbDFfB0C98E6F0b` | deployed | +| `MembershipStakeVault` | `0x4C0f8b27c509cBA4A32E1Cd2BC5709bBD2699024` | deployed | | `SkillRegistry` | `0x2B687899EF4aF05A18F4f36cE1fE9d51c017A97c` | deployed | -| `WrappedSALT` | `0xaa918302b94a4b0e75e01e019cc6b819b4f7c906` | deployed | -| `AgentDecisionRegistry` | `0xd4008e0b4f0bd00d630810d1f7f0f78db0ba837a` | deployed | -| `SpecRegistry` | `0x8ce7000c83d0ef5276a70bdc34bf2fa2fe0159ff` | deployed | -| `IPFSIncentives` | `0xb79e438bc8c68f7d94cf694eb0ec8eae40525680` | deployed | -| `X402Facilitator` | `0xae0d2ddc74732df4424d2a89c0815cba84be37e7` | deployed | -| `X402Paywall` | `0xca98b1678a3127a4d605ac3b37454646adbf5453` | deployed | -| `LiquidStakingPool` | `0xead6a4a47c528ecea2a86cd9d9af7504d7a5e30e` | deployed | -| `ContributionAccounting` | `0xd00d442c735c16d00f04ae31a180c78eec5ec32f` | deployed | -| `NematocystSlashing` | `0xfeb23abd20084d36a1145da8a2dc04e8b48f65c7` | deployed | -| `MarketMakerAllocation` | `0xfcc747d35d616c48bddef98a31b7e8ebc8786864` | deployed | -| `ModelMarketplace` | `0xbd94012b113c81843dc66196d13fe0667651a7f0` | deployed | -| `InferenceRouter` | `0x00463e63a5645de75083460f5f1ee108d0870815` | deployed | -| `LoRAFactory` | `0xbb7eeb6286a756b0e23af2ead3e03acca72f9e39` | deployed | -| `LearningPool` | `0xd973cc744f9fd8da55a8b08cde303d5b59a29771` | deployed | -| `LearningCycleManager` | `0xcce506d1f270f954b726c552879ebc19f3719035` | deployed | -| `ClassroomRegistry` | `0x124f5f69691e0963c3a7c4d9497d1e224568ffb3` | deployed | -| `MentorMatcher` | `0x78ca904036cd144b55f6dd07bc3e36603c6c089b` | deployed | -| `ComputeVerifier` | `0x067c16ea5c2b90045607d9b33c127606e67e61da` | deployed | -| `ComputeMarketplace` | `0x527e636389a46784b9537690716db00d4ab987d4` | deployed | -| `ComputePool` | `0xcd778fc9820ac8cada5cd95aa7cddf6e4ca4d375` | deployed | -| `HeartbeatMonitor` | `0xe9eaac272844f342266862bbefc6d117a227ad9b` | deployed | -| `DisputeResolution` | `0x4562d2a68063a61b83683aae301fe0f480e4f03a` | deployed | -| `ComputePricingOracle` | `0x10b5c17d6f018631fc221594ed8b8bb003c9c975` | deployed | -| `StablecoinTreasury` | `0x0e9c5953bd7c77252119e32f989ba94f735c8599` | deployed | -| `BulkComputeGateway` | `0xf55f743e4a20557f03fdaa3cc43b0d2354c73c79` | deployed | -| `TestnetFarmingAccounting` | `0x220cc378641607df8ff9cff6e985d67331704ca4` | deployed | -| `TreasuryGovernor` | `0xab7c486db6377225453a04a0bf7161291f2611b1` | deployed | -| `InstitutionalVault` | `0xb38a64922fad87e86e36254dc2fd65a971eb211e` | deployed | -| `ClassroomClusterV1` | `0xdd6bad78e88147a46f502e02ff56808916c8c4b8` | deployed | -| `BudgetAllocation` | `0x220a8dbb48ba3dfbe2c4f5ae162c5e5b6dc2351e` | deployed | -| `CashoutRequest` | `0xaeb938bf9eedcffb14ab2db1e8787e591b539700` | deployed | -| `AIModelRegistryPortable` | `0xda30a0408b1690afa739fb63901a6608547f4da6` | deployed | -| `AIInferenceRouterPortable` | `0x85b04c554ee0137818a0e9acbe6d5f8f4b6ef1d7` | deployed | -| `AILearningCycleCorePortable` | `0x615297a23f954681ef4b648eaaf722455eca925c` | deployed | -| `ModelAccessControl` | `0xc68f19c4f3e1fae734ca0a053af8a5b34ed98c63` | deployed | -| `TEEAttestationRegistry` | `0x0834a05a5607af5ff10dade01e1c96cd6e83bd8b` | deployed | -| `ComputePoolTraining` | `0x0858b110dfa9c61df34b9d57576e751229b900b7` | deployed | -| `KYCRegistry` | `0xf72248f5dfe5c8dab3047ae52958aa65b216be8f` | deployed | -| `IPFSIncentivesV2` | `0x951ddc6316efbeda36dcb940e4d81747415b8500` | deployed | -| `IPFSIncentivesV3` | `0xc27a867b8d076d77cf17981f235c64a0d0203a68` | deployed | -| `AggregationChallenge` | `0xe7d7ebe1242feec29d514b00c9272fbffc9e69be` | deployed | -| `ComputePoolPipeline` | `0xc05a38141bb095275f8dc24dfbbcf69722cd1a3b` | deployed | -| `ValidatorRegistry` | `0x2655d9fbbe599e75ff6e53790f99ebc9a20c93bf` | deployed | -| `EduForwarder` | `0xe4c6aa7afd77e24c838f8a490aae6f34b286faff` | deployed | -| `AnchorRegistry` | `0xfeaacf58d9a38c60cbc473c3abea55dd629cf660` | **not deployed** (no code) | -| `MeetingRegistry` | `0x8fffde6f66901b30adcd1544763c279ca1e1b30a` | **not deployed** (no code) | -| `GovernanceTemplateRegistry` | `0x90a3d1ccc159501833d1160190a58470db1a0a88` | **not deployed** (no code) | -| `GovernanceProtocolFactory` | `0xbba38be5c9a0ad00c7d24430b53a7049f46c9b3f` | **not deployed** (no code) | -| `PolicyBinding` | `0x76c41259d1454d983a2def855d532bfd67e10ea0` | **not deployed** (no code) | -| `CapabilityGrant` | `0x3139e17e23914e9442e126228f5b14658b50a449` | **not deployed** (no code) | -| `VoteAllowance` | `0xee0f77fb2e6f5f31ac8b5df14932ba4715b558bd` | **not deployed** (no code) | -| `Sortition` | `0x1eabce0dddb74f5c144c7f452a7d27292c62e900` | **not deployed** (no code) | -| `PatronageLedger` | `0x726f2c8a0bfa4145dca7c154577705803c8dafa3` | **not deployed** (no code) | -| `ModelCooperative` | `0x54b70368373b0b22ac8ad9882961228d790133bf` | **not deployed** (no code) | -| `FacilitySBTImpl` | `0xa8ad418a0be3877a797f183bade8dc52b1608ad0` | **not deployed** (no code) | -| `NetworkSBTImpl` | `0x3a6ff326f83cd77ed936dcb1620ece2f5b41d7af` | **not deployed** (no code) | -| `FacilitySBT` | `0x2520b5307752318b03047cf547b38b99311f65fb` | **not deployed** (no code) | -| `NetworkSBT` | `0x062b355f67b8252054ad59c220b3aac1cd0a0ff6` | **not deployed** (no code) | -| `CitrateMemberSBT` | `0xf0badd9eed5a81871a2f0d309b1f0a225646448a` | deployed | -| `MemberBond` | `0x7d6b92757e928ab4207be3b54166ecd2c491aa92` | deployed | -| `MembershipStakeVaultImpl` | `0x72035977f3ec295c70e2a734acbdffb0c98e6f0b` | deployed | -| `MembershipStakeVault` | `0x53fb4badffaceedd575d47d0e74bb721504f786e` | deployed | -| `CitrateCooperativeFactory` | `0xd4750aa00f0634cb2d5154dfc19eb8dcbc885e9f` | **not deployed** (no code) | -| `CoopDeployer` | `0xccdfcb866f42dcde3aa19d2aa1434e4c3c6a3b48` | **not deployed** (no code) | -| `CoopMembershipSBT` | `0xb455c14880aca8eeddf95f6e1dcfddb13d8b6a83` | **not deployed** (no code) | -| `ContributionRewardPool` | `0x2aee5a81e0fa056d2e6949d71aaf96456218b6c8` | **not deployed** (no code) | -| `CoopGovernor` | `0x8046c10f1bb4bf58cedb4a7a55ebfa7f8b09e64b` | **not deployed** (no code) | +| `InstitutionTreeV1` | `0x028f98faeFeE5FF58cb494E493eD8f636aa3042B` | deployed | +| `ComplianceRegistry` | `0xa301FA601702B0fb850201182cEF381E312c63ee` | deployed | +| `AnchorRegistry` | `0x41e0f9A4dCD29C650dc58Ee569BF267fD9ba4817` | deployed | +| `FacilitySBTImpl` | `0x58ac5816c42a3d293552Fe368C4db53b89edB02c` | deployed | +| `FacilitySBT` | `0xb266e583A30cb47cFF54d9d7429aD55DC68C5e57` | deployed | +| `NetworkSBTImpl` | `0x01D34046343a171ec7cd4DB8978adbaB095EF955` | deployed | +| `NetworkSBT` | `0x823c5031A273a304C5a087a3F414F228907Ad7DB` | deployed | +| `CitAgentTimelock` | `0xBaC05BC639af6eF107F40fe606f1c4A22b7836A2` | deployed | +| `OrganizationSBT` | `0xB1Bb65Fc3F2188Ff1209845cBe64eba985461689` | deployed | +| `AgentSBT` | `0xd16b1ad6e744F3E92223C65F492c35D36ae07c7b` | deployed | +| `CapsuleRegistry` | `0xb2b1DF947d8064797083CE6024DCe0C64999C79C` | deployed | +| `CitAgentAnchorRegistry` | `0xB38b0e8b264d828A4e55276033B54800C223De45` | deployed | +| `BenchmarkRegistry` | `0x84247a5f65370947c792181A3afeD5AC0F452EC8` | deployed | +| `TenantHierarchy` | `0x7e92a5CbD49659fe594B503b50B26F5BD7060e90` | deployed | +| `ClassificationRegistry` | `0x37844e433f6E1Df3eBdaa9FdF2cFC44c222f251d` | deployed | +| `RoleEscalation` | `0xAab258228E85A22C99Cb298915277521eb7049D9` | deployed | +| `MultiSigEnvelope` | `0x3052Ef8C8d6B71f1fF12703C65b33f29F6627Bbf` | deployed | +| `AgentDecisionRegistryV2` | `0x678D03b31A77F146b8977E1128c2f57D3e3583F7` | deployed | +| `ContradictionLedger` | `0xdeB5D07716a20838b1b7287c51bCA00e8d12D20d` | deployed | +| `QuorumAnchorRegistry` | `0x94Aca73127c7A34d5872A861D0A7C9393030Fa2E` | deployed | +| `MeetingRegistry` | `0x4B0C7Cf5feF3B6f5A8b042E2E71788450Ec70De4` | deployed | +| `GovernanceTemplateRegistry` | `0xFF6481c1F532E52aA1EFaaEEF117A29e1F84bC82` | deployed | +| `GovernanceProtocolFactory` | `0x17c2e4e24e8E041302cBbe8C51A7996949719Df6` | deployed | +| `PolicyBinding` | `0x2e54Ea789a4EeC4A4e360b9436B04085419a29e9` | deployed | +| `CapabilityGrant` | `0x1670F43B5eC0cd18088147d4fd7543E51A679d30` | deployed | +| `VoteAllowance` | `0x2271042A2f4F4783949ee74733A2AbfE0c50018C` | deployed | +| `Sortition` | `0xB934aE6B6836ad17F2525b6428CcE7A5F7D6F0ec` | deployed | +| `PartProvenanceRegistry` | `0x60FF23F311E5Cbec702E62aFC99F96F36180347d` | deployed | +| `SupplierRegistry` | `0x3A40A13EEa4a28Fc3A86E6cc9ab3F4CDF4C85f26` | deployed | +| `MoqRegistry` | `0x577Fb91D26569820A64bB752d78Da008EF04666d` | deployed | +| `DefensePrimeFLScopeIndex` | `0x100d4e9Eb591f20Cc39D9A0080F2811ea12CA28E` | deployed | +| `AppRegistry` | `0xA0C18325Ee5426A26Feff56b3d2F7C6EF8264ad0` | deployed | +| `CrossOrgIndex` | `0xD62f4A63054e53B1cbE10a1956AA607D65CD2FDd` | deployed | +| `AuditBundleRegistry` | `0xAc20e8F340c15D990270832650bB76d0805fe473` | deployed | +| `DefensePrimeComplianceRegistry` | `0xd99dF90C0385ba89be36Bf5FBc42d6bc950920E6` | deployed | +| `RoleGrantTenantIndex` | `0xC8127F90D5b7B7e88B48Ec7312c2cB5884a3f1D6` | deployed | +| `EntityRegistry` | `0xE97f4529A0C9e8c81FB32ad72D3a578F92dE4b79` | deployed | +| `TinaWorkpaperRegistry` | `0xA9E05E9Bd0DDB62CabDcBBe263429a093B21aeC9` | deployed | +| `CrossOrgEnvelope` | `0x3b2913E078fa859dc10Aa9EA90A6FCF3E1D47480` | deployed | +| `TripwireRegistry` | `0xaC5e1E599788540a5E97B1258d6bd99c417Ce9eA` | deployed | +| `SponsorEvidenceRegistry` | `0xc5CA4ae343367995b49570d97759643A289F298D` | deployed | +| `ReleaseManifestRegistry` | `0x7B955B307c4EbBeA469C40FfeD41AD08a8ECe075` | deployed | +| `KYCRegistry` | `0x2a45692244dE4A87172061F604b1167D2c7f6C3b` | deployed | +| `IPFSIncentivesV2` | `0x7747745AA3d78c93993DD1eF6aCD8CC8AFED8aEC` | deployed | +| `IPFSIncentivesV3` | `0xe016f7655172dCc8039863484F1A8C306553867D` | deployed | +| `AggregationChallenge` | `0xB5D143dC15dD9C570D198c7156Fe427ccEFc7377` | deployed | +| `ComputePoolPipeline` | `0xbE60946E62697e99697bdF90b59700D4D76f5E60` | deployed | ## Membership @@ -110,8 +133,8 @@ from the book. | Contract | Address | Status | |---|---|---| -| `CitrateMemberSBT` | `0xf0bADD9Eed5A81871a2F0D309b1f0a225646448a` | deployed | -| `MembershipStakeVault` | `0x53fB4baDfFacEEDD575D47D0E74Bb721504F786e` | deployed | +| `CitrateMemberSBT` | `0xA24aa35fbA269f8755C2173779cc3DBC9690c4C9` | deployed | +| `MembershipStakeVault` | `0x4C0f8b27c509cBA4A32E1Cd2BC5709bBD2699024` | deployed | ## Account abstraction @@ -120,13 +143,13 @@ together. | Contract | Address | Status | |---|---|---| -| `EntryPoint` | `0x97d5391a647429233e202f99231743c53a648f3c` | deployed | +| `EntryPoint` | `0x97d5391a647429233E202f99231743C53a648f3c` | deployed | +| `WebAuthnP256Validator` | `0x0f421a99A0b8F6138Dea12F45A523Cb896D09fc7` | deployed | +| `CitrateECDSAValidator` | `0xD2d35421379Ae5b461e216BFcdD1B7e6a64BBC40` | deployed | +| `GuardianRecoveryModule` | `0x0A909769160C1945401b8f37a9310d37DbB6a891` | deployed | | `CitrateWallet` | `0x2D742B98D867Fc7363F530DD6d756622e4Eb768D` | deployed | -| `CitrateWalletFactory` | `0x86486d1de9f256e2cba327c46ac11120df0aa51a` | deployed | -| `CitratePaymaster` | `0xfdc9f7a72163b5d45becdb8a9d8d44b970f77318` | deployed | -| `WebAuthnP256Validator` | `0x0f421a99a0b8f6138dea12f45a523cb896d09fc7` | deployed | -| `CitrateECDSAValidator` | `0xd2d35421379ae5b461e216bfcdd1b7e6a64bbc40` | deployed | -| `GuardianRecoveryModule` | `0x0a909769160c1945401b8f37a9310d37dbb6a891` | deployed | +| `CitrateWalletFactory` | `0x24e2a41E48Fb3d5A054528bF017ebAeC0aC94EFf` | deployed | +| `CitratePaymaster` | `0x8E65bff91E4c53556E1Cee8b0135ffb09D427E58` | deployed | ## Precompiles diff --git a/content/contracts/models.md b/content/contracts/models.md index 44956537..d3f1aa54 100644 --- a/content/contracts/models.md +++ b/content/contracts/models.md @@ -6,10 +6,12 @@ org_scope: ~ source_kind: authored source: citrate-chain/contracts/src (ModelRegistry.sol, ModelMarketplace.sol, LoRAFactory.sol, ModelAccessControl.sol, InferenceRouter.sol, interfaces/IModelRegistry.sol, interfaces/IModelMarketplace.sol) surfaces: [SC-model-registry, SC-model-marketplace, SC-model-lora, SC-model-access, SC-model-router] -audited_against_sha: e68af83 +audited_against_sha: fa7c913 status: Implemented created: 2026-06-17T00:00:00Z -author: Citrate team +author: Citrate team; precompile section Larry Klosowski + Claude Opus 5.5 +branch: hup/n7-chain-precompile-followups +updated: 2026-10-04 --- These are the contracts that register a model on the public ledger, sell access to it, route inference @@ -21,13 +23,15 @@ and integrators. A model on Citrate is a record, not a file. The weights stay where the owner put them, usually behind an IPFS CID; the public ledger keeps the model's identity, its owner, its price, and a count of the work it -has done. Inference itself runs through the runtime [precompiles](/chain/precompiles); the contracts -here charge for it, route it, and gate it. +has done. Inference itself runs off chain on the operators that serve the model; the contracts here +charge for it, route it, and gate it. Where a contract asks a [precompile](/chain/precompiles) for an +answer, it does so through one library that refuses to treat "no answer" as an answer (see +[Precompile calls](#precompile-calls) below). Five contracts cover the model economy: -- **ModelRegistry** is the root record. Registering a model yields a `modelHash` and proxies inference - through the model precompile. +- **ModelRegistry** is the root record. Registering a model yields a `modelHash`; `requestInference` + asks the model inference precompile at `0x0101`, which contract code cannot reach on 40204 today. - **InferenceRouter** load-balances inference across staked operators, with a cache and a refund path. - **ModelMarketplace** lets owners list a model and sell access, with bulk discounts and a treasury fee. - **LoRAFactory** builds, trains, merges, and cryptographically verifies low-rank adapters on top of a @@ -63,8 +67,8 @@ The shortest path from nothing to a paid inference call. hit returns the stored output with a partial refund; otherwise the assigned operator returns the result with `completeInference` and is paid, with any excess refunded. 5. Adapt it, optionally. Build a LoRA adapter on the base model with `createLoRA(...)` on - **LoRAFactory**, have an operator finish training, and verify the adapter against the inference proof - precompile before anyone relies on it. + **LoRAFactory**, have an operator train it off chain and record the weights, and verify the adapter + against the inference proof precompile before anyone relies on it. ## Reference @@ -76,9 +80,10 @@ hand-copied from here. `contracts/src/ModelRegistry.sol`, `contract ModelRegistry is IModelRegistry, AccessControl, ReentrancyGuard` (the project-local `AccessControl` and `ReentrancyGuard`, not OpenZeppelin; interface at -`contracts/src/interfaces/IModelRegistry.sol`). Stores model metadata, manages owner permissions, -charges a fixed registration fee, and proxies registration and inference to the model precompile at -`0x1000`. The constructor grants the deployer the admin and operator roles. +`contracts/src/interfaces/IModelRegistry.sol`). Stores model metadata, manages owner permissions, and +charges a fixed registration fee. Registration and updates are records only: no precompile is called, and +the weights stay at the IPFS CID. `requestInference` calls `0x0101` MODEL_INFERENCE through the +`CitratePrecompiles` library. The constructor grants the deployer the admin and operator roles. | Function | Notes | |---|---| @@ -87,13 +92,14 @@ charges a fixed registration fee, and proxies registration and inference to the | `setInferencePrice(bytes32 modelHash, uint256 newPrice)` | Owner only. | | `deactivateModel(bytes32 modelHash)` / `activateModel(bytes32 modelHash)` | Owner or operator role. | | `grantPermission(bytes32 modelHash, address user)` / `revokePermission(bytes32 modelHash, address user)` | Owner only. | -| `requestInference(bytes32 modelHash, bytes inputData) payable returns (bytes)` | Forwards the full `msg.value` to the model owner; no marketplace fee is retained. | +| `requestInference(bytes32 modelHash, bytes inputData) payable returns (bytes)` | Forwards the full `msg.value` to the model owner; no marketplace fee is retained. Calls `0x0101` with `modelHash || msg.sender || inputData`; reverts with `PrecompileUnavailable(0x0101)` on every 40204 node today, payment included. | | `withdrawFees()` | `onlyRole(DEFAULT_ADMIN_ROLE)`. | Views include `getModel` (an 8-tuple of owner, name, framework, version, IPFS CID, inference price, total inferences, and active flag), `getModelsByOwner`, `getModelRevenue`, `hasPermission`, -`getAllModelHashes`, and `getModelsInfo`. Constants: `REGISTRATION_FEE = 0.1 ether` and -`MODEL_PRECOMPILE = 0x1000`. Note that `setRegistrationFee` is intentionally inert: it is a `view` that +`getAllModelHashes`, and `getModelsInfo`. Constant: `REGISTRATION_FEE = 0.1 ether` (the old +`MODEL_PRECOMPILE = 0x1000` and `ARTIFACT_PRECOMPILE = 0x1002` constants are gone; nothing served those +addresses). Note that `setRegistrationFee` is intentionally inert: it is a `view` that always reverts with "Registration fee is immutable", so the fee cannot be changed. ### InferenceRouter @@ -148,26 +154,32 @@ rating. `contracts/src/LoRAFactory.sol`, `contract LoRAFactory is AccessControl` (project-local; it does not inherit `ReentrancyGuard`). A factory for creating, training, merging, and cryptographically verifying -low-rank adapters against base models in the registry, through the LoRA precompile at `0x1001` and the -Halo2-KZG inference-proof verifier at `0x0108`. The constructor takes the registry address and grants the -deployer admin and operator roles. +low-rank adapters against base models in the registry. Training and merges run off chain (the compute +pool or an operator) and are recorded here: `createLoRA` emits `TrainingStarted`, `mergeLoRAs` emits +`MergeRequested`, and the operator records the results with `completeTraining` and `completeMerge`. +Verification goes through the Halo2-KZG inference-proof verifier at `0x0108`; adapter inference goes to +`0x0101` with the adapter's id. The constructor takes the registry address and grants the deployer admin +and operator roles. | Function | Notes | |---|---| -| `createLoRA(bytes32 baseModelHash, string name, string description, uint256 rank, uint256 alpha, uint256 dropout, TrainingConfig config) payable returns (bytes32)` | Requires permission on the base model; fee is `trainingFeePerEpoch` times the epoch count. | +| `createLoRA(bytes32 baseModelHash, string name, string description, uint256 rank, uint256 alpha, uint256 dropout, TrainingConfig config) payable returns (bytes32)` | Requires permission on the base model; fee is `trainingFeePerEpoch` times the epoch count. Emits `TrainingStarted`; no precompile call. | | `completeTraining(bytes32 loraHash, string ipfsCID)` | `onlyRole(OPERATOR_ROLE)`; records the trained-weights CID. | | `setAdapterModelCommitment(bytes32 loraHash, bytes32 commitment)` | `onlyRole(OPERATOR_ROLE)`; one-shot. | | `verifyAdapterAt(bytes32 loraHash, bytes32 inputCommitment, bytes32 outputCommitment, bytes proofBytes)` | Proof-backed verification through `0x0108`. | | `isAdapterVerified(bytes32 loraHash) view returns (bool)` | Whether the adapter has a verified proof. | -| `mergeLoRAs(bytes32[] loraHashes, uint256[] weights, uint256 mergeType) payable returns (bytes32)` | Weights must sum to `1e18`. | +| `mergeLoRAs(bytes32[] loraHashes, uint256[] weights, uint256 mergeType) payable returns (bytes32)` | Weights must sum to `1e18`. Emits `MergeRequested`; the merge itself runs off chain. | | `completeMerge(bytes32 requestHash, string resultCID)` | `onlyRole(OPERATOR_ROLE)`. | -| `inferWithLoRA(bytes32 baseModelHash, bytes32 loraHash, bytes inputData) payable returns (bytes)` | Splits 20% to the adapter creator, 80% through `modelRegistry.requestInference`. | +| `inferWithLoRA(bytes32 baseModelHash, bytes32 loraHash, bytes inputData) payable returns (bytes)` | Calls `0x0101` with `loraHash || msg.sender || inputData`, then splits 20% to the adapter creator and 80% through `modelRegistry.requestInference`. Reverts with `PrecompileUnavailable(0x0101)` on 40204 today, so nobody is paid. | | `setPublicStatus` / `grantPermission` / `revokePermission` | Creator only. | | `setTrainingFee` / `setMergeFee` / `withdrawFees` | `onlyRole(DEFAULT_ADMIN_ROLE)`. | Views include `getLoRA`, `getUserLoRAs`, `getModelLoRAs`, and `getMergeRequest`. Constants: -`trainingFeePerEpoch = 0.01 ether`, `mergeFee = 0.05 ether`, `LORA_PRECOMPILE = 0x1001`, -`INFERENCE_PROOF_VERIFY = 0x0108`, and `INFERENCE_CIRCUIT_V1 = 1`. +`trainingFeePerEpoch = 0.01 ether`, `mergeFee = 0.05 ether`, `INFERENCE_PROOF_VERIFY = 0x0108`, and +`INFERENCE_CIRCUIT_V1 = 1`. The old `LORA_PRECOMPILE = 0x1001` constant is gone: nothing served that +address, and its `startTraining`, `mergeLoras` and `applyAndInfer` calls never ran. On-chain LoRA +arithmetic is now the pair of precompiles at `0x0112` and `0x0113` described under +[Precompile calls](#precompile-calls). Adapter provenance, as this contract implements it, ties to three things and no more: the base model hash, which must exist in the registry; the IPFS CIDs for the trained weights and for the dataset, the @@ -186,7 +198,8 @@ separate [Citrate Orchard](/research/learning) surface. OpenZeppelin versions, the one contract on this page that does). A standalone tiered access registry, distinct from ModelRegistry: paid or approval-based grants, per-model staking, revenue sharing, and an encrypted-inference path through the runtime precompiles for model inference at `0x0101` and model -encryption at `0x0106`. The constructor makes the deployer the owner. +encryption at `0x0106`, both called through the `CitratePrecompiles` library, so both fail closed on 40204 +today. The constructor makes the deployer the owner. | Function | Notes | |---|---| @@ -206,6 +219,45 @@ are `ACCESS_NONE = 0`, `ACCESS_INFERENCE = 1`, `ACCESS_FULL = 2`, and `ACCESS_AD `getModelStats` returns `uniqueUsers` as a placeholder zero, and `updatePrecompileAddress` is a non-functional placeholder. +## Precompile calls + +Every precompile call in these contracts goes through one library, `CitratePrecompiles` +(`contracts/src/lib/CitratePrecompiles.sol`). It encodes each precompile's native input (the node does not +decode Solidity ABI selectors) and fails closed: a call that fails or returns nothing reverts with +`PrecompileUnavailable(address)`, and a wrong-shaped answer reverts with +`PrecompileBadOutput(address, length)`. This matters because a call to an address with no code succeeds +with empty data, so a contract that checks only the success flag would read "no precompile here" as a +result and pay for it. + +| Address | Name | Used by | On 40204 today | +|---|---|---|---| +| `0x0101` | MODEL_INFERENCE | ModelRegistry, LoRAFactory, ModelAccessControl | not served to contract code; the call reverts | +| `0x0106` | MODEL_ENCRYPTION | ModelAccessControl | not served to contract code; the call reverts | +| `0x0108` | INFERENCE_PROOF_VERIFY | LoRAFactory adapter verification | live where the node build carries the verifier | +| `0x0112` | LORA_APPLY | library helper `loraApply` | active from genesis of the 2026-10-05 reroll | +| `0x0113` | LORA_MERGE | library helper `loraMerge` | active from genesis, as above | +| `0x0121` | MEMORY_ANCHOR_VERIFY | library helpers `memoryAnchorCommitment`, `AnchorProofs.isRecordAnchored` | active from genesis, as above | +| `0x0122` | AGENT_OPS | library helpers `deviceLinkValid`, `deviceRevocationValid` | active from genesis, as above | + +`0x0112` applies one LoRA adapter to one tile of weights (`W + (alpha / r) (B . A)` in Q16.16 fixed +point) and `0x0113` merges up to 16 adapters on one tile, which is what lets a challenger recompute one +disputed tile of an aggregate instead of the whole tensor. `0x0121` checks a nightly decision-anchor +inclusion proof and returns the day commitment to look up in `AnchorRegistry`. `0x0122` checks device link +and revocation signatures. All four are pure byte functions that every node computes identically. + +**Activation.** The four agent precompiles sit behind a per-network activation height. On 40204 that +height is genesis: the release pins `(40204, Some(0))`, so after the 2026-10-05 reroll they are live from +the first block and no mid-chain activation is scheduled. The gas schedule is the owner-signed genesis +schedule. On a network whose height is not reached (or not set) the addresses behave as if absent: every +library call to them reverts with `PrecompileUnavailable`, so no contract can mistake a missing precompile +for a "valid" or "invalid" verdict. Nodes on the rerolled 40204 must be built from the release commit that +carries this pin. + +The byte layouts, gas formulas, activation rules and test evidence are specified once, in the chain +repository's +[agent precompile specification](https://github.com/CitrateNetwork/citrate-chain/blob/main/docs/precompiles/AGENT_PRECOMPILES.md) +(`docs/precompiles/AGENT_PRECOMPILES.md`). This page does not repeat them. + ## Design rationale The registry keeps a model as a small record and pushes the work to the precompiles because the weights @@ -238,6 +290,14 @@ These contracts move value and gate access, so the sharp edges are worth naming. - **Unverified reviews.** A marketplace review does not require a verified purchase, so the average rating can be moved by addresses that never bought the model; the `verified` flag distinguishes them but does not exclude them from the average. +- **Inference calls revert on 40204.** `requestInference` on the registry, `inferWithLoRA`, and both + inference paths of ModelAccessControl revert with `PrecompileUnavailable(0x0101)` (or `0x0106`) on every + 40204 node today, and any payment sent with them is returned by the revert. Earlier builds called + addresses (`0x1000`, `0x1001`) that nothing served; those calls are gone. +- **Agent precompiles where they are not active.** On 40204 after the 2026-10-05 reroll these are live + from genesis. On any other network, code that uses `0x0112`, `0x0113`, `0x0121` or `0x0122` through the + library reverts until that network's activation height is reached. Handle the revert; do not catch it + and treat it as a negative answer. - **Inert and placeholder surfaces.** `setRegistrationFee` on the registry always reverts by design. On ModelAccessControl, `getModelStats` reports a placeholder zero for unique users and `updatePrecompileAddress` does nothing. @@ -250,15 +310,18 @@ commercial, the paid-seat depth covering marketplace economics, the adapter pipe staking design. No secrets appear on this page. There are no private keys, mnemonics, internal hostnames, or -credentials. The only hardcoded addresses are public protocol precompiles: `0x1000`, `0x1001`, `0x0101`, -`0x0106`, and `0x0108`. Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity. +credentials. The only hardcoded addresses are public protocol precompiles: `0x0101`, `0x0106`, `0x0108`, +`0x0112`, `0x0113`, `0x0121`, and `0x0122` (and the retired `0x1000`, `0x1001` and `0x1002`, named only to +say they are gone). Identity verification through VERI, Citrate's in-house verification, is part of membership; node and consensus code do not check operator identity. ## Source and verification - Source: `citrate-chain/contracts/src/`, in `ModelRegistry.sol`, `InferenceRouter.sol`, `ModelMarketplace.sol`, `LoRAFactory.sol`, and `ModelAccessControl.sol`, with interfaces `interfaces/IModelRegistry.sol` and `interfaces/IModelMarketplace.sol`. -- Audited against `citrate-chain` SHA `9d5959e`. +- Audited against `citrate-chain` SHA `fa7c913`, the head of the agent precompile fork stack (pull + request 273 and the stack above it). Until that stack reaches `main`, the deployed contracts and `main` + still carry the old constants; this page describes the code that ships with the fork. - Status: Implemented, pre-audit, on testnet 40204. The LoRAFactory reentrancy gap, the InferenceRouter guard mismatch, the unverified-review weighting, and the ModelAccessControl placeholders are open items noted above and not yet externally audited. Re-verify deployed bytecode with `eth_getCode` if the chain diff --git a/content/core/fleet-wizard.md b/content/core/fleet-wizard.md new file mode 100644 index 00000000..8b1146c2 --- /dev/null +++ b/content/core/fleet-wizard.md @@ -0,0 +1,87 @@ +--- +title: Connect your machines (fleet wizard) +codex_slug: /core/fleet-wizard +tier: public +source_kind: authored +source: citrate-core/src-tauri/src/fleet*.rs, src/fleet, docs/FLEET_WIZARD_RUNBOOK.md +surfaces: [CORE-cluster] +audited_against_sha: 81ef7a0 +status: Implemented (pre-audit), arrives with Citrate Core 0.5.0; two-machine run on member hardware pending +created: 2026-10-04T00:00:00Z +branch: hup/n7-docs-almanac-retro +author: Larry Klosowski + Claude Opus 5.5 +nav_order: 12 +--- + +The fleet wizard connects the machines you own that run Citrate Core, so they know about each other +and can work as one fleet. This page is for members with a second laptop, a desktop or a home server. +The technical runbook is +[FLEET_WIZARD_RUNBOOK.md](https://github.com/CitrateNetwork/citrate-core/blob/main/docs/FLEET_WIZARD_RUNBOOK.md) +in the core repo. + +## What it is + +The wizard lives on the **Cluster** surface. It checks this machine, can look for your other +machines on the local network if you allow it, pairs two machines with a one-time link or QR code, +and helps with Tailscale when the machines cannot reach each other. When both machines are linked to +you, the pairing also carries each machine's link code, so each one knows the other is yours. + +## How to use it + +1. On the machine that already runs Citrate Core, open **Cluster** and start the wizard. It shows + this machine's tier (from the same local check as onboarding) and a suggested role. Rename the + machine if you like; that name is what your other machines see. +2. Optional: tick **Find machines** to look for other Citrate Core machines on this network. It is + off by default and turns itself off when the wizard closes. +3. Choose **Create pairing link**. The wizard shows a `citrate://pair` link and the same link as a + QR code. It works once and expires after 10 minutes. +4. On the new machine, install Citrate Core if needed (the pair step shows the download link and a + QR code for it). Paste the pairing link, choose **Check link**, then **Pair with this machine**. + Opening the link from the system also works; nothing pairs until you press **Pair**. +5. If the machines cannot reach each other, the wizard reads Tailscale's status and tells you what + to do next. +6. **Your machines** lists this machine, the ones you paired and the ones seen on the network, each + with tier and role. + +## Reference + +| Property | Value | +|---|---| +| Pairing link lifetime | 10 minutes (a default pending owner sign-off) | +| Uses per link | one; a second use is refused as "already used" | +| Open links per machine | at most 8 | +| What discovery shares | a random per-run id, the machine name you typed, tier and role; never an account address or the computer's name | +| Roles | T0 light, T1 worker, T2 heavy (defaults pending owner sign-off) | +| Tailscale | read only: the wizard runs `tailscale status` and never signs you in or changes its settings | + +## Design rationale + +A pairing link is signed with a key that exists only in memory while the app runs. It is never the +key for your account and can sign nothing but pairing links, so pairing two machines cannot move +value. A short lifetime and a single use mean a link that leaks in a chat or a screenshot is soon +worthless. Discovery is opt-in and forgets your choice on restart, because announcing yourself on a +shared network should be a decision you make each time. + +## Failure modes + +- **macOS asks to accept incoming connections** when you create a link. Allow it, or pairing over + the local network cannot reach this machine. +- **No other machines answered.** The other machine needs discovery on too, and some guest or office + networks block it. Pairing by link does not need discovery. +- **The other machine could not be reached.** Check the firewall, or turn on Tailscale on both + machines with the same account and create a new link. +- **"Already used" or "expired".** Create a new link on the first machine. +- **A machine that belongs to someone else.** If the pairing carries another member's link code, + the machine is reported and not added as yours. + +## Access and canon + +The roster of your machines is a file in the app data folder on each machine. Nothing about the +fleet is published to the network by the wizard. + +## Source and verification + +Source: `citrate-core/src-tauri/src/fleet.rs`, `fleet_pairing.rs`, `fleet_mdns.rs`, +`fleet_tailscale.rs`, and `src/fleet/`. Audited against core `81ef7a0`. Status: **Implemented**, +pre-audit. Device links are **Verified** as a TLA+ model (`DeviceLink.tla` in `citrate-cluster`) +checked with TLC at small bounds. A recorded two-machine run on member hardware is still pending. diff --git a/content/core/hermes.md b/content/core/hermes.md new file mode 100644 index 00000000..f3b7d9a3 --- /dev/null +++ b/content/core/hermes.md @@ -0,0 +1,91 @@ +--- +title: Hermes, the agent in Citrate Core +codex_slug: /core/hermes +tier: public +source_kind: authored +source: citrate-core/src/agent, src-tauri/src/hermes.rs; citrate-agent-runtime/agent-sidecar, agent-loop +surfaces: [CORE-agent] +audited_against_sha: 81ef7a0 +status: Implemented (pre-audit), arrives with Citrate Core 0.5.0 +created: 2026-10-04T00:00:00Z +branch: hup/n7-docs-almanac-retro +author: Larry Klosowski + Claude Opus 5.5 +nav_order: 8 +--- + +Hermes is the agent that runs inside Citrate Core, on your own machine and on a model your machine +can hold. This page is for members who want to know what Hermes can do in Citrate Core 0.5.0, what it +asks you before it acts, and where the technical detail lives. + +## What it is + +Hermes proposes; you decide. It can read, plan, search, write inside folders you grant, run a +contract through tests and audits, and prepare an on-chain action. Every effect on the world, such +as a signature, a transaction, a file write outside a granted folder, a shell command, or a skill or +memory it wants to keep, waits behind a Human In Control (HIC) gate. Nothing is reported as done +unless a check outside the model, such as a passing test or an audit report, says so. + +Hermes runs as a separate process next to the app, the sidecar. The sidecar holds no key and cannot +sign. When Hermes needs a signature it asks Citrate Core, and Core opens the same signing ceremony +you already use for every other approval. The chat in the app, the `citrate-agent` command line, and +an MCP client can all look at the same Hermes session. + +## How to use it + +1. Open the **Agent** surface. Hermes starts with the model your machine was matched to during + onboarding (the tier probe picks it; a small machine gets a small model). +2. Ask for what you want in plain language. For a longer piece of work, pick a track (full project, + smart contract, code, creative, or project management) and answer its short interview. +3. Pick a voice if you like, at the end of onboarding or in **Settings** under **Hermes, persona**. + Hermes ships with six [personas](/core/personas); each changes tone and the skills on offer, never + the approval rules. +4. Watch the approvals. An action that needs you appears as a card that says what will happen. + Allow it once, or deny it. +5. Turn on the extras you want in **Settings**. Web search, page reading, the managed browser, the + shell and the [node MCP server](/core/node-mcp) are all off until you turn them on. + +## Reference + +| Ability | Default | Where it is described | +|---|---|---| +| Chat with a local model | on | [Getting started](/core/getting-started) | +| Folder grants: read and write only inside folders you choose | no folder granted | [agent-grants](https://github.com/CitrateNetwork/citrate-agent-runtime/tree/main/agent-grants) | +| Web search, page reading and the decide step | off | [HERMES_WEB_SEARCH_AND_DECIDE](https://github.com/CitrateNetwork/citrate-core/blob/main/docs/HERMES_WEB_SEARCH_AND_DECIDE.md) | +| The Browser pop-out, where you watch Hermes browse | off | [HERMES_BROWSER](https://github.com/CitrateNetwork/citrate-core/blob/main/docs/HERMES_BROWSER.md) | +| Sign-in to sites you chose, a bounded number of times | no budget granted | [WEB_SIGNING_BUDGETS](https://github.com/CitrateNetwork/citrate-core/blob/main/docs/WEB_SIGNING_BUDGETS.md) | +| Tools from MCP servers you add | none added | [MCP_USER_SERVERS](https://github.com/CitrateNetwork/citrate-core/blob/main/docs/MCP_USER_SERVERS.md) | +| Skills, and learning a new one from verified work | reviewed set installed | [Skills](/core/skills) | +| Deploying a contract through the deploy gate | deploy waits for a READY verdict and your approval | the DeployGate section of [formal/README](https://github.com/CitrateNetwork/citrate-core/blob/main/src-tauri/formal/README.md); deploy gas from the faucet (off by default): [FAUCET_IN_APP](https://github.com/CitrateNetwork/citrate-core/blob/main/docs/FAUCET_IN_APP.md) | +| Connecting your other machines | off | [Fleet wizard](/core/fleet-wizard) | + +## Design rationale + +Open agents fail in familiar ways: they grade their own work as a success, they act before asking, +and they hang without saying why. Hermes is built against each of those. The model never decides +that a step is done; verifiers do. The sidecar cannot sign at all, so a mistake in the agent cannot +move value without you. Each risky ability starts off, so a member who never opens Settings gets the +same app as before plus a better chat. + +## Failure modes + +- **A locked account.** Any request that would sign fails closed. Unlock and ask again. +- **Untrusted content.** Once a session has read a web page or MCP output, it is marked as tainted. + A tainted session cannot keep a skill or memory, and its effectful calls always ask you. +- **The sidecar stops.** Core restarts it under a supervisor. A request that never reached a + decision is not signed later; Hermes has to ask again. +- **A small model.** Small models make more tool-call mistakes. Hermes on a small machine uses a + guided mode with fewer tools at a time. + +## Access and canon + +Hermes runs on your hardware. Prompts, files and memories stay on your machine unless you turn on a +feature that says it sends something out (web search sends the query; the Jina reader sends the URL). +Every decision you make on an approval card is written to a local decision log. + +## Source and verification + +Source: `citrate-core` (the app and Core side) and `citrate-agent-runtime` (the sidecar, the loop, +the tools). Audited against core `81ef7a0`. Status: **Implemented**, pre-audit, arriving with +Citrate Core 0.5.0. The agent loop, folder grants, sign-in budgets, the deploy gate and the spend +budget are each **Verified** as a TLA+ model checked with TLC at small bounds; that checks the +design, not the code. diff --git a/content/core/node-mcp.md b/content/core/node-mcp.md new file mode 100644 index 00000000..8a3c14e4 --- /dev/null +++ b/content/core/node-mcp.md @@ -0,0 +1,83 @@ +--- +title: Use your node from another agent (node MCP server) +codex_slug: /core/node-mcp +tier: public +source_kind: authored +source: citrate-core/src-tauri/src/node_mcp_*.rs, docs/NODE_MCP_SERVER.md +surfaces: [CORE-settings, CORE-agent] +audited_against_sha: 81ef7a0 +status: Implemented (pre-audit), off by default, arrives with Citrate Core 0.5.0 +created: 2026-10-04T00:00:00Z +branch: hup/n7-docs-almanac-retro +author: Larry Klosowski + Claude Opus 5.5 +nav_order: 9 +--- + +Citrate Core can act as an MCP server, so an agent you already use (Claude Code, Cursor, or Hermes +itself) can read your node and ask you to approve an action. This page is for members who want to +connect one. The full technical reference is +[NODE_MCP_SERVER.md](https://github.com/CitrateNetwork/citrate-core/blob/main/docs/NODE_MCP_SERVER.md) +in the core repo. + +## What it is + +MCP (the Model Context Protocol) is a common way for an agent to discover and call tools. With the +node MCP server on, Citrate Core listens on this computer only and offers a set of read tools (node +status, the chain head, balances, contract reads, the shared knowledge graphs, your clusters) and a +small set of write tools. A write tool never acts by itself: it creates a request, the request shows +up in the app, and anything that signs goes through the signing ceremony you already know. + +## How to use it + +1. In Citrate Core, open **Settings**, then **API endpoints & keys**, then **Node MCP server**. +2. Turn it on. It listens at `http://127.0.0.1:47204/mcp`, on this computer only. +3. Create a connect token and give it the name of the client it is for. The token is shown once, + with ready-made commands for that client. Copy it then; Core keeps only a hash of it. +4. Add the server to your client. For Claude Code, paste the command the panel shows. The panel also + offers a stdio form that runs the app binary as a small relay. +5. Ask your agent something simple, such as "use citrate-node to show the chain head". +6. Revoke a token from the same list at any time. Its next request is refused and its pending + requests close. + +To let Hermes use the same tools, turn on **Your node** in the Agent surface under **Connected tools +(MCP)**. Hermes gets the read tools only. + +## Reference + +| Kind | Examples | What happens | +|---|---|---| +| Read tools | `node_status`, `chain_head`, `get_balance`, `chain_call`, `get_logs`, `memory_search`, `cluster_status` | Answer at once. Each answer says whether it came from your node or the public 40204 endpoint. | +| Write tools | `tx_propose`, `deploy_propose`, `pin_add`, `invite_create`, `cluster_join` | Create a request you approve in the app. A transaction is signed only through the signing ceremony. | +| Budgeted tool | `faucet_request` | Runs inside a faucet budget you granted in Settings, Budgets. Off by default. | +| Status | `request_status` | A client sees only its own requests: pending, approved, rejected, failed or expired. | + +The complete list, the limits and the protocol notes are in +[NODE_MCP_SERVER.md](https://github.com/CitrateNetwork/citrate-core/blob/main/docs/NODE_MCP_SERVER.md). + +## Design rationale + +A connect token is a password for one client, so it is shown once, stored only as a hash, and +revocable. The server binds to loopback so nothing on your network can reach it. Before the stdio +relay sends a token at all, it checks that the program on the port really is Citrate Core, so +another program that grabs the port while Core is closed never sees a token. Write tools only queue +requests, because an agent outside the app should have no more power than Hermes has inside it. + +## Failure modes + +- **The port is taken.** The panel shows the error. The port is a default pending owner sign-off. +- **A token leaks.** Revoke it. Its sessions end and its pending requests close. +- **A deploy is not ready.** `deploy_propose` is refused at once, naming the failing checks, unless + the deploy gate marked exactly that code READY. +- **Your node is still syncing.** Reads fall back to the public 40204 endpoint and say so. + +## Access and canon + +The server is off until you turn it on and listens on this computer only. Personal memory is never +offered to a token. Hermes's own token is read-only and lives in memory until the app quits. + +## Source and verification + +Source: `citrate-core/src-tauri/src/node_mcp_*.rs` and `hermes_mcp.rs`. Audited against core +`81ef7a0`. Status: **Implemented**, pre-audit, off by default. External client runs with Claude Code +and with Hermes are recorded in the core reference. The port, the task lifetime and whether Hermes +may use write tools are defaults pending owner sign-off. diff --git a/content/core/personas.md b/content/core/personas.md new file mode 100644 index 00000000..957f1240 --- /dev/null +++ b/content/core/personas.md @@ -0,0 +1,83 @@ +--- +title: Hermes personas and tracks +codex_slug: /core/personas +tier: public +source_kind: authored +source: citrate-agent-runtime/agent-loop/personas/personas.toml, agent-loop/tracks, agent-loop/PERSONAS.md; citrate-core/src/components/PersonaPicker.tsx +surfaces: [CORE-agent, CORE-settings] +audited_against_sha: 81ef7a0 +status: Implemented (pre-audit), arrives with Citrate Core 0.5.0 +created: 2026-10-04T00:00:00Z +branch: hup/n7-docs-almanac-retro +author: Larry Klosowski + Claude Opus 5.5 +nav_order: 11 +--- + +A persona is a voice for Hermes: how it writes, which skills it reaches for, and where it starts. A +track is a goal, such as a full project or a contract audit. This page is for members choosing a +persona or a track. The technical reference is +[PERSONAS.md](https://github.com/CitrateNetwork/citrate-agent-runtime/blob/main/agent-loop/PERSONAS.md) +in the runtime repo. + +## What it is + +Each persona bundles a voice and tone, a few writing rules, a default track and workflow, up to four +tools it keeps in view, and a list of the skills it may load. Any persona can run any track. A persona +changes tone and the skills on offer. It never grants a tool, changes an approval, or touches the +signing ceremony. + +## How to use it + +1. Choose a persona at the end of onboarding, or later in **Settings** under **Hermes, persona**. + The default is Hermes's own voice, which changes nothing. +2. Start a piece of work. Hermes opens with the persona's default track unless you pick another. +3. Run a track workflow from the chat with `/run `, for example `/run status-note`. +4. To make your own persona, use the custom form in the same Settings card: a name, a voice, a tone + and one to twelve writing rules. It is checked before it is saved, and it stays on your machine. +5. Turn on **Read replies aloud** if you want Hermes to speak. It is off by default and uses your + system's voice. + +## Reference + +The six shipped personas. The names were chosen by the owner on 2026-10-01; the role is the stable +part, and a rename never loses your saved choice. + +| Name | Role | Voice | Starts with | +|---|---|---|---| +| Graft | Builder: ships code and dApps | Direct and terse; shows the diff or the command | full project, hello mint | +| Pith | Auditor: a skeptical reviewer | Calm and evidence-first; says "not ready" plainly | smart contract, audit a contract | +| Zest | Maker: creative work | Playful and visual; offers options | creative, creative project | +| Trellis | Steward: plans and tracks | Organized and brief; checklists | project management, project plan | +| Sprout | Guide: onboarding and teaching | Warm and patient; explains why | full project, launch checklist | +| Crew | Operator: nodes, fleet and learning together | Precise and numbers-first | project management, status note | + +The five tracks are **full project**, **smart contract**, **code**, **creative** and **project +management**. Each owns a short interview and a family of workflows. A workflow is a list of steps, +and only its checks (tests, scans, required answers) say a step is done. + +Guide and Operator have no track of their own yet; they start on the nearest one. That choice, and +the unset speaking voice, are shipped defaults pending owner sign-off. + +## Design rationale + +Keeping voice and capability apart is what makes personas safe to customize. A custom persona can +change how Hermes talks, but there is nothing in a persona that could widen what Hermes is allowed to +do. Narrowing the skill list per persona also keeps each request small, which matters on a local +model. + +## Failure modes + +- **A persona names a skill that is not installed.** It is skipped and reported, never invented. +- **A custom persona is malformed.** The check refuses it with a reason; nothing is saved. +- **A custom persona tries to add a heading or instructions.** Each field is collapsed to one line, + so it cannot open a new section of the prompt. + +## Access and canon + +Your persona choice and any custom persona are stored with your local settings. + +## Source and verification + +Source: `citrate-agent-runtime/agent-loop/personas/personas.toml` (the one data file for shipped +personas), `agent-loop/tracks/`, and `citrate-core/src/components/PersonaPicker.tsx`. Audited against +core `81ef7a0`. Status: **Implemented**, pre-audit. diff --git a/content/core/skills.md b/content/core/skills.md new file mode 100644 index 00000000..b9ece25c --- /dev/null +++ b/content/core/skills.md @@ -0,0 +1,87 @@ +--- +title: Hermes skills, and how Hermes learns +codex_slug: /core/skills +tier: public +source_kind: authored +source: citrate-core/skills.lock, src-tauri/skills, docs/HERMES_LEARNING.md; citrate-agent-runtime/agent-loop/src/skills.rs, agent-learn +surfaces: [CORE-agent] +audited_against_sha: 81ef7a0 +status: Implemented (pre-audit), arrives with Citrate Core 0.5.0; publishing to the network is off +created: 2026-10-04T00:00:00Z +branch: hup/n7-docs-almanac-retro +author: Larry Klosowski + Claude Opus 5.5 +nav_order: 10 +--- + +A skill is a short written method that Hermes can load when a task calls for it, such as how to +review a contract or how to write a status note. This page is for members who want to know where +Hermes's skills come from, how to see them, and how Hermes may add one of its own. + +## What it is + +Each skill is a folder with a `SKILL.md` file: a name, a one-line description, and the method in +plain text. Hermes keeps a short index of the skills on offer and loads the full text of one only +when it needs it, which keeps each request small enough for a local model. + +Skills come from three places: + +- **The reviewed set.** Third-party skills from Trail of Bits, frontend-skills, agentile-skills and + the open-source hermes-agent project, each reviewed one by one before it ships. The review records + the source commit and a hash of every file in `skills.lock`. On 2026-10-01 the lock held 296 + reviewed skills, of which 240 ship. Scripts and other executables are stripped from every skill + that ships. +- **Citrate skills.** Four skills written for Citrate itself: paraconsensus, the precompiles, Belnap + aggregation and sidecar consensus. +- **Skills Hermes learned.** Skills that you accepted from Hermes's own verified work (below). + +## How to use it + +1. In the **Agent** surface, ask Hermes which skills it has. A [persona](/core/personas) narrows + the list to the skills that fit its role. +2. Ask for the work, not the skill. Hermes loads a skill when the task matches its description. +3. To teach Hermes, open **What Hermes learned**, then **Teach Hermes**. Write a task and the phrases + a correct answer must contain. Hermes runs it on your local model. +4. If every check passed, Hermes may propose keeping a skill or a memory. Review the card, which shows + the content and every check result, then choose **Accept** or **Reject**. + +## Reference + +| Rule | What it means for you | +|---|---| +| Only verified work | A proposal must come from a run whose checks all passed. Hermes saying it succeeded never counts. | +| Nothing is kept without you | Every proposal waits for Accept or Reject, and the decision is logged first. | +| Untrusted input blocks learning | A session that read a web page or MCP output cannot propose anything. | +| Contradictions are shown, not merged | A memory that disagrees with one you have is kept alongside it as unresolved until you choose **Keep this one**. | +| Publishing is a signature | Sending a skill to the on-chain SkillRegistry needs one approval per publish through the signing ceremony. It is off in this release. | + +The technical flow is in +[HERMES_LEARNING.md](https://github.com/CitrateNetwork/citrate-core/blob/main/docs/HERMES_LEARNING.md), +and the review of every third-party skill is in the core repo under +[.agentile/skill-intake](https://github.com/CitrateNetwork/citrate-core/tree/main/.agentile/skill-intake). + +## Design rationale + +Open agents tend to grade their own runs as a success and then save the mistake as a skill. Hermes +separates the two: a check outside the model decides whether a run passed, and you decide whether +anything is kept. Third-party skills are pinned by hash so that a later change upstream cannot +change what Hermes reads without a new review. + +## Failure modes + +- **A skill does not load.** The loader is strict and refuses a malformed skill instead of guessing. + The skill stays out of the index. +- **A skill file changed on disk.** It no longer matches its recorded hash and is refused. +- **A proposal from an unverified run.** It is refused before it reaches you. + +## Access and canon + +Skills and learned memories live in your app data folder on your machine. Nothing leaves it unless +you publish, and publishing is off in this release. + +## Source and verification + +Source: `citrate-core/skills.lock`, `src-tauri/skills/`, `docs/HERMES_LEARNING.md`, and in +`citrate-agent-runtime` the skill loader (`agent-loop/src/skills.rs`) and `agent-learn`. Audited +against core `81ef7a0`. Status: **Implemented**, pre-audit. Skill persistence is **Verified** as a +TLA+ model (`SkillPersistence.tla`) checked with TLC at small bounds. "Teach Hermes" has not yet been +run in a packaged build. diff --git a/package-lock.json b/package-lock.json index 69d9907a..87b7cbe4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "ai": "^6.0.193", "jose": "^6.2.3", "mermaid": "^11.15.0", - "next": "^16.3.3", + "next": "^16.3.8", "react": "^19.0.0", "react-dom": "^19.0.0", "react-markdown": "^10.1.0", @@ -768,15 +768,15 @@ } }, "node_modules/@next/env": { - "version": "16.3.3", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.3.3.tgz", - "integrity": "sha512-U2eYQRwXj+dsqxV79zFqExDdatnNY/ZWc2nsJU1p/OgT7fd3dXwlF6OjYaFQCfMoeTA19PWq+wVmYgimVA+V+g==", + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.3.8.tgz", + "integrity": "sha512-Al9zqHVV7TJv0eFuOU4U7Lvv74PTih4Ch63sk2xCIpSTkE3udFnaOcnzP2lQVymiL7yS9Cj2iClUXlR3EQ5sEw==", "license": "MIT" }, "node_modules/@next/swc-darwin-arm64": { - "version": "16.3.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.3.3.tgz", - "integrity": "sha512-8Hiv32QJPwdV6KYJ8meR9SBA061tQqnIKTJDocvOXlEQqib0xMFpzArosuffFUUc0sslbh7QQ8a3Yey1QV8EIw==", + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.3.8.tgz", + "integrity": "sha512-2JPRMh2nmQG5CiL7cXGL9AGwnPWJQ//cTtAUCT+w511QHk79SYz3LGv/pc5X643B/WEO0rvu3Yww0hqwt3kgeA==", "cpu": [ "arm64" ], @@ -790,9 +790,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "16.3.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.3.3.tgz", - "integrity": "sha512-A1lgKgwVchRYmSe467zdwhxT9040dd8lH+o65sL5Jet8fjB4kegw/rDyPIpYVRb6jAqwXFOJpjIXJLxQKLiE3A==", + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.3.8.tgz", + "integrity": "sha512-GZtCCOBKJ4leVIT/Th0llWKhD1ca92lzbQiS5R5ON9QkoiFnilFsebDae1JU2a3HWoKMEmEZWGs1AGLavVM72Q==", "cpu": [ "x64" ], @@ -806,9 +806,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.3.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.3.3.tgz", - "integrity": "sha512-bf0FIssMFueU2dm7vQEWWxk0c8UjKTdW0yzuh0sQsD8pf1+KCLDdaqhYZNMYGmXwEOiHAUzgBKudovIlcvvBjg==", + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.3.8.tgz", + "integrity": "sha512-O659ygeQYqneJ1fBKMpFxIFqYkYswu8IAS1OCKK/4f3ZgJJm1dRz4fVJZRi/kLLWjnBKnebOePA4WNv+sV1pVA==", "cpu": [ "arm64" ], @@ -825,9 +825,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.3.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.3.3.tgz", - "integrity": "sha512-W7viwCk9JY/cAkdz/A273rd5bb3RgT/IHwR7Upv90tunjBWNtAAhGhoecHh+teRNRSinuAFmE+l7fwZ4YKkrXg==", + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.3.8.tgz", + "integrity": "sha512-dSjKSyWpzxoO1d3DIZZcP4XJcNaKeLmxQMFOiYl5vuBRMmweIqnAhty8tAmRsvTss779cK1FtYnDMj40e4TQlg==", "cpu": [ "arm64" ], @@ -844,9 +844,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.3.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.3.3.tgz", - "integrity": "sha512-0W46zw1N3ODpI6n0GeivHvvob1pooozgZVqy65k0mh4/7vr+FbY9+WpHzNVXjHipJf/A3FDheBG19H1s5A25rA==", + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.3.8.tgz", + "integrity": "sha512-lbqOuz3RPRcv+o9msNsJw5x4+Y1ZwPTs6vmL6DCf7i0fZfvng/F59wyeDwqHIvV0mK//RBy/jJkZ+nCKsSMXjQ==", "cpu": [ "x64" ], @@ -863,9 +863,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "16.3.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.3.3.tgz", - "integrity": "sha512-H4mBso8ZTMBPtdT0PN0pBx2ayTvQuTuvS6qT13d77yVFJXAPCxkyIhLTmdMaGTJs0krQYI/qpzdHijCeihXhbg==", + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.3.8.tgz", + "integrity": "sha512-+316WswI8ScVgZeUd+1KGaXkHhaYQzCjvH/05TZSpJ8zBizb1a4G7DtO7F12jcBIqMOtsz9ji1t48fmKtzqsGA==", "cpu": [ "x64" ], @@ -882,9 +882,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.3.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.3.3.tgz", - "integrity": "sha512-cTMUJpcEGmeywofCUfhR+rSsoE33+rVPnPEYNTNdLNlsOeEg/vktOsKUSTb28vUGqD2jkm4Zaskcwn7OCI6FQg==", + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.3.8.tgz", + "integrity": "sha512-ji0gd4kMYUxO+1fJBIbiBVRCjzG/lloiyCccnlebvb1ZJ5qXCPZqYg4Jl1DrrixnWNMKylzgpmMWx0yNDYXlzw==", "cpu": [ "arm64" ], @@ -898,9 +898,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.3.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.3.3.tgz", - "integrity": "sha512-2VR4cTBzHXaBjnGsuH6GyJjENzQOmHeAh11uY1iUhjm3j5dEUrVJuUj+VL78jaGi/Dik8xS76zEj18BsFhlVZQ==", + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.3.8.tgz", + "integrity": "sha512-WcTlaKt/TWkh5kUjdJcUmB1XgZ+1c6fz4Y9fDHL73YNSdGaUWjceeWrrlwF0nv19iABYWC4iAq1oX1w4Bn0vfg==", "cpu": [ "x64" ], @@ -1462,6 +1462,72 @@ "node": ">=14.0.0" } }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { + "version": "2.8.1", + "dev": true, + "inBundle": true, + "license": "0BSD", + "optional": true + }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", @@ -4365,12 +4431,12 @@ } }, "node_modules/next": { - "version": "16.3.3", - "resolved": "https://registry.npmjs.org/next/-/next-16.3.3.tgz", - "integrity": "sha512-tuRTx1nQ/yVw83cwJBo9F+njGUgMn3UHQycreWHB8XsStvvAh1AthbI8/4IpKnFaF58F+iSiHejYOlMQ/eq83g==", + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/next/-/next-16.3.8.tgz", + "integrity": "sha512-U7QEZaTini6wKrb8A8hqLLqYQyCetegKjCpJOyxk642vWoMoU1x5PyZCJFvgYgiptA8xc5j/9xYlZFO7w9Sjmw==", "license": "MIT", "dependencies": { - "@next/env": "16.3.3", + "@next/env": "16.3.8", "@swc/helpers": "0.5.23", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", @@ -4384,15 +4450,15 @@ "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.3.3", - "@next/swc-darwin-x64": "16.3.3", - "@next/swc-linux-arm64-gnu": "16.3.3", - "@next/swc-linux-arm64-musl": "16.3.3", - "@next/swc-linux-x64-gnu": "16.3.3", - "@next/swc-linux-x64-musl": "16.3.3", - "@next/swc-win32-arm64-msvc": "16.3.3", - "@next/swc-win32-x64-msvc": "16.3.3", - "sharp": "^0.35.3" + "@next/swc-darwin-arm64": "16.3.8", + "@next/swc-darwin-x64": "16.3.8", + "@next/swc-linux-arm64-gnu": "16.3.8", + "@next/swc-linux-arm64-musl": "16.3.8", + "@next/swc-linux-x64-gnu": "16.3.8", + "@next/swc-linux-x64-musl": "16.3.8", + "@next/swc-win32-arm64-msvc": "16.3.8", + "@next/swc-win32-x64-msvc": "16.3.8", + "sharp": "^0.35.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", diff --git a/package.json b/package.json index 2682b58a..87210e50 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "ai": "^6.0.193", "jose": "^6.2.3", "mermaid": "^11.15.0", - "next": "^16.3.3", + "next": "^16.3.8", "react": "^19.0.0", "react-dom": "^19.0.0", "react-markdown": "^10.1.0",