--- url: /reference/sati-agent0-sdk.md description: API reference for the agent0-compatible SATI adapter --- # @cascade-fyi/sati-agent0-sdk Thin adapter that wraps [@cascade-fyi/sati-sdk](/reference/sati-sdk) with [agent0-sdk](https://www.npmjs.com/package/agent0-sdk) types (`AgentId`, `Feedback`, `AgentSummary`, etc.). Use this if your codebase already depends on agent0-sdk or you need ERC-8004 type compatibility. For Solana-native development, start with [sati-sdk](/reference/sati-sdk) directly. \[\[toc]] ## SatiAgent0 ### Constructor ```typescript import { SatiAgent0 } from "@cascade-fyi/sati-agent0-sdk"; const sdk = new SatiAgent0(config: SatiAgent0Config); ``` #### SatiAgent0Config | Field | Type | Required | Description | |-------|------|----------|-------------| | `network` | `"mainnet" \| "devnet" \| "localnet"` | Yes | SATI network | | `signer` | `KeyPairSigner` | No | Server-side write access | | `transactionSender` | `SatiTransactionSender` | No | Browser wallet write access | | `rpcUrl` | `string` | No | Custom RPC URL (overrides network default) | | `pinataJwt` | `string` | No | Pinata JWT for IPFS uploads | | `onWarning` | `(warning: SatiWarning) => void` | No | Non-fatal warning callback | ### Properties | Property | Type | Description | |----------|------|-------------| | `network` | `"mainnet" \| "devnet" \| "localnet"` | Configured network | | `chain` | `string` | CAIP-2 chain reference | | `isReadOnly` | `boolean` | `true` if no signer/sender configured | | `sati` | `Sati` | Underlying low-level SATI client | | `feedbackSchema` | `string \| undefined` | Feedback schema address | | `feedbackPublicSchema` | `string \| undefined` | FeedbackPublic schema address | | `validationSchema` | `string \| undefined` | Validation schema address | | `lookupTable` | `string \| undefined` | Address Lookup Table address | *** ### Agent Methods #### createAgent Create a new agent in memory. Call `agent.registerIPFS()` or `agent.registerHTTP()` to register on-chain. ```typescript sdk.createAgent(name: string, description: string, image?: URI): SatiAgent ``` #### loadAgent Load an existing agent by its CAIP-2 agent ID. ```typescript await sdk.loadAgent(agentId: AgentId): Promise ``` #### searchAgents Search agents with filters. ```typescript await sdk.searchAgents( filters?: SearchFilters, options?: SatiSearchOptions, ): Promise ``` **SatiSearchOptions** extends agent0-sdk's `SearchOptions`: | Field | Type | Default | Description | |-------|------|---------|-------------| | `includeFeedbackStats` | `boolean` | `false` | Fetch feedbackCount + averageValue per agent (slower) | | `limit` | `number` | `100` | Max results | | `offset` | `bigint` | - | Offset (1-based member number) for pagination | | `sort` | `string[]` | - | Sort keys, e.g. `["averageValue:desc"]` | **SearchFilters** - agent0-sdk compatible. Key fields: | Field | Type | Description | |-------|------|-------------| | `name` | `string` | Substring match on agent name | | `agentIds` | `AgentId[]` | Filter to specific agents | | `hasMCP` / `hasA2A` | `boolean` | Require endpoint type | | `mcpTools` / `a2aSkills` | `string[]` | Match at least one listed tool/skill | | `active` | `boolean` | Filter by active status | | `x402support` | `boolean` | Filter by x402 support | | `feedback` | `FeedbackFilters` | Reputation-based filtering | | `oasfSkills` / `oasfDomains` | `string[]` | OASF taxonomy match | #### transferAgent Transfer agent ownership. ```typescript await sdk.transferAgent( agentId: AgentId, newOwner: Address, ): Promise> ``` *** ### Feedback Methods #### giveFeedback Give feedback to an agent. Uses FeedbackPublicV1 schema. ```typescript await sdk.giveFeedback( agentId: AgentId, value: number | string, // 0-100 tag1?: string, tag2?: string, endpoint?: string, feedbackFile?: FeedbackFileInput, ): Promise> ``` SATI-specific fields can be passed via `feedbackFile`: * `feedbackFile.outcome` - `Outcome.Negative | Outcome.Neutral | Outcome.Positive` (default: Neutral) * `feedbackFile.taskRef` - `Uint8Array` (32 bytes, default: random) #### prepareFeedbackFile Build an optional off-chain feedback file payload. ```typescript sdk.prepareFeedbackFile( input: FeedbackFileInput, extra?: Record, ): FeedbackFileInput ``` #### prepareFeedback Prepare feedback for browser wallet signing (step 1 of 2). ```typescript await sdk.prepareFeedback( agentId: AgentId, value: number, tag1?: string, tag2?: string, opts?: { endpoint?: string; text?: string; counterparty?: string; outcome?: Outcome; taskRef?: Uint8Array; }, ): Promise ``` #### submitPreparedFeedback Submit wallet-signed feedback (step 2 of 2). Requires a `KeyPairSigner` on the SDK. ```typescript await sdk.submitPreparedFeedback( prepared: PreparedFeedback, counterpartySignature: Uint8Array, ): Promise> ``` #### searchFeedback Search feedback with filters. ```typescript await sdk.searchFeedback( filters: FeedbackSearchFilters, options?: SatiFeedbackSearchOptions, ): Promise ``` **SatiFeedbackSearchOptions:** | Field | Type | Default | Description | |-------|------|---------|-------------| | `includeTxHash` | `boolean` | `false` | Populate `txHash` per result (1 RPC call each) | | `minValue` / `maxValue` | `number` | - | Filter by value range | **FeedbackSearchFilters** - key fields: | Field | Type | Description | |-------|------|-------------| | `agentId` | `AgentId` | Filter to specific agent | | `agents` | `AgentId[]` | Filter to multiple agents | | `reviewers` | `Address[]` | Filter by reviewer wallet(s) | | `tags` | `string[]` | Filter by tag1/tag2 | #### getReputationSummary Aggregate feedback stats for an agent. ```typescript await sdk.getReputationSummary( agentId: AgentId, tag1?: string, tag2?: string, ): Promise<{ count: number; averageValue: number }> ``` #### revokeFeedback Revoke feedback. Two overloads: ```typescript // Preferred: pass Feedback object (stable addressing) await sdk.revokeFeedback(feedback: Feedback): Promise> // Legacy: by index (fragile) await sdk.revokeFeedback(agentId: AgentId, feedbackIndex: number): Promise> ``` #### revokeFeedbackByAddress Revoke by compressed account address. ```typescript await sdk.revokeFeedbackByAddress( compressedAddress: string, ): Promise> ``` *** ### Validation Methods #### searchValidations Query validation attestations for an agent. ```typescript await sdk.searchValidations(agentId: AgentId): Promise ``` **ValidationResult:** | Field | Type | Description | |-------|------|-------------| | `outcome` | `number` | 0=Negative, 2=Positive | | `agentMint` | `string` | Agent mint address | | `counterparty` | `string` | Validator address | | `createdAt` | `number` | Unix timestamp (approximate) | | `compressedAddress` | `string` | For tx lookup | *** ## SatiAgent Returned by `sdk.createAgent()` or `sdk.loadAgent()`. ### Properties | Property | Type | Description | |----------|------|-------------| | `agentId` | `AgentId \| undefined` | Set after registration | | `agentURI` | `URI \| undefined` | On-chain metadata URI | | `name` | `string` | Agent name | | `description` | `string` | Agent description | | `image` | `URI \| undefined` | Avatar URL | | `mcpEndpoint` | `string \| undefined` | MCP server URL | | `a2aEndpoint` | `string \| undefined` | A2A agent card URL | | `walletAddress` | `Address \| undefined` | Payment wallet | | `mcpTools` | `string[]` | Auto-fetched MCP tools | | `mcpPrompts` | `string[]` | Auto-fetched MCP prompts | | `mcpResources` | `string[]` | Auto-fetched MCP resources | | `a2aSkills` | `string[]` | Auto-fetched A2A skills | | `oasfSkills` | `string[]` | OASF skill slugs | | `oasfDomains` | `string[]` | OASF domain slugs | ### Configuration Methods All return `this` for chaining (except async methods). ```typescript await agent.setMCP(endpoint: string, version?: string, autoFetch?: boolean): Promise await agent.setA2A(agentcard: string, version?: string, autoFetch?: boolean): Promise agent.setENS(name: string, version?: string): this agent.setWallet(addr: Address): this agent.addSkill(slug: string): this agent.removeSkill(slug: string): this agent.addDomain(slug: string): this agent.removeDomain(slug: string): this agent.setActive(active: boolean): this agent.setX402Support(x402Support: boolean): this agent.setTrust(reputation?: boolean, cryptoEconomic?: boolean, teeAttestation?: boolean): this agent.setMetadata(kv: Record): this agent.updateInfo(name?: string, description?: string, image?: URI): this agent.removeEndpoint(opts?: { type?: EndpointType; value?: string }): this agent.removeEndpoints(): this ``` ### Registration Methods ```typescript // Register new agent via IPFS (requires pinataJwt in SDK config) await agent.registerIPFS(): Promise> // Register new agent via HTTP URI await agent.registerHTTP(agentUri: URI): Promise> // Update existing agent's IPFS metadata await agent.updateIPFS(): Promise> // Update existing agent's URI to HTTP endpoint await agent.updateHTTP(agentUri: URI): Promise> // Transfer agent to new owner await agent.transfer(newOwner: Address): Promise> ``` *** ## SolanaTransactionHandle\ Returned by all write operations. Compatible with agent0-sdk's `TransactionHandle` pattern. | Member | Type | Description | |--------|------|-------------| | `hash` | `string` | Transaction signature (base58) | | `waitMined()` | `Promise<{ receipt: SolanaReceipt; result: T }>` | Resolves immediately (Solana confirms before returning) | | `waitConfirmed()` | `Promise<{ receipt: SolanaReceipt; result: T }>` | Alias for `waitMined()` | ```typescript const handle = await sdk.giveFeedback(agentId, 85); console.log(handle.hash); // signature const { result: feedback } = await handle.waitMined(); ``` *** ## Error Classes All extend `SatiError` which has a `code: string` property. | Class | Code | Thrown When | |-------|------|------------| | `SatiError` | varies | Base class for all errors | | `AgentNotFoundError` | `AGENT_NOT_FOUND` | Agent mint doesn't exist on-chain | | `ReadOnlyError` | `READ_ONLY` | Write op without signer/sender | | `SignerRequiredError` | `SIGNER_REQUIRED` | Op needs `KeyPairSigner` (not just `TransactionSender`) | | `SchemaNotDeployedError` | `SCHEMA_NOT_DEPLOYED` | SAS schema missing on configured network | | `InvalidAgentIdError` | `INVALID_AGENT_ID` | Malformed CAIP-2 agent ID | | `UnsupportedOperationError` | `UNSUPPORTED_OPERATION` | Op not available on SATI (e.g. `appendResponse`) | *** ## Re-exports The package re-exports commonly used types so consumers don't need to install `agent0-sdk` or `@cascade-fyi/sati-sdk` directly: ```typescript // agent0-sdk types import type { AgentSummary, Feedback, RegistrationFile, AgentId } from "@cascade-fyi/sati-agent0-sdk"; import { EndpointType, TrustModel, EndpointCrawler } from "@cascade-fyi/sati-agent0-sdk"; // SATI types and constants import { SolanaTransactionHandle, SatiError, AgentNotFoundError } from "@cascade-fyi/sati-agent0-sdk"; import { Outcome, ContentType, SATI_PROGRAM_ADDRESS } from "@cascade-fyi/sati-agent0-sdk"; import { parseFeedbackContent, getImageUrl, handleTransactionError } from "@cascade-fyi/sati-agent0-sdk"; ``` ### Adapter Utilities ```typescript import { SOLANA_CAIP2_CHAINS, // { mainnet, devnet, localnet } formatSatiAgentId, // (mint, chain) => AgentId parseSatiAgentId, // (agentId) => mint address toAgentSummary, // SATI identity => AgentSummary toAgent0RegistrationFile, // SATI reg => agent0 RegistrationFile fromAgent0RegistrationFile, // agent0 RegistrationFile => SATI params toAgent0Endpoints, // SATI services => agent0 Endpoint[] fromAgent0Endpoints, // agent0 Endpoint[] => SATI services toFeedback, // SATI attestation => agent0 Feedback } from "@cascade-fyi/sati-agent0-sdk"; ``` --- --- url: /reference/sati-sdk.md description: API reference for the SATI SDK --- # @cascade-fyi/sati-sdk Full-featured SDK for SATI on Solana. Agent registration, feedback, search, reputation queries, and low-level attestation access. \[\[toc]] ## Installation ```bash pnpm add @cascade-fyi/sati-sdk ``` **Peer dependencies:** ```bash pnpm add @solana/kit @solana-program/token-2022 ``` ## Sati Client ```typescript import { Sati } from "@cascade-fyi/sati-sdk"; const sati = new Sati({ network: "devnet", rpcUrl: "https://devnet.helius-rpc.com?api-key=YOUR_KEY", // optional override }); ``` ### SATIClientOptions | Field | Type | Required | Description | |-------|------|----------|-------------| | `network` | `"mainnet" \| "devnet" \| "localnet"` | Yes | SATI network | | `rpcUrl` | `string` | No | Custom RPC URL (overrides network default) | | `wsUrl` | `string` | No | Custom WebSocket URL for subscriptions | | `photonRpcUrl` | `string` | No | Photon RPC URL for Light Protocol queries (defaults to hosted proxy) | | `onWarning` | `(warning: SatiWarning) => void` | No | Non-fatal warning callback (parse errors, RPC failures) | ### Properties | Property | Type | Description | |----------|------|-------------| | `network` | `"mainnet" \| "devnet" \| "localnet"` | Configured network | | `rpc` | `SolanaRpc` | Solana RPC client | | `deployedConfig` | `SATISASConfig \| null` | SAS schema config for this network | | `feedbackPublicSchema` | `Address \| undefined` | FeedbackPublic schema address | | `feedbackSchema` | `Address \| undefined` | Feedback schema address | | `validationSchema` | `Address \| undefined` | Validation schema address | | `lookupTable` | `Address \| undefined` | Address Lookup Table address | *** ## SatiAgentBuilder Fluent builder for agent registration and management. Created via `sati.createAgentBuilder()`. ### Create a Builder ```typescript const builder = sati.createAgentBuilder( "My AI Assistant", "An intelligent assistant for data analysis.", "https://example.com/agent-image.png", ); ``` ### Fluent Configuration All setters return `this` for chaining: ```typescript builder .setMCP("https://mcp.example.com", "2025-06-18", { tools: ["search", "summarize"], prompts: ["code-review"], resources: ["project-context"], }) .setA2A("https://a2a.example.com/.well-known/agent-card.json", "1.0", { skills: ["data-analysis"], }) .setWallet("WalletAddress123") .setActive(true) .setX402Support(true) .setSupportedTrust(["reputation"]) .setExternalUrl("https://myagent.com"); ``` | Method | Parameters | Description | |--------|-----------|-------------| | `setMCP(url, version?, meta?)` | `meta: { tools?, prompts?, resources? }` | Set MCP endpoint with optional capabilities | | `setA2A(url, version?, meta?)` | `meta: { skills? }` | Set A2A endpoint with optional skills | | `setWallet(address)` | `string` | Set payment wallet endpoint | | `setEndpoint(endpoint)` | `Endpoint` | Set a generic endpoint | | `removeEndpoint(name)` | `string` | Remove an endpoint by name | | `setActive(active)` | `boolean` | Set active status | | `setX402Support(x402)` | `boolean` | Set x402 payment support | | `setSupportedTrust(trusts)` | `TrustMechanism[]` | Set trust mechanisms | | `setExternalUrl(url)` | `string` | Set external URL | | `updateInfo(opts)` | `{ name?, description?, image? }` | Update basic info | ::: tip No Auto-Fetch Unlike `sati-agent0-sdk`'s `SatiAgent.setMCP()`, the builder does **not** auto-fetch MCP capabilities. Pass tools/prompts/resources explicitly via the `meta` parameter. ::: ### Register ```typescript import { createSatiUploader, createPinataUploader } from "@cascade-fyi/sati-sdk"; // Hosted uploader (zero config) const result = await builder.register({ payer: signer, uploader: createSatiUploader(), }); // Or with Pinata const result = await builder.register({ payer: signer, uploader: createPinataUploader(process.env.PINATA_JWT!), nonTransferable: true, // soulbound (default) }); console.log(result.mint); // Agent mint address console.log(result.memberNumber); // Registry member number console.log(result.signature); // Transaction signature ``` **Cost:** ~0.003 SOL ### Register with Pre-existing URI ```typescript const result = await builder.registerWithUri({ payer: signer, uri: "ipfs://Qm...", }); ``` ### Update After modifying the builder with fluent setters: ```typescript const updated = await builder.update({ payer: signer, owner: ownerKeypair, // must be the current NFT owner uploader: createSatiUploader(), }); ``` ### Properties | Property | Type | Description | |----------|------|-------------| | `params` | `RegistrationFileParams` | Current registration parameters | | `identity` | `AgentIdentity \| undefined` | On-chain identity (available after register/load) | *** ## Feedback Methods ### giveFeedback Give feedback to an agent. Uses FeedbackPublicV1 schema. Handles SIWS message construction and signing automatically. ```typescript const result = await sati.giveFeedback({ payer: signer, // pays fees + is the reviewer agentMint: address("..."), // agent to review value: 85, // ERC-8004 signed fixed-point value valueDecimals: 0, // decimal places (0-18) tag1: "quality", // first tag dimension tag2: "speed", // second tag dimension (optional) message: "Fast and accurate", endpoint: "https://api.example.com", outcome: Outcome.Positive, // optional (defaults to Neutral) taskRef: taskHashBytes, // optional 32-byte reference }); console.log(result.signature); // transaction signature console.log(result.attestationAddress); // compressed account address ``` **Cost:** ~$0.002 #### GiveFeedbackParams | Field | Type | Required | Description | |-------|------|----------|-------------| | `payer` | `KeyPairSigner` | Yes | Pays fees and is the reviewer | | `agentMint` | `Address` | Yes | Agent mint address | | `value` | `number` | No | ERC-8004 signed fixed-point value | | `valueDecimals` | `number` | No | Decimal places for value (0-18, default 0) | | `tag1` | `string` | No | First tag dimension | | `tag2` | `string` | No | Second tag dimension | | `message` | `string` | No | Human-readable feedback | | `endpoint` | `string` | No | Endpoint being reviewed | | `outcome` | `Outcome` | No | Defaults to Neutral | | `taskRef` | `Uint8Array` | No | 32-byte task reference (random if omitted) | ### prepareFeedback Prepare feedback for browser wallet signing (step 1 of 2). Returns SIWS message bytes for the counterparty to sign externally. ```typescript const prepared = await sati.prepareFeedback({ agentMint: address("..."), counterparty: walletAddress, value: 90, tag1: "quality", }); // Send prepared.messageBytes to the wallet for signing ``` ### submitPreparedFeedback Submit wallet-signed feedback (step 2 of 2). ```typescript const result = await sati.submitPreparedFeedback({ payer: serverSigner, prepared, counterpartySignature: walletSignature, }); ``` ### searchFeedback Search feedback attestations with client-side filtering. ```typescript const feedbacks = await sati.searchFeedback({ agentMint: address("..."), counterparty: address("..."), // filter by reviewer tag1: "quality", minValue: 70, maxValue: 100, includeTxHash: true, }); for (const fb of feedbacks) { console.log(`value=${fb.value} from ${fb.counterparty}`); console.log(`tag1=${fb.tag1}, tag2=${fb.tag2}, Message: ${fb.message}`); } ``` #### FeedbackSearchOptions | Field | Type | Description | |-------|------|-------------| | `agentMint` | `Address` | Filter by agent | | `counterparty` | `Address` | Filter by reviewer | | `tag1` | `string` | Filter by first tag dimension | | `tag2` | `string` | Filter by second tag dimension | | `minValue` | `number` | Minimum value (inclusive) | | `maxValue` | `number` | Maximum value (inclusive) | | `includeTxHash` | `boolean` | Populate `txSignature` per result (extra RPC call each) | #### ParsedFeedback | Field | Type | Description | |-------|------|-------------| | `compressedAddress` | `Address` | Compressed account address | | `agentMint` | `Address` | Agent mint address | | `counterparty` | `Address` | Reviewer address | | `outcome` | `Outcome` | Feedback outcome | | `value` | `number \| undefined` | ERC-8004 signed fixed-point value | | `valueDecimals` | `number \| undefined` | Decimal places (0-18) | | `tag1` | `string \| undefined` | First tag dimension | | `tag2` | `string \| undefined` | Second tag dimension | | `message` | `string \| undefined` | Feedback message | | `endpoint` | `string \| undefined` | Endpoint reviewed | | `createdAt` | `number` | Approximate Unix timestamp | | `txSignature` | `string \| undefined` | Transaction signature (if requested) | ### getReputationSummary Aggregate feedback stats for an agent. ```typescript const summary = await sati.getReputationSummary(agentMint); console.log(`${summary.count} reviews, avg value ${summary.averageValue}`); // Filter by tags const quality = await sati.getReputationSummary(agentMint, "quality"); const qualityLatency = await sati.getReputationSummary(agentMint, "quality", "latency"); ``` Returns `{ count: number; averageValue: number }`. ### revokeFeedback Revoke (close) a feedback attestation. The payer must be the original reviewer. ```typescript const result = await sati.revokeFeedback({ payer: signer, attestationAddress: feedback.compressedAddress, }); ``` *** ## Agent Search Methods ### searchAgents Search registered agents with filters. ```typescript // All agents const all = await sati.searchAgents(); // Filtered const results = await sati.searchAgents({ name: "weather", active: true, endpointTypes: ["MCP"], limit: 25, offset: 50n, includeFeedbackStats: true, }); for (const agent of results) { console.log(`${agent.identity.name} (${agent.identity.mint})`); if (agent.registrationFile) { console.log(` MCP: ${agent.registrationFile.services?.find(e => e.name === "MCP")?.endpoint}`); } if (agent.feedbackStats) { console.log(` Avg value: ${agent.feedbackStats.averageValue} (${agent.feedbackStats.count} reviews)`); } } ``` #### AgentSearchOptions | Field | Type | Description | |-------|------|-------------| | `name` | `string` | Substring match on agent name | | `owner` | `Address` | Filter by owner address | | `active` | `boolean` | Filter by active status | | `endpointTypes` | `string[]` | Filter by endpoint types (e.g., `["MCP", "A2A"]`) | | `limit` | `number` | Max results | | `offset` | `bigint` | Offset for pagination (member number) | | `includeFeedbackStats` | `boolean` | Compute feedback stats per agent (slower) | #### AgentSearchResult | Field | Type | Description | |-------|------|-------------| | `identity` | `AgentIdentity` | On-chain identity (mint, owner, name, uri, memberNumber) | | `registrationFile` | `RegistrationFile \| null` | Fetched metadata (null if fetch failed) | | `feedbackStats` | `ReputationSummary \| undefined` | Only if `includeFeedbackStats` was true | ### searchValidations Query validation attestations for an agent. ```typescript const validations = await sati.searchValidations(agentMint); for (const v of validations) { console.log(`${v.outcome === 2 ? "Positive" : "Negative"} by ${v.counterparty}`); console.log(`At: ${new Date(v.createdAt * 1000).toISOString()}`); } ``` ::: warning Timestamp Precision `createdAt` is approximate - derived from Solana slot numbers at ~400ms/slot. May drift by minutes for recent data. ::: *** ## Agent Registration (Low-Level) For full control over registration without using `SatiAgentBuilder`. ```typescript const result = await sati.registerAgent({ payer, // KeyPairSigner (pays fees + becomes owner) name: "MyAgent", // Max 32 chars uri: "ipfs://Qm...", // Agent metadata JSON owner: ownerAddress, // Optional: mint NFT to a different address additionalMetadata: [ // Optional key-value pairs { key: "version", value: "1.0" }, ], nonTransferable: true, // Default: true (soulbound) }); console.log(result.mint); // Agent's token address (identity) console.log(result.memberNumber); // Registry member number ``` ### IPFS Upload + Registration ```typescript import { createPinataUploader } from "@cascade-fyi/sati-sdk"; const uploader = createPinataUploader(process.env.PINATA_JWT!); const uri = await sati.uploadRegistrationFile( { name: "MyAgent", description: "AI assistant", image: "https://example.com/avatar.png", services: [ { name: "MCP", endpoint: "https://myagent.com/mcp", version: "2025-06-18", mcpTools: ["search"] }, ], supportedTrust: ["reputation"], }, uploader, ); const result = await sati.registerAgent({ payer, name: "MyAgent", uri }); ``` ### Custom Storage Providers ```typescript import type { MetadataUploader } from "@cascade-fyi/sati-sdk"; const arweaveUploader: MetadataUploader = { async upload(data: unknown): Promise { return `ar://${txId}`; }, }; ``` *** ## Querying (Low-Level) ### List Agents ```typescript const agents = await sati.listAllAgents(); for (const agent of agents) { console.log(`Agent ${agent.memberNumber}: ${agent.mint}`); } // By member number const agent = await sati.getAgentByMemberNumber(1n); // By mint const agent = await sati.loadAgent(mintAddress); ``` ### List Feedbacks ```typescript import { loadDeployedConfig } from "@cascade-fyi/sati-sdk"; const config = loadDeployedConfig("mainnet"); const feedbackSchema = config!.schemas.feedback; const result = await sati.listFeedbacks({ sasSchema: feedbackSchema, agentMint, }); for (const fb of result.items) { console.log(`Outcome: ${fb.data.outcome}`); console.log(`Counterparty: ${fb.data.counterparty}`); } // Pagination if (result.cursor) { const nextPage = await sati.listFeedbacks({ sasSchema: feedbackSchema, agentMint, cursor: result.cursor, }); } ``` *** ## Creating Attestations (Low-Level) ### Feedback (Compressed) ```typescript const result = await sati.createFeedback({ payer, sasSchema, taskRef: new Uint8Array(32), agentMint, counterparty: clientAddress, dataHash: requestHash, outcome: Outcome.Positive, contentType: ContentType.JSON, content: new TextEncoder().encode(JSON.stringify({ value: 85, valueDecimals: 0, tag1: "quality" })), agentSignature: { pubkey: agentAddress, signature: agentSig }, counterpartySignature: { pubkey: clientAddress, signature: counterpartySig }, counterpartyMessage: siwsMessageBytes, }); ``` **Cost:** ~$0.002 ### Validation (Compressed) ```typescript const result = await sati.createValidation({ payer, sasSchema: validationSchema, taskRef, agentMint, counterparty: validatorAddress, dataHash: workHash, outcome: Outcome.Positive, contentType: ContentType.JSON, content: new TextEncoder().encode(JSON.stringify({ method: "automated_code_review" })), agentSignature: { pubkey: agentAddress, signature: agentSig }, validatorSignature: { pubkey: validatorAddress, signature: validatorSig }, counterpartyMessage: siwsMessageBytes, }); ``` ### ReputationScoreV3 (Regular SAS) ```typescript import { computeReputationNonce, zeroDataHash, ContentType } from "@cascade-fyi/sati-sdk"; const nonce = computeReputationNonce(providerAddress, agentMint); const result = await sati.createReputationScore({ payer, provider: providerAddress, providerSignature: providerSig, sasSchema, satiCredential, agentMint, taskRef: nonce, dataHash: zeroDataHash(), outcome: Outcome.Positive, contentType: ContentType.JSON, content: createJsonContent({ score: 85, feedbackCount: 127 }), }); ``` ### Update ReputationScoreV3 Closes the existing score and creates a new one in a single call: ```typescript const result = await sati.updateReputationScore({ payer, provider: providerKeypair, sasSchema, satiCredential, agentMint, outcome: Outcome.Positive, contentType: ContentType.JSON, content: createJsonContent({ score: 90, feedbackCount: 150 }), }); ``` *** ## Closing Attestations ### Compressed (Feedback/Validation) ```typescript const result = await sati.closeCompressedAttestation({ payer, counterparty: payer, // must be the original feedback giver sasSchema: feedbackSchema, attestationAddress, // compressed account address (base58) lookupTableAddress, // optional ALT for tx size }); ``` ### Regular (ReputationScoreV3) ```typescript const result = await sati.closeRegularAttestation({ payer, provider: providerKeypair, // KeyPairSigner (must sign the close) sasSchema: reputationSchema, satiCredential, agentMint, attestation: attestationPda, // SAS attestation PDA address }); ``` *** ## Encrypted Content End-to-end encrypted feedback using X25519-XChaCha20-Poly1305. ```typescript import { encryptContent, deriveEncryptionKeypair, ContentType } from "@cascade-fyi/sati-sdk"; // Encrypt for agent const { publicKey: agentEncPubkey } = deriveEncryptionKeypair(agentEd25519Seed); const encrypted = encryptContent(plaintext, agentEncPubkey); // Use in attestation await sati.createFeedback({ // ... contentType: ContentType.Encrypted, content: serializeEncryptedPayload(encrypted), }); // Agent decrypts const { privateKey } = deriveEncryptionKeypair(agentEd25519Seed); const payload = deserializeEncryptedPayload(feedback.content); const decrypted = decryptContent(payload, privateKey); ``` **Size limit:** 439 bytes plaintext (512 - 73 bytes overhead). *** ## Photon Querying Direct compressed account queries via Helius Photon: ```typescript import { createPhotonRpc } from "@cascade-fyi/compression-kit"; import { SATI_PROGRAM_ADDRESS } from "@cascade-fyi/sati-sdk"; const rpc = createPhotonRpc("https://devnet.helius-rpc.com?api-key=YOUR_KEY"); const feedbacks = await rpc.getCompressedAccountsByOwner( SATI_PROGRAM_ADDRESS, { filters: [ { offset: 0, bytes: feedbackSchemaAddress }, // sas_schema at offset 0 { offset: 32, bytes: agentMint }, // agent_mint at offset 32 ], limit: 50, }, ); ``` ### Memcmp Offsets Compressed account data layout: `sas_schema(32) | agent_mint(32) | data_len(4) | data_bytes(...)`. | Field | Offset | Notes | |-------|--------|-------| | `sas_schema` | 0 | Filter by attestation type | | `agent_mint` | 32 | Filter by agent | | `outcome` | 165 | 0=Negative, 1=Neutral, 2=Positive (at data\_start + 97) | *** ## Signature Flow SATI uses a dual-signature model (blind feedback): ```typescript import { computeInteractionHash } from "@cascade-fyi/sati-sdk"; // 1. Agent signs BEFORE knowing outcome const interactionHash = computeInteractionHash(sasSchema, taskRef, dataHash); const agentSig = await signMessage(agentKeypair, interactionHash); // 2. Counterparty signs human-readable SIWS message after task completion // (built automatically by the SDK) ``` *** ## Hash Functions ```typescript import { computeInteractionHash, // Agent blind signature input computeAttestationNonce, // Deterministic compressed account address computeReputationNonce, // One per provider+agent pair computeDataHash, // Hash request + response content computeDataHashFromStrings, // String convenience wrapper zeroDataHash, // Zero hash for CounterpartySigned schemas } from "@cascade-fyi/sati-sdk"; ``` ## Constants ```typescript import { SATI_PROGRAM_ADDRESS, // Program ID (all networks) MAX_CONTENT_SIZE, // 512 bytes MAX_DUAL_SIGNATURE_CONTENT_SIZE, // 70 bytes (DualSignature mode) MAX_COUNTERPARTY_SIGNED_CONTENT_SIZE, // 100 bytes (CounterpartySigned mode) MAX_AGENT_OWNER_SIGNED_CONTENT_SIZE, // 240 bytes (AgentOwnerSigned mode) } from "@cascade-fyi/sati-sdk"; ``` ## Error Handling The SDK throws typed errors for common failure cases: ```typescript import { SatiError, DuplicateAttestationError, AgentNotFoundError } from "@cascade-fyi/sati-sdk"; try { await sati.createFeedback({ ... }); } catch (error) { if (error instanceof DuplicateAttestationError) { console.error("Attestation already exists"); } else if (error instanceof AgentNotFoundError) { console.error(`Agent not found: ${error.agentMint}`); } else if (error instanceof SatiError) { console.error(`SATI error [${error.code}]: ${error.message}`); } } ``` | Error Class | Code | Cause | |-------------|------|-------| | `DuplicateAttestationError` | `DUPLICATE_ATTESTATION` | Same (schema, agent, taskRef, dataHash) submitted twice | | `AgentNotFoundError` | `AGENT_NOT_FOUND` | Agent mint is not a registered SATI agent | | `SchemaNotFoundError` | `SCHEMA_NOT_FOUND` | Schema not registered or not initialized | The program also returns on-chain errors (via Anchor). Common ones: | Program Error | Cause | |---------------|-------| | `InvalidSignatureCount` | Wrong number of sigs for SignatureMode | | `SignatureMismatch` | Sig pubkey doesn't match expected | | `SelfAttestationNotAllowed` | agentMint == counterparty | | `AttestationNotCloseable` | Schema has closeable: false | | `SchemaConfigNotFound` | Schema not registered with SATI | --- --- url: /guides/agent-marketplace.md description: 'Add identity, reputation, and discovery to your agent marketplace' --- # Agent Marketplace This guide walks you through integrating SATI into an agent marketplace. By the end, your platform will register agents with on-chain identity, collect feedback after interactions, and display reputation scores. \[\[toc]] ## What You'll Build * Agents register on-chain when they join your marketplace * After each interaction, feedback is recorded as a compressed attestation * Marketplace pages show reputation scores pulled from on-chain data * Users can search and filter agents by capabilities, feedback stats, and more ## Prerequisites * A server-side Node.js environment (the signer holds SOL and pays for transactions) * A funded Solana wallet (~0.01 SOL per agent registration, ~$0.002 per feedback) * See [Getting Started](/getting-started) for installation ## Initialize the SDK ```typescript import { SatiAgent0 } from "@cascade-fyi/sati-agent0-sdk"; import { createKeyPairSignerFromBytes } from "@solana/kit"; import { base58 } from "@scure/base"; const signer = await createKeyPairSignerFromBytes( base58.decode(process.env.SATI_PRIVATE_KEY!), ); const sdk = new SatiAgent0({ network: "mainnet", signer, }); ``` ## Register Agents When an agent joins your marketplace, register it on-chain: ```typescript const agent = sdk.createAgent( "WeatherBot", "Real-time weather data for any location", "https://yourcdn.com/weatherbot-avatar.png", ); // Configure endpoints and capabilities await agent.setMCP("https://weatherbot.example.com/mcp"); agent.setWallet("AgentWalletAddress123"); agent.setActive(true); agent.setX402Support(true); agent.addSkill("weather-forecast"); agent.addDomain("data-services"); // Register on IPFS and on-chain const handle = await agent.registerIPFS(); console.log(agent.agentId); // solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:MintAddr... ``` ### Update an Existing Agent ```typescript const agent = await sdk.loadAgent(agentId); agent.updateInfo("WeatherBot v2", "Updated weather data with forecasts"); await agent.setMCP("https://weatherbot-v2.example.com/mcp"); const handle = await agent.updateIPFS(); ``` ## Collect Feedback After an interaction completes, record feedback: ```typescript const handle = await sdk.giveFeedback( agentId, 85, // value (0-100) "quality", // tag1 (optional) "speed", // tag2 (optional) "https://api.example.com", // endpoint (optional) { text: "Fast and accurate" }, // feedbackFile (optional) ); const { result: feedback } = await handle.waitMined(); console.log(handle.hash); // transaction signature ``` ### Typed Outcomes For structured workflows (escrow, quality gates), use typed outcomes: ```typescript import { Outcome } from "@cascade-fyi/sati-agent0-sdk"; const handle = await sdk.giveFeedback(agentId, 85, "governance", "defi", undefined, { text: "Proposal executed correctly", outcome: Outcome.Positive, taskRef: proposalHashBytes, // deterministic 32-byte reference }); ``` ## Display Reputation ### Summary Stats ```typescript const summary = await sdk.getReputationSummary(agentId); console.log(`${summary.count} reviews, avg ${summary.averageValue}`); // Filter by tags const qualitySummary = await sdk.getReputationSummary(agentId, "quality"); ``` ### Feedback History ```typescript const feedbacks = await sdk.searchFeedback( { agentId, tags: ["quality"] }, { includeTxHash: true }, ); for (const fb of feedbacks) { console.log(`${fb.value}/100 from ${fb.reviewer}`); } ``` ### Revoke Feedback If a reviewer wants to retract their feedback: ```typescript const [feedback] = await sdk.searchFeedback({ agentId, reviewers: [myAddress], }); const handle = await sdk.revokeFeedback(feedback); ``` ## Search and Discovery ```typescript // By name const results = await sdk.searchAgents({ name: "weather" }); // By capabilities const mcpAgents = await sdk.searchAgents({ hasMCP: true, mcpTools: ["web-search"], }); // With reputation stats (slower - extra RPC calls per agent) const ranked = await sdk.searchAgents( { active: true }, { includeFeedbackStats: true, sort: ["averageValue:desc"] }, ); // Paginated const page = await sdk.searchAgents({}, { limit: 25, offset: 50n }); ``` ## Architecture Tips * **Cache reputation data.** Don't query Solana RPC on every page load. Cache `getReputationSummary` results with a 30-60 second TTL and invalidate after writing new feedback. * **Use read-only mode for frontends.** `new SatiAgent0({ network: "mainnet" })` - no signer needed for search and display. * **Server-side signer for writes.** Keep your signing key on the server. Budget ~0.01 SOL per 5 feedback submissions. * **Agent metadata on IPFS.** `registerIPFS()` works out of the box - no API keys needed. For production, you can pass your own `pinataJwt` for full control over your IPFS pins. Use `registerHTTP()` only if you need mutable metadata at a URL you control. ## Next Steps * **[API Reference: sati-sdk](/reference/sati-sdk)** - full SDK reference with convenience methods * **[API Reference: sati-agent0-sdk](/reference/sati-agent0-sdk)** - agent0-compatible wrapper * **[Query Reputation](/guides/query-reputation)** - read-only integration for displaying scores * **[Browser Wallet Flow](/guides/browser-wallet)** - collect feedback from browser wallets * **[How It Works](/how-it-works)** - understand blind feedback and compression --- --- url: /reference.md description: SDK reference documentation for SATI --- # API Reference SATI provides two SDK packages at different abstraction levels. ## [@cascade-fyi/sati-sdk](/reference/sati-sdk) Full-featured SDK for SATI on Solana. Agent registration, feedback, search, reputation, and low-level attestation access. **Start here** for Solana-native development. ```bash pnpm add @cascade-fyi/sati-sdk ``` ## [@cascade-fyi/sati-agent0-sdk](/reference/sati-agent0-sdk) Thin adapter that wraps sati-sdk with [agent0-sdk](https://www.npmjs.com/package/agent0-sdk) types (AgentId, Feedback, AgentSummary, etc.). Use this if your codebase already depends on agent0-sdk or you need ERC-8004 type compatibility. ```bash pnpm add @cascade-fyi/sati-agent0-sdk ``` --- --- url: /guides/browser-wallet.md description: Collect feedback signed by browser wallets in client-side apps --- # Browser Wallet Flow This guide covers collecting feedback in browser-based apps where the user's wallet (Phantom, Solflare, Backpack) signs the feedback. This is a two-step flow: the frontend prepares the feedback, the user signs it, and a server-side signer submits it on-chain. \[\[toc]] ## Prerequisites * A browser wallet adapter (e.g., `@solana/wallet-adapter`) * A server-side signer with SOL for transaction fees * See [Getting Started](/getting-started) for SDK installation ## How It Works 1. **Frontend** calls `prepareFeedback()` - builds a SIWS message for the user to sign 2. **User's wallet** signs the message (no SOL needed from the user) 3. **Server** calls `submitPreparedFeedback()` with the user's signature - pays for the transaction This separation means your users never need SOL - your server covers transaction costs. ## Frontend: Prepare and Sign ```typescript import { SatiAgent0 } from "@cascade-fyi/sati-agent0-sdk"; // Initialize with TransactionSender (browser wallet adapter) const sdk = new SatiAgent0({ network: "mainnet", transactionSender: walletAdapter, }); // Step 1: Prepare the feedback (builds the SIWS message) const prepared = await sdk.prepareFeedback( agentId, 85, // value (0-100) "quality", // tag1 "speed", // tag2 ); // Step 2: User signs with their wallet const walletSignature = await wallet.signMessage(prepared.messageBytes); // Step 3: Send to your server await fetch("/api/submit-feedback", { method: "POST", body: JSON.stringify({ prepared, signature: Array.from(walletSignature), }), }); ``` ## Server: Submit On-Chain ```typescript import { SatiAgent0 } from "@cascade-fyi/sati-agent0-sdk"; // Server SDK with a KeyPairSigner (pays for transactions) const sdk = new SatiAgent0({ network: "mainnet", signer: serverSigner, }); // Submit the wallet-signed feedback const handle = await sdk.submitPreparedFeedback( prepared, new Uint8Array(signatureFromClient), ); const { result: feedback } = await handle.waitMined(); console.log(handle.hash); // transaction signature ``` ## With Typed Outcomes For workflows that need structured outcomes: ```typescript import { Outcome } from "@cascade-fyi/sati-agent0-sdk"; const prepared = await sdk.prepareFeedback( agentId, 85, "quality", "speed", { endpoint: "https://api.example.com", text: "Excellent response", outcome: Outcome.Positive, taskRef: interactionHashBytes, // deterministic 32-byte reference }, ); ``` ## Error Handling ```typescript import { SatiError, AgentNotFoundError, ReadOnlyError, SignerRequiredError, } from "@cascade-fyi/sati-agent0-sdk"; try { await sdk.submitPreparedFeedback(prepared, walletSignature); } catch (err) { if (err instanceof AgentNotFoundError) { // Agent was deregistered between prepare and submit } else if (err instanceof SignerRequiredError) { // Server SDK missing KeyPairSigner } } ``` ## Next Steps * **[Agent Marketplace](/guides/agent-marketplace)** - full server-side integration * **[API Reference: sati-sdk prepareFeedback](/reference/sati-sdk#preparefeedback)** - sati-sdk method signature * **[API Reference: sati-agent0-sdk prepareFeedback](/reference/sati-agent0-sdk#preparefeedback)** - agent0-compatible wrapper --- --- url: /advanced/benchmarks.md description: CU measurements for all SATI program instructions --- # Compute Unit Benchmarks Measured 2025-12-17 with Solana CLI 3.1.1 (Agave). | Name | CUs | Delta | |------|------|-------| | initialize | 10918 | - new - | | update\_registry\_authority\_transfer | 3516 | - new - | | update\_registry\_authority\_renounce | 3447 | - new - | | register\_agent\_minimal | 58342 | - new - | | register\_agent\_typical\_3\_fields | 82877 | - new - | | register\_agent\_max\_10\_fields | 168097 | - new - | | register\_agent\_soulbound | 79255 | - new - | --- --- url: /erc-8004.md description: How SATI implements the ERC-8004 agent identity standard on Solana --- # ERC-8004 and SATI [ERC-8004](https://eips.ethereum.org/EIPS/eip-8004) is the agent identity standard for discovering, choosing, and interacting with agents across organizational boundaries without pre-existing trust. It defines three registries - Identity, Reputation, and Validation - that give agents a portable, censorship-resistant identity with pluggable trust models. SATI is ERC-8004 on Solana. Full feature parity with the standard, plus Solana-native enhancements that unlock direct on-chain composability. \[\[toc]] ## How SATI Relates to ERC-8004 ERC-8004 is the standard. SATI is an implementation of that standard on Solana, with Solana-native extensions on top. Think of it like this: ERC-8004 defines *what* agent identity infrastructure should look like - registration, reputation, validation, and a shared registration file format. SATI implements all of that using Solana's primitives (Token-2022 for identity, Light Protocol for storage, Photon for indexing) and adds capabilities that the standard leaves to implementers. ### What's the same * **Registration file format** - same JSON schema, same field names, same CAIP-2 identifiers. An ERC-8004 registration file on Base and a SATI registration file on Solana are interchangeable. * **Feedback fields** - `value`, `valueDecimals`, `tag1`, `tag2`, `endpoint` map directly between ERC-8004 and SATI. See [Feedback Field Mapping](#feedback-field-mapping) below. * **Cross-chain identity** - agents can be registered on both chains simultaneously, linked via the `registrations` array in the shared registration file. ### What SATI adds ERC-8004 defines the interfaces and leaves implementation choices to each chain. SATI makes three specific choices that extend the standard: **1. Optional proof of participation (FeedbackV1)** ERC-8004's `giveFeedback()` is an open model - any reviewer can submit feedback about any agent. SATI implements this as FeedbackPublicV1 (the default). On top of that, FeedbackV1 adds an optional dual-signature flow where the agent cryptographically commits to the interaction before knowing the feedback outcome. This makes feedback verifiable by on-chain programs - useful for DeFi composability, escrow, and automated trust decisions. Both models coexist. FeedbackPublicV1 is the ERC-8004 compatible default. FeedbackV1 is available when stronger guarantees are needed. **2. Native indexing via Photon** On EVM, querying "all feedback for agent X" typically requires an external indexer like The Graph - a separate service with its own trust assumptions and operational cost. On Solana, Photon RPC indexes compressed accounts natively. Every RPC provider that supports Light Protocol serves this data as a standard RPC call, with no additional infrastructure to deploy or maintain. **3. Cost-predictable compressed storage** Every feedback interaction is stored individually on-chain at ~$0.002 via ZK Compression - not just aggregated summaries. This cost is stable regardless of network conditions. On EVM L2s, per-transaction costs are comparable at current gas prices but vary with network congestion. ### Can agents exist on both? Yes. An agent registered through SATI on Solana and through ERC-8004 on Base can share the same registration file, the same services, and the same identity. The `registrations` array in the registration file links identities across chains using CAIP-2 identifiers. See [Cross-Chain Agent Identity](#cross-chain-agent-identity) below. ## What's Compatible SATI implements every component of the ERC-8004 specification: ### Identity | ERC-8004 | SATI | Notes | |----------|------|-------| | ERC-721 NFT registration | Token-2022 NFT | Visible in Phantom, Solflare, Backpack | | Auto-incrementing `agentId` | `TokenGroupMember.member_number` | Same sequential numbering | | `tokenURI` / registration file | `TokenMetadata.uri` | Same JSON format (see below) | | `transfer()` | Native Token-2022 transfer | Standard SPL token operation | | `setApprovalForAll()` | Token delegate | Solana's native delegation | | On-chain metadata (`getMetadata`/`setMetadata`) | `TokenMetadata.additionalMetadata` | Key-value pairs on-chain | ### Reputation | ERC-8004 | SATI | Notes | |----------|------|-------| | `giveFeedback()` | FeedbackPublicV1 schema | Open model - any reviewer can submit | | `revokeFeedback()` | `close_compressed_attestation()` | Removes attestation | | `appendResponse()` | FeedbackResponse schema | Agent or third-party responses | | `getSummary()` | Photon RPC queries | Native indexing, no subgraph needed | | `readFeedback()` | Fetch compressed attestation | Via Photon RPC | ### Validation | ERC-8004 | SATI | Notes | |----------|------|-------| | `validationRequest()` | ValidationV1 schema | Third-party quality checks | | `validationResponse()` | ValidationV1 schema with response | Validator attestations | ### Registration File SATI's registration file follows the ERC-8004 format. The same JSON structure works across both ecosystems: ```json { "type": "https://eips.ethereum.org/EIPS/eip-8004#registration-v1", "name": "MyAgent", "description": "An AI assistant for code review", "image": "https://example.com/avatar.png", "services": [ { "name": "MCP", "endpoint": "https://mcp.example.com", "version": "2025-06-18" }, { "name": "A2A", "endpoint": "https://a2a.example.com/.well-known/agent-card.json", "version": "0.3.0" } ], "registrations": [ { "agentId": 42, "agentRegistry": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:satiRkxEiwZ51cv8PRu8UMzuaqeaNU9jABo6oAFMsLe" } ], "supportedTrust": ["reputation"], "active": true, "x402Support": true } ``` The `registrations` array uses [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md) chain identifiers. ERC-8004 agents on EVM use `eip155:{chainId}:{registryAddress}`. SATI agents use `solana:{chainRef}:{programId}`. Both formats are recognized by any CAIP-2 aware consumer, which means agents are discoverable across chains without custom handling. ### Cross-Chain Agent Identity An agent can be registered on multiple chains simultaneously. The `registrations` array in the registration file links all of an agent's identities: ```json { "registrations": [ { "agentId": 42, "agentRegistry": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:satiRkxEiwZ51cv8PRu8UMzuaqeaNU9jABo6oAFMsLe" }, { "agentId": 108, "agentRegistry": "eip155:8453:0x8004a6090Cd10A7288092483047B097295Fb8847" } ] } ``` This means reputation isn't locked to one chain. An agent registered through SATI on Solana and through ERC-8004 on Base shares the same registration file, the same services, and the same identity - discoverable from either ecosystem. ## What Solana Enables on Top The ERC-8004 compatible flow (FeedbackPublicV1) is the default. Beyond that, Solana's architecture enables capabilities that extend the standard: ### Blind Feedback (FeedbackV1) FeedbackV1 adds **dual-signature proof of participation** - the agent cryptographically commits to the interaction before knowing the reviewer's feedback, and the reviewer needs the agent's signature to submit feedback. Both parties must sign the same task reference. This makes feedback **directly composable by on-chain programs**. Because the Solana runtime can verify that both parties participated, smart contracts can use FeedbackV1 attestations as trust inputs without relying on off-chain aggregation: * **Reputation-based lending** - a DeFi protocol reads an agent's verified interaction history to determine creditworthiness * **Escrow resolution** - a smart contract releases funds based on cryptographically verified delivery * **Automated routing** - an on-chain program checks proof of participation before forwarding requests ERC-8004's open feedback model is designed for off-chain aggregation and scoring - the spec explicitly expects ecosystem players to build reputation systems on top of the public data. FeedbackV1 is a complementary approach for use cases where trust decisions need to happen on-chain, within the same transaction. ### ReputationScore ReputationScore provides a standardized on-chain mechanism for **reputation providers** to publish scoring algorithms directly on Solana. Any entity can compute scores from the public feedback data (both FeedbackPublicV1 and FeedbackV1) and publish them as on-chain attestations. This means: * Multiple competing reputation providers can serve the same agent * Scores are readable by any Solana program (CPI composability) * Applications choose which providers they trust, same as ERC-8004's model of filtering by `clientAddress` ### ZK Compression Every feedback interaction is stored individually on-chain at ~$0.002 via [Light Protocol](https://www.lightprotocol.com/) ZK Compression - not just aggregated summaries. This complete history is indexed by Photon RPC (standard Solana infrastructure from Helius and Triton), queryable with the same RPC call used for any other Solana data. No subgraph deployment, no external indexer, no additional infrastructure. ## Feedback Field Mapping SATI uses the same field names and semantics as ERC-8004 for feedback content: | ERC-8004 Field | SATI Field | Notes | |----------------|------------|-------| | `value` (int128) | `value` in feedback content JSON | Same signed fixed-point semantics | | `valueDecimals` (uint8) | `valueDecimals` in feedback content JSON | Same 0-18 decimal precision | | `tag1` | `tag1` | Same purpose - feedback dimension | | `tag2` | `tag2` | Same purpose - sub-dimension | | `endpoint` | `endpoint` in feedback content JSON | Endpoint being reviewed | | `feedbackURI` | Attestation content (on-chain via compression) | SATI stores content on-chain instead of referencing off-chain URIs | | `feedbackHash` | `dataHash` (on-chain) | Integrity verification | | `clientAddress` | `counterparty` | The reviewer's address | ## Next Steps * **[Getting Started](/getting-started)** - register an agent and give feedback in 5 minutes * **[How It Works](/how-it-works)** - architecture deep dive into feedback schemas, compression, and indexing * **[Specification](/specification)** - byte-level protocol details and security model --- --- url: /getting-started.md description: Go from zero to a registered agent with feedback in 5 minutes --- # Getting Started SATI is the [ERC-8004](https://eips.ethereum.org/EIPS/eip-8004) agent identity standard on Solana. This guide walks you through installing the SDK, registering an agent on devnet, giving feedback, and querying reputation. \[\[toc]] ## Prerequisites Before you begin, ensure you have: * **Node.js 18+** and a package manager (pnpm recommended) * **A Solana wallet with devnet SOL** - you'll need ~0.01 SOL for transactions ::: tip Getting Devnet SOL Generate a keypair and airdrop SOL using `@solana/kit`: ```typescript import { generateKeyPairSigner, createSolanaRpc, address } from "@solana/kit"; const signer = await generateKeyPairSigner(); const rpc = createSolanaRpc("https://api.devnet.solana.com"); await rpc.requestAirdrop(signer.address, 1_000_000_000n).send(); // 1 SOL ``` Or use the [Solana Faucet](https://faucet.solana.com) with your wallet address. ::: ## Install ::: code-group ```bash [pnpm] pnpm add @cascade-fyi/sati-sdk ``` ```bash [npm] npm install @cascade-fyi/sati-sdk ``` ::: **Peer dependencies:** ```bash pnpm add @solana/kit @solana-program/token-2022 ``` ## Which SDK? | Package | Use When | |---------|----------| | **[@cascade-fyi/sati-sdk](https://www.npmjs.com/package/@cascade-fyi/sati-sdk)** | Solana-native development - agent registration, feedback, search, reputation, and low-level access. **Start here.** | | **[@cascade-fyi/sati-agent0-sdk](https://www.npmjs.com/package/@cascade-fyi/sati-agent0-sdk)** | Your codebase already uses [agent0-sdk](https://www.npmjs.com/package/agent0-sdk) types (`AgentId`, `Feedback`, `AgentSummary`). Thin wrapper over sati-sdk. | ## Quick Start The full loop: initialize, register an agent, give feedback, query reputation. ::: code-group ```typescript [sati-sdk] import { Sati, createSatiUploader } from "@cascade-fyi/sati-sdk"; import { generateKeyPairSigner, createSolanaRpc } from "@solana/kit"; // 1. Create a funded devnet wallet const signer = await generateKeyPairSigner(); const rpc = createSolanaRpc("https://api.devnet.solana.com"); await rpc.requestAirdrop(signer.address, 1_000_000_000n).send(); // 1 SOL // 2. Initialize the client const sati = new Sati({ network: "devnet" }); // 3. Register an agent (metadata uploaded to IPFS automatically) const builder = sati .createAgentBuilder("MyAgent", "An AI trading assistant", "https://example.com/avatar.png") .setMCP("https://mcp.example.com", "2025-06-18") .setActive(true); const reg = await builder.register({ payer: signer, uploader: createSatiUploader(), // hosted IPFS - no API keys needed }); console.log(reg.mint); // agent mint address console.log(reg.signature); // transaction signature // 4. Give feedback (ERC-8004 value/tag fields) const fb = await sati.giveFeedback({ payer: signer, agentMint: reg.mint, value: 85, tag1: "quality", tag2: "speed", }); // 5. Query reputation const summary = await sati.getReputationSummary(reg.mint); console.log(`${summary.count} reviews, avg ${summary.averageValue}`); ``` ```typescript [sati-agent0-sdk] import { SatiAgent0 } from "@cascade-fyi/sati-agent0-sdk"; import { generateKeyPairSigner, createSolanaRpc } from "@solana/kit"; // 1. Create a funded devnet wallet const signer = await generateKeyPairSigner(); const rpc = createSolanaRpc("https://api.devnet.solana.com"); await rpc.requestAirdrop(signer.address, 1_000_000_000n).send(); // 1 SOL // 2. Initialize the SDK const sdk = new SatiAgent0({ network: "devnet", signer, }); // 3. Register an agent (metadata is uploaded to IPFS automatically) const agent = sdk.createAgent("MyAgent", "An AI trading assistant"); agent.setActive(true); const regHandle = await agent.registerIPFS(); console.log(agent.agentId); // solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1:MintAddr... console.log(regHandle.hash); // transaction signature // 4. Give feedback (ERC-8004 value/tag fields) const fbHandle = await sdk.giveFeedback(agent.agentId!, 85, "quality", "speed"); const { result: feedback } = await fbHandle.waitMined(); // 5. Query reputation const summary = await sdk.getReputationSummary(agent.agentId!); console.log(`${summary.count} reviews, avg ${summary.averageValue}`); ``` ::: ::: tip Bring Your Own IPFS `createSatiUploader()` uses a hosted service - no API keys needed. To use your own [Pinata](https://pinata.cloud) account instead: ```typescript import { createPinataUploader } from "@cascade-fyi/sati-sdk"; const uploader = createPinataUploader(process.env.PINATA_JWT!); ``` ::: ## SDK Modes ```typescript import { Sati } from "@cascade-fyi/sati-sdk"; // Read-only - search agents, read feedback (no signer needed) const sati = new Sati({ network: "devnet" }); // With convenience writes - pass a payer to each write method const result = await sati.giveFeedback({ payer: signer, agentMint, value: 85 }); ``` ## Next Steps * **[How It Works](/how-it-works)** - understand blind feedback, compression, and the architecture * **[Agent Marketplace Guide](/guides/agent-marketplace)** - add reputation to your platform * **[Register an MCP Agent](/guides/mcp-agent)** - register your MCP server on-chain * **[Query Reputation](/guides/query-reputation)** - read-only integration for displaying agent scores * **[API Reference](/reference/)** - full SDK documentation --- --- url: /how-it-works.md description: The architecture behind on-chain agent identity and verifiable reputation --- # How It Works SATI implements the [ERC-8004](https://eips.ethereum.org/EIPS/eip-8004) agent identity standard on Solana with five components: agent identity, blind feedback, compressed storage, native indexing, and delegation. This page explains what each does and why it's built this way. \[\[toc]] ## Agent Identity Every SATI agent is a **Token-2022 NFT** on Solana. This means: * **Wallet visibility** - agents show up in Phantom, Solflare, Backpack without custom explorers * **Rich metadata** - name, description, image, MCP/A2A endpoints, skills, and trust models stored on-chain via metadata extensions * **Enumeration** - agents belong to a TokenGroup, giving each a unique member number for pagination and discovery * **Transferable or soulbound** - agents can be locked to one owner (`nonTransferable: true`) or made transferable Agent IDs use the [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md) format for cross-chain compatibility: ``` solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:MintAddress123 └── chain reference ──────────────────┘ └─ agent mint ─┘ ``` ## Feedback Schemas SATI supports two feedback models. **FeedbackPublicV1** is the ERC-8004 compatible open model - any reviewer can submit feedback about any agent, same as the standard. This is the default for most integrations. **FeedbackV1** adds proof of participation on top - a Solana-native enhancement that makes feedback directly composable by on-chain programs (DeFi lending, escrow resolution, smart contract trust checks). It uses a **dual-signature model**: ### The Problem FeedbackV1 Solves In traditional review systems, the service provider decides which reviews get published. An agent could serve 100 requests, collect feedback on all of them, and only publish the 90 positive ones. SATI's FeedbackV1 prevents this with dual signatures: ### The Flow 1. **Agent commits blind.** When the agent responds to a request, it signs a hash of the interaction data - `keccak256(schema, taskRef, dataHash)`. At this point, the agent has no idea what feedback the reviewer will give. It has cryptographically committed to the interaction. 2. **Reviewer submits feedback after the fact.** The reviewer sees the agent's work, decides on a value and outcome, and signs a human-readable [Sign In With Solana](https://github.com/phantom/sign-in-with-solana) (SIWS) message confirming the feedback. 3. **Both signatures go on-chain.** The attestation includes both signatures, proving the agent participated and the reviewer scored it. Neither party can back out. ### Why This Matters * **Agents can't cherry-pick.** They signed before knowing the outcome. They can refuse to participate entirely, but they can't selectively publish only positive reviews. * **Reviewers can't fake interactions.** They need the agent's blind signature, which only exists if the agent actually served the request. * **It's cryptographically enforced.** Not a moderation policy or terms of service - mathematical proof that both parties participated. ::: info What if an agent refuses to sign? Enforcement is handled at the application layer - facilitators, clients, and marketplaces can require proof of participation before routing traffic to an agent. The protocol provides the mechanism; your application decides the policy. ::: ## ZK Compression Storing every feedback interaction as a regular Solana account would cost ~$0.40 each. At that price, only aggregated scores are economically viable - you lose the individual interaction history. SATI uses [Light Protocol](https://www.lightprotocol.com/) ZK Compression to store attestations as **compressed accounts** at ~$0.002 each. This is cheap enough to store every single interaction, not just summaries. ### What Gets Compressed | Data | Storage | Why | |------|---------|-----| | Feedback attestations | Compressed (Light Protocol) | High volume, low cost per write | | Validation attestations | Compressed (Light Protocol) | Same economics as feedback | | Reputation scores | Regular SAS accounts | Low volume, needs on-chain queryability | | Delegation grants | Regular SAS accounts | Needs on-chain verification by programs | ### How Querying Works Compressed accounts are indexed by **Photon RPC** (provided by Helius and Triton). The SDK ships with a hosted Photon proxy so you don't need to configure a specific RPC provider - it works out of the box. No custom indexer, no database, no Graph deployment. ```typescript // Zero config - the SDK handles Photon routing automatically const feedbacks = await sdk.searchFeedback({ agentId }); const summary = await sdk.getReputationSummary(agentId); const agents = await sdk.searchAgents({ hasMCP: true }); ``` For production workloads, you can point to your own Photon-compatible RPC (Helius, Triton) via `photonRpcUrl` in the SDK config. ## Schemas SATI uses a **universal base layout** for all attestations - 131 bytes that every attestation type shares: * Layout version, task reference, agent mint, counterparty, outcome (Positive/Neutral/Negative) * Data hash, content type, and up to 512 bytes of content New attestation types (schemas) can be registered without upgrading the program. The base layout is validated on-chain; schema-specific interpretation happens in the SDK and indexers. Current schemas: | Schema | Signature Mode | Use | |--------|---------------|-----| | FeedbackV1 | DualSignature (agent + reviewer) | Blind feedback with proof of participation | | FeedbackPublicV1 | CounterpartySigned (reviewer only) | Open feedback (ERC-8004 compatible) - any reviewer can submit | | ValidationV1 | DualSignature (agent + validator) | Third-party quality attestations | | ReputationScoreV3 | SingleSigner (provider) | Aggregated scores from reputation oracles | | DelegateV1 | CounterpartySigned (delegator) | Hot/cold wallet authorization | ## Delegation Agents need operational security. The identity key (which owns the NFT) should stay in cold storage, while a hot wallet handles day-to-day signing. SATI's delegation system lets an agent owner authorize a hot wallet to sign on behalf of the agent, with optional expiry. Delegations are stored as regular SAS attestations so they can be verified on-chain by other programs. ::: warning Coming Soon The delegation schema is supported at the program level, but high-level SDK methods (`createDelegation`, `revokeDelegation`) are not yet available. To use delegation today, build the raw instructions using the low-level generated Codama client from `@cascade-fyi/sati-sdk`. ::: ## Why Solana? SATI is built on Solana for specific technical reasons: * **Token-2022 metadata extensions** - agent identity stored natively, visible in all wallets without custom explorers * **Light Protocol ZK Compression** - sub-cent attestation storage with state proof verification * **Photon RPC** - compressed account indexing provided by existing infrastructure (Helius, Triton), no custom indexer * **CPI architecture** - atomic multi-program operations in a single transaction (register agent + create attestation + update score) ## Next Steps * **[Getting Started](/getting-started)** - install and build something in 5 minutes * **[Agent Marketplace Guide](/guides/agent-marketplace)** - full integration walkthrough * **[Specification](/specification)** - byte-level protocol details, security model, data layouts --- --- url: /guides/query-reputation.md description: Read-only integration for displaying agent scores and feedback --- # Query Reputation This guide covers read-only SATI integration - querying agent reputation, searching feedback, and displaying scores. No signer or SOL required. \[\[toc]] ## Prerequisites * See [Getting Started](/getting-started) for SDK installation * No wallet or SOL needed - read operations are free ## Initialize (Read-Only) ::: code-group ```typescript [sati-sdk] import { Sati } from "@cascade-fyi/sati-sdk"; const sati = new Sati({ network: "mainnet" }); ``` ```typescript [sati-agent0-sdk] import { SatiAgent0 } from "@cascade-fyi/sati-agent0-sdk"; const sdk = new SatiAgent0({ network: "mainnet" }); ``` ::: That's it. No signer, no keys. All read operations work immediately. ## Reputation Summary Get the aggregate value for an agent: ```typescript const summary = await sdk.getReputationSummary(agentId); console.log(`${summary.count} reviews, average ${summary.averageValue}/100`); ``` Filter by tags to get category-specific values: ```typescript const quality = await sdk.getReputationSummary(agentId, "quality"); const speed = await sdk.getReputationSummary(agentId, "speed"); console.log(`Quality: ${quality.averageValue}/100 (${quality.count} reviews)`); console.log(`Speed: ${speed.averageValue}/100 (${speed.count} reviews)`); ``` ## Search Feedback Retrieve individual feedback entries: ```typescript const feedbacks = await sdk.searchFeedback({ agentId }); for (const fb of feedbacks) { console.log(`${fb.value}/100 from ${fb.reviewer}`); } ``` ### Filter by Reviewer ```typescript const myFeedback = await sdk.searchFeedback({ agentId, reviewers: ["ReviewerWalletAddress"], }); ``` ### Filter by Tags ```typescript const qualityFeedback = await sdk.searchFeedback({ agentId, tags: ["quality"], }); ``` ### Include Transaction Hashes ```typescript const feedbacks = await sdk.searchFeedback( { agentId }, { includeTxHash: true }, ); for (const fb of feedbacks) { console.log(`tx: ${fb.txHash}`); // Solana transaction signature } ``` ## Search Agents Find agents by capabilities, endpoints, or reputation: ```typescript // By name const results = await sdk.searchAgents({ name: "weather" }); // By endpoint type const mcpAgents = await sdk.searchAgents({ hasMCP: true }); const a2aAgents = await sdk.searchAgents({ hasA2A: true }); // By specific tools const codeReviewers = await sdk.searchAgents({ mcpTools: ["review-code"], active: true, }); // With reputation stats (slower - extra RPC calls per agent) const ranked = await sdk.searchAgents( { active: true }, { includeFeedbackStats: true, sort: ["averageValue:desc"] }, ); ``` ### Pagination ```typescript const page1 = await sdk.searchAgents({}, { limit: 25 }); const page2 = await sdk.searchAgents({}, { limit: 25, offset: 25n }); ``` ## Load Agent Details ```typescript const agent = await sdk.loadAgent(agentId); console.log(agent.name); console.log(agent.description); console.log(agent.mcpEndpoint); console.log(agent.mcpTools); // auto-fetched tool names console.log(agent.a2aEndpoint); console.log(agent.a2aSkills); // auto-fetched skill names console.log(agent.oasfSkills); // OASF taxonomy slugs console.log(agent.oasfDomains); console.log(agent.walletAddress); ``` ## Validation Attestations Query third-party validation results: ```typescript const validations = await sdk.searchValidations(agentId); for (const v of validations) { console.log(`${v.outcome === 2 ? "Positive" : "Negative"} by ${v.counterparty}`); console.log(`At: ${new Date(v.createdAt * 1000).toISOString()}`); } ``` ::: warning Timestamp Precision `createdAt` is approximate - derived from Solana slot numbers at ~400ms/slot. May drift by minutes for recent data. For exact timestamps, use `sdk.getCreationSignature(compressedAddress)` and then Solana's `getBlockTime()`. ::: ## Caching Recommendations For production apps displaying reputation data: * **Cache `getReputationSummary`** with a 30-60 second TTL * **Cache `searchAgents` results** - agent metadata changes infrequently * **Invalidate after writes** - if your app also submits feedback, clear the cache after `giveFeedback` * **Use server-side caching** for marketplace pages - don't hit RPC on every page load ## Next Steps * **[Agent Marketplace](/guides/agent-marketplace)** - full integration with write operations * **[API Reference: sati-sdk](/reference/sati-sdk)** - full SDK reference with convenience methods * **[API Reference: sati-agent0-sdk](/reference/sati-agent0-sdk)** - agent0-compatible wrapper * **[How It Works](/how-it-works)** - understand what the data represents --- --- url: /guides/mcp-agent.md description: Register your MCP server on-chain with discoverable tools and metadata --- # Register an MCP Agent This guide walks you through registering an MCP (Model Context Protocol) server as a SATI agent. Your MCP server gets an on-chain identity, its tools are auto-discovered and indexed, and other agents and platforms can find it by searching for specific capabilities. \[\[toc]] ## Prerequisites * A running MCP server with a public URL * See [Getting Started](/getting-started) for SDK installation and wallet setup ## Register Your MCP Server ```typescript import { SatiAgent0 } from "@cascade-fyi/sati-agent0-sdk"; const sdk = new SatiAgent0({ network: "devnet", signer, }); const agent = sdk.createAgent( "My MCP Server", "Code review and analysis tools", "https://example.com/avatar.png", ); // Set the MCP endpoint - tools, prompts, and resources are auto-fetched await agent.setMCP("https://mcp.example.com"); // Add OASF taxonomy for structured discovery agent.addSkill("code-review"); agent.addDomain("developer-tools"); // Mark as active and register agent.setActive(true); const handle = await agent.registerIPFS(); console.log(agent.agentId); // solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1:MintAddr... console.log(agent.mcpTools); // ["review-code", "analyze-dependencies", ...] console.log(agent.mcpPrompts); // ["code-review-prompt", ...] console.log(agent.mcpResources); // ["project-context", ...] ``` ::: tip Auto-Fetch When you call `agent.setMCP(url)`, the SDK connects to your MCP server and fetches its tool, prompt, and resource listings. These are stored in the agent metadata so other agents and platforms can discover your server's capabilities without connecting to it. ::: ## Add A2A Alongside MCP If your agent also supports [A2A (Agent-to-Agent)](https://google.github.io/A2A/) protocol: ```typescript await agent.setMCP("https://mcp.example.com"); await agent.setA2A("https://a2a.example.com/.well-known/agent.json"); // Both endpoints' capabilities are auto-fetched console.log(agent.mcpTools); // MCP tools console.log(agent.a2aSkills); // A2A skills ``` ## Configure Trust and Payment ```typescript // Declare which trust models your agent supports agent.setTrust( true, // reputation - participates in SATI feedback false, // cryptoEconomic - staking/slashing (future) false, // teeAttestation - TEE verification (future) ); // Set a wallet for receiving payments agent.setWallet("YourWalletAddress123"); // Declare x402 support agent.setX402Support(true); ``` ## Update After Deployment Changes When your MCP server adds new tools or changes its endpoint: ```typescript const agent = await sdk.loadAgent(agentId); // Update the endpoint - tools are re-fetched automatically await agent.setMCP("https://mcp-v2.example.com"); // Re-upload metadata to IPFS const handle = await agent.updateIPFS(); ``` ## Make Your Agent Discoverable Other developers can find your agent by searching: ```typescript // Someone searching for code review tools const agents = await sdk.searchAgents({ hasMCP: true, mcpTools: ["review-code"], active: true, }); ``` To maximize discoverability: * **Set a clear name and description** - these are used in search * **Add OASF skills and domains** - structured taxonomy for filtering * **Keep your endpoint live** - consumers may verify the endpoint is reachable * **Enable feedback** - agents with positive track records rank higher in sorted searches ## Next Steps * **[Agent Marketplace](/guides/agent-marketplace)** - the full agent lifecycle with feedback * **[Query Reputation](/guides/query-reputation)** - check your agent's reputation programmatically * **[API Reference: sati-sdk](/reference/sati-sdk)** - full SDK reference including SatiAgentBuilder * **[API Reference: SatiAgent](/reference/sati-agent0-sdk#satiagent)** - agent0-compatible agent class --- --- url: /index.md description: >- Solana Agent Trust Infrastructure - on-chain identity and reputation for AI agents --- # SATI: Solana Agent Trust Infrastructure On-chain identity and reputation for AI agents. ## The Problem Thousands of agent endpoints, all anonymous. No identity, no track record, no way to verify delivery before paying. Agents try multiple endpoints before finding one that works. There's no trust layer. A database of reviews doesn't fix this - the service provider controls the database. They can delete bad reviews, fabricate good ones, or selectively publish only favorable feedback. ## What It Does SATI implements [ERC-8004](https://eips.ethereum.org/EIPS/eip-8004) on Solana - the agent identity standard. Agents register with on-chain metadata (Token-2022 NFTs), accumulate feedback from interactions, and build a verifiable track record. [Learn more about ERC-8004 compatibility](/erc-8004). Feedback lives on-chain via ZK Compression (~$0.002 per attestation). Every marketplace, platform, or app can query the same data. The agent doesn't rebuild reputation from scratch on each platform - the track record is already there. ## Who Is SATI For? * **Agent marketplace builders** - add identity, reputation, and discovery to your platform with one SDK integration * **x402 sellers** - link feedback directly to payment transactions, so your track record proves you delivered * **Platform operators** - register all your platform's agents with a single integration, give them portable reputation * **Reputation providers** - publish competing scoring algorithms on top of the same on-chain data ## Ready to Build? Start with the [Getting Started](/getting-started) guide to go from zero to working in 5 minutes, or read [How It Works](/how-it-works) to understand the architecture first. ### Costs | Operation | Cost | |-----------|------| | Agent registration | ~0.003 SOL | | Feedback (single) | ~$0.002 | | Feedback (batched 5/tx) | ~$0.001 | | Validation | ~$0.002 | ### Deployed Addresses | Network | Program ID | |---------|------------| | Mainnet | `satiRkxEiwZ51cv8PRu8UMzuaqeaNU9jABo6oAFMsLe` | | Devnet | `satiRkxEiwZ51cv8PRu8UMzuaqeaNU9jABo6oAFMsLe` | --- --- url: /specification.md description: SATI v1.0 Technical Specification --- # SATI Specification v1.0 ## Solana Agent Trust Infrastructure **Version**: 1.0 | **License**: Apache 2.0 *** ## Abstract SATI is open trust infrastructure for AI agents on Solana solving the economics of on-chain feedback: * **Proof of participation** — Agent signs with response (blind to outcome); cannot selectively participate in only positive reviews * **x402 native** — Canonical feedback extension; payment tx becomes task reference (CAIP-220) * **Cost-efficient storage** — ZK Compression via Light Protocol with native Photon indexing * **Schema agnostic** — Program verifies signatures on 131-byte universal base layout; new schemas without upgrades * **No reputation monopoly** — Multiple providers compete with different scoring algorithms * **Hot/cold wallet separation** — Delegates can sign attestations without full ownership permissions * **On-chain agent enumeration** — AgentIndex PDAs enable listing all agents without external indexing **Built on**: Token-2022 (identity), SAS (schemas), Light Protocol (storage), Photon (indexing), x402 (payments) *** ## Core Concepts ### Blind Feedback Model The breakthrough enabling free on-chain feedback. Agent and counterparty sign different data at different times: | Party | Signs | When | Proves | |-------|-------|------|--------| | Agent | `interaction_hash = keccak256(schema \|\| task_ref \|\| data_hash)` | With response | "I served this task" | | Counterparty | Human-readable SIWS message (see Off-Chain Signing) | After service | "I gave this feedback" | **Flow**: Client pays (x402) → Agent responds + signs (blind) → Client signs feedback → Agent/facilitator submits **Key insight**: Agent signs BEFORE knowing feedback sentiment — cannot selectively participate. **Enforcement note**: The protocol does not enforce that agents sign with every response. Enforcement can be handled at the application layer: * **Facilitators** can require agent signatures before settling payments * **Clients** can refuse to pay agents that don't participate in reputation * **Marketplaces** can filter for agents with reputation participation This is not a protocol concern — SATI provides the infrastructure, enforcement is delegated to ecosystem participants. ### Incentive Alignment | Feedback | Who Pays | Why | |----------|----------|-----| | Positive | Agent | Benefits from reputation boost | | Negative | Client | Motivated to warn others | | None | No one | Client chose not to sign | **Participation is opt-in**: No agent signature = not participating = no verified feedback possible. ### x402 Integration SATI is the canonical feedback extension for x402. Payment tx hash becomes `task_ref` (CAIP-220 format). **Facilitators** are natural feedback managers: already in payment flow, trusted by both parties, can batch submissions. ### Costs | Operation | Cost | Notes | |-----------|------|-------| | Agent registration | ~0.003 SOL | Mint + metadata + group + AgentIndex | | Feedback (single) | ~$0.002 | ~0.00002 SOL via Light Protocol | | Feedback (batched 5/tx) | ~$0.001 | Amortized proof cost | | Validation | ~$0.002 | Same as feedback | | ReputationScoreV3 | ~0.002 SOL | Regular SAS attestation | | Delegation grant | ~0.002 SOL | Regular SAS attestation (reclaimable) | | Delegation revoke | ~0.000005 SOL | Tx fee only; ~0.002 SOL rent returned | | Photon indexing | Free | Native indexing for compressed attestations | *** ## Architecture ``` ┌───────────────────────────┐ │ Token-2022 │ │ • Identity storage │ │ • TokenMetadata │ │ • TokenGroup │ └───────────────────────────┘ ▲ │ (CPI: mint NFT) ┌─────────────────────────────────────────────────────────────────────┐ │ SATI Program │ │ (satiRkxEiwZ51cv8PRu8UMzuaqeaNU9jABo6oAFMsLe) │ ├─────────────────────────────────────────────────────────────────────┤ │ Registry: │ │ initialize() → Create registry + TokenGroup │ │ register_agent() → Token-2022 NFT + group + index │ │ update_registry_authority() → Transfer/renounce control │ │ Attestation: │ │ register_schema_config() → Register schema + auth + storage│ │ create_compressed_attestation() → Verify sigs → Light Protocol │ │ create_regular_attestation() → Verify sigs → SAS storage │ │ close_compressed_attestation() → Close compressed attestation │ │ close_regular_attestation() → Close regular attestation │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ (CPI: compressed) │ (CPI: regular) ▼ ▼ ┌───────────────────────────┐ ┌────────────────────────────────┐ │ Light Protocol │ │ Solana Attestation Service │ │ (Compressed Storage) │ │ (Regular Storage) │ ├───────────────────────────┤ ├────────────────────────────────┤ │ • Feedback attestations │ │ • ReputationScore attestations │ │ • Validation attestations │ │ • Delegation attestations │ │ • ~$0.002 per attestation │ │ • ~$0.40 per attestation │ │ • Photon indexing (free) │ │ • On-chain queryable (RPC) │ └───────────────────────────┘ └────────────────────────────────┘ ``` | Component | Responsibility | |-----------|----------------| | **SATI Program** | Agent registration, signature verification, storage routing, delegation | | **Token-2022** | Identity storage, metadata, transfers | | **SAS** | Schema definitions + regular attestation storage | | **Light Protocol** | Compressed attestation storage | | **Photon** | Free indexing for compressed accounts | *** ## SATI Program **Program ID**: `satiRkxEiwZ51cv8PRu8UMzuaqeaNU9jABo6oAFMsLe` ### Registry #### RegistryConfig (PDA: `["registry"]`) | Field | Type | Description | |-------|------|-------------| | `group_mint` | Pubkey | SATI TokenGroup mint | | `authority` | Pubkey | Registry authority (default = immutable) | | `total_agents` | u64 | Agent counter | | `bump` | u8 | PDA bump | #### AgentIndex (PDA: `["agent_index", member_number]`) | Field | Type | Description | |-------|------|-------------| | `mint` | Pubkey | Agent mint address | | `bump` | u8 | PDA bump | **Size**: 8 (discriminator) + 32 (mint) + 1 (bump) = 41 bytes (~0.0003 SOL) > **Why AgentIndex?** Token-2022 TokenGroupMember stores `member_number` but provides no query mechanism. `getProgramAccounts` with memcmp fails because extension offsets vary by extension order. AgentIndex provides a forward index: derive PDA from `member_number`, fetch `mint`. > **Invariant**: AgentIndex PDAs are never deleted. Enumeration relies on sequential member\_numbers with no gaps. Concurrent registrations are safe due to PDA collision protection (second tx fails if same member\_number). #### Instructions | Instruction | Parameters | Behavior | |-------------|------------|----------| | `initialize` | — | Create registry + TokenGroup (one-time) | | `register_agent` | name, symbol\*, uri, additional\_metadata?, non\_transferable | Create Token-2022 NFT, add to group, create AgentIndex PDA, renounce mint | | `update_registry_authority` | new\_authority? | Transfer or renounce (None = immutable) | | `link_evm_address` | evm\_address, chain\_id, signature, recovery\_id | Verify secp256k1 signature, emit event | > \* **Note on `symbol`**: This field is vestigial from Token-2022's fungible token origin. For NFTs it has no semantic meaning. The SDK hardcodes this to an empty string `""`. The on-chain program still accepts and validates the field (max 10 bytes) for backwards compatibility. #### Events | Event | Fields | |-------|--------| | `AgentRegistered` | mint, owner, member\_number, name, uri, non\_transferable | | `RegistryAuthorityUpdated` | old\_authority, new\_authority | | `EvmAddressLinked` | agent\_mint, evm\_address, chain\_id, linked\_at | #### Errors `InvalidGroupMint` · `InvalidAuthority` · `ImmutableAuthority` · `NameTooLong` · `SymbolTooLong` · `UriTooLong` · `TooManyMetadataEntries` · `MetadataKeyTooLong` · `MetadataValueTooLong` · `Overflow` · `MintAuthorityNotRenounced` **EVM linking errors:** * `InvalidSecp256k1Signature` — invalid secp256k1 signature format * `Secp256k1RecoveryFailed` — secp256k1 public key recovery failed * `EvmAddressMismatch` — recovered address doesn't match provided `evm_address` * `InvalidEvmAddressRecovery` — failed to extract EVM address from recovered key ### Attestation #### SchemaConfig (PDA: `["schema_config", schema]`) | Field | Type | Description | |-------|------|-------------| | `sas_schema` | Pubkey | SAS schema address | | `signature_mode` | SignatureMode | DualSignature / CounterpartySigned / AgentOwnerSigned | | `storage_type` | StorageType | Compressed / Regular | | `delegation_schema` | `Option` | Schema for delegation verification (None = owner only) | | `closeable` | bool | Whether attestations can be closed | | `name` | String | Schema name for signing messages (max 32 chars) | | `bump` | u8 | PDA bump seed | **`delegation_schema` semantics**: * `Some(schema)`: Owner OR valid delegate can sign (delegation checked against specified schema) * `None`: Only owner can sign (used for DelegateV1 itself to prevent recursive delegation) #### CompressedAttestation Compressed accounts require Light Protocol derives for hashing and discrimination: ```rust #[derive(Clone, Debug, Default, LightDiscriminator, LightHasher)] pub struct CompressedAttestation { /* fields below */ } ``` | Field | Type | Offset | Description | |-------|------|--------|-------------| | `sas_schema` | Pubkey | 0 | Schema (memcmp filter) | | `agent_mint` | Pubkey | 32 | Agent mint address (memcmp filter) | | `data` | Vec\ | 64+ | Schema-conformant bytes (universal base layout) | | `num_signatures` | u8 | varies | Number of signatures (1 or 2) | | `signature1` | \[u8; 64] | varies | First Ed25519 signature | | `signature2` | \[u8; 64] | varies | Second Ed25519 signature (zeros if single-sig) | #### Universal Base Data Layout (first 131 bytes) All schemas MUST use this universal layout: | Offset | Size | Field | Description | |--------|------|-------|-------------| | 0 | 1 | `layout_version` | Layout version (currently `1`) | | 1 | 32 | `task_ref` | CAIP-220 tx hash or task identifier | | 33 | 32 | `agent_mint` | Agent mint address | | 65 | 32 | `counterparty` | Attester pubkey (Ed25519) | | 97 | 1 | `outcome` | Universal: 0=Negative, 1=Neutral, 2=Positive | | 98 | 32 | `data_hash` | Agent's blind commitment (zeros for AgentOwnerSigned/CounterpartySigned) | | 130 | 1 | `content_type` | Format: 0=None, 1=JSON, 2=UTF-8, 3=IPFS, 4=Arweave, 5=Encrypted | | 131 | var | `content` | Variable length, up to 512 bytes | **On-chain validation:** * `layout_version` == 1 (reject unknown versions for forward compatibility) * `outcome` ∈ {0, 1, 2} (0=Negative, 1=Neutral, 2=Positive) * `content_type` ≤ 15 (0-5 defined, 6-15 reserved for future) * Data length ≥ 131 bytes > **Layout versioning**: The `layout_version` byte enables future layout changes without requiring new schemas. Indexers and SDKs check byte 0 first to determine parsing strategy. Version 0 is reserved (never used). Future versions (2+) may add fields, reorder for alignment, or change semantics. **`data_hash` semantics:** For DualSignature schemas, this is the agent's cryptographic commitment (`keccak256(request || response)`). For AgentOwnerSigned schemas, this field stores schema-specific data (e.g., delegator pubkey for DelegateV1) or zeros if unused. For CounterpartySigned schemas, this field should be zero-filled. Program parses offsets 0-130 for signature binding and base validation. Content structure parsed by SDK/indexers. > **Note on `agent_mint` naming**: This field stores the **agent's mint address** (the stable identity), not an Associated Token Account (ATA). The agent mint is the Token-2022 NFT mint that serves as the agent's unique identifier in the SATI registry. **Note on timestamps**: Attestation creation time is tracked via Photon's `slotCreated` field. For interaction time (when the original event occurred), clients can look up the transaction referenced in `task_ref`. #### Signature Verification (On-Chain) Signatures are **extracted** from Ed25519 precompile instructions in the transaction, matched by **message content** (not index). This is resilient to transactions containing unrelated Ed25519 instructions from other protocols. Verification differs by `SignatureMode`: **DualSignature** (Feedback, Validation): 1. Extract 2 signatures from Ed25519 instructions by matching expected messages 2. **Agent authorization**: `verify_agent_authorization()` for signer of interaction\_hash message 3. **Counterparty binding**: Verify SIWS message signer matches pubkey from `data[65..97]` 4. **Self-attestation**: `agent_mint != counterparty` **AgentOwnerSigned** (DelegateV1): 1. Extract 1 signature matching interaction\_hash message 2. **Agent authorization**: `verify_agent_authorization()` for extracted signer 3. No counterparty binding (signer IS the agent owner/delegate) **CounterpartySigned** (FeedbackPublic, ReputationScore): 1. Extract 1 signature matching interaction\_hash message 2. **No agent authorization** — anyone can submit about any agent 3. **Counterparty binding**: Verify extracted signer matches pubkey from `data[65..97]` 4. **Self-attestation**: `agent_mint != counterparty` > **Note**: `agent_mint` is the agent's MINT ADDRESS. For DualSignature/AgentOwnerSigned, the agent OWNER (or delegate) signs the interaction\_hash. For DualSignature, counterparty signs the SIWS message. For CounterpartySigned, only the counterparty signs the interaction\_hash. #### verify\_agent\_authorization() Verification steps for agent authorization: 1. **Owner fast path** (~100 CU): If signer equals agent ATA owner, authorize immediately 2. **Delegation required**: If `delegation_schema` is `None`, reject with `OwnerOnly` 3. **Attestation required**: Delegation attestation must be provided 4. **PDA verification**: Derive expected PDA using `keccak256(schema || signer || agent_mint)` as nonce with SATI credential; reject if attestation key doesn't match (prevents schema confusion) 5. **Delegate binding**: Verify `counterparty` field equals signer 6. **Agent binding**: Verify `agent_mint` field equals agent mint account 7. **Owner binding**: Verify `data_hash` field equals current ATA owner (invalidates delegation after NFT transfer) 8. **Expiration check**: Verify `expiry == 0` OR `expiry > current_timestamp` > **Note**: PDA verification ensures the attestation belongs to the expected DelegateV1 schema, preventing attestation confusion attacks. The `data_hash` check invalidates delegation when the agent NFT is transferred. The `sati_credential` used in PDA derivation is the SATI SAS credential account (derived from authority + "SATI" name). #### Off-Chain Message Signing Format (Wallet UX) Counterparty signs a human-readable SIWS-inspired message for Phantom/Solflare: ``` SATI {schema_name} Agent: {base58(agent_mint)} Task: {base58(task_ref)} Outcome: {Negative|Neutral|Positive} Details: {content as UTF-8, or "[Encrypted]"} Sign to create this attestation. ``` **Fields:** | Field | Source | Description | |-------|--------|-------------| | `schema_name` | SchemaConfig.name | Schema identifier (e.g., "feedback") | | `Agent` | data\[33..65] | Agent mint address as base58 | | `Task` | data\[1..33] | Task reference as base58 | | `Outcome` | data\[97] | Mapped: 0→Negative, 1→Neutral, 2→Positive | | `Details` | data\[131..] | Content as UTF-8, or "\[Encrypted]" if content\_type=5 | **Example (Feedback):** ``` SATI feedback Agent: 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU Task: 9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM Outcome: Positive Details: {"value":85,"valueDecimals":0,"tag1":"helpful","tag2":"fast","m":"Great service!"} Sign to create this attestation. ``` **Signature types:** * **Agent**: Signs `interaction_hash = keccak256(DOMAIN_INTERACTION || schema || task_ref || data_hash)` — 32-byte hash * **Counterparty**: Signs the full human-readable message above (~300 bytes) — bypasses Phantom's 32-byte restriction > **Note**: The human-readable format enables wallet display while the full message (~300 bytes) bypasses Phantom's restriction on signing 32-byte messages (which look like transaction hashes). #### Instructions | Instruction | Parameters | Behavior | |-------------|------------|----------| | `register_schema_config` | schema, signature\_mode, storage\_type, delegation\_schema, closeable, name | Register schema config (authority only) | | `create_compressed_attestation` | data, proof, address\_tree\_info, output\_state\_tree\_index | Verify sigs → Light Protocol | | `create_regular_attestation` | data, expiry | Verify sigs → SAS storage | | `close_compressed_attestation` | proof, account\_meta, current\_data | Close compressed attestation | | `close_regular_attestation` | attestation\_pda | Close regular attestation | > **Note on signature handling**: Signatures are NOT included in instruction parameters. The program extracts pubkeys and signatures directly from Ed25519 precompile instructions that MUST precede the SATI instruction in the same transaction. This optimization saves ~192 bytes for DualSignature transactions (2× pubkey + 2× signature), enabling larger attestation content. > **Note**: Instructions are named explicitly for their storage type to avoid ambiguity. `delegation_schema` in `register_schema_config` controls whether delegates can sign attestations for that schema. **Routing**: Program checks `SchemaConfig.storage_type` and CPIs to Light Protocol (compressed) or SAS (regular). SATI Program PDA is the sole authorized signer for both storage backends. #### Events | Event | Fields | |-------|--------| | `SchemaConfigRegistered` | schema, signature\_mode, storage\_type, delegation\_schema, closeable, name | | `AttestationCreated` | sas\_schema, agent\_mint, counterparty, storage\_type, address | | `AttestationClosed` | sas\_schema, agent\_mint, address | #### Errors `SchemaConfigNotFound` · `InvalidSignatureCount` · `InvalidSignature` · `StorageTypeNotSupported` · `StorageTypeMismatch` · `AttestationDataTooSmall` · `AttestationDataTooLarge` · `ContentTooLarge` · `SignatureMismatch` · `SelfAttestationNotAllowed` · `AgentAtaMintMismatch` · `AgentAtaEmpty` · `AgentAtaRequired` · `UnauthorizedClose` · `AttestationNotCloseable` · `InvalidOutcome` · `InvalidContentType` · `UnsupportedLayoutVersion` · `LightCpiInvocationFailed` **Ed25519 signature verification:** * `InvalidEd25519Instruction` — invalid Ed25519 instruction format * `MissingSignatures` — required Ed25519 signatures not found in transaction * `MessageMismatch` — signature was for different data than expected * `InvalidInstructionsSysvar` — invalid instructions sysvar * `DuplicateSigners` — duplicate signers not allowed for dual signature mode * `Ed25519InstructionNotFound` — no Ed25519 instruction in transaction * `AgentSignatureNotFound` — agent's Ed25519 signature not found * `CounterpartySignatureNotFound` — counterparty's Ed25519 signature not found **Universal base layout validation:** * `InvalidOutcome` — outcome not in {0, 1, 2} * `InvalidContentType` — content\_type > 15 (0-5 defined, 6-15 reserved) * `UnsupportedLayoutVersion` — layout version not supported **Delegation validation:** * `OwnerOnly` — schema requires owner signature but delegate attempted * `DelegationAttestationRequired` — delegate signed but no delegation attestation provided * `InvalidDelegationPDA` — delegation attestation PDA doesn't match expected derivation * `DelegateMismatch` — delegation attestation delegate doesn't match signer * `AgentMintMismatch` — delegation attestation agent doesn't match target agent * `DelegationOwnerMismatch` — delegation was created by different owner (NFT was transferred) * `DelegationExpired` — delegation attestation has expired *** ## Identity: Token-2022 NFT ### Extensions | Extension | Purpose | |-----------|---------| | MetadataPointer | Points to metadata location | | TokenMetadata | name, symbol, uri, additionalMetadata | | GroupMemberPointer | Points to group membership | | TokenGroupMember | SATI Registry membership | | NonTransferable | Optional: soulbound agents | > ⚠️ **Soulbound Warning**: The `NonTransferable` extension is **permanent and irreversible**. Once set at mint creation, the agent NFT can NEVER be transferred to another wallet. Use only when you are certain the agent should be permanently bound to the initial owner. Consider using a smart account (Squads) if you may need to change control in the future. ### Configuration | Property | Value | |----------|-------| | decimals | 0 (NFT) | | supply | 1 (unique) | | mint\_authority | None (renounced) | | freeze\_authority | None | ### TokenMetadata | Field | Description | |-------|-------------| | `updateAuthority` | Agent owner | | `mint` | Agent ID | | `name` | Agent name | | `symbol` | Empty string (legacy field, not used) | | `uri` | Registration file URL | | `additionalMetadata` | agentWallet, did, a2a, mcp | **Common additionalMetadata keys:** * `agentWallet` — Agent's payment wallet (CAIP-10 format) * `did` — Decentralized identifier * `a2a` — Agent-to-Agent endpoint URL * `mcp` — MCP server endpoint URL ### Operations * **Update metadata**: Direct `spl-token-metadata` calls * **Transfer**: Standard Token-2022 transfer * **Smart accounts**: Squads can own via ATAs *** ## Schemas ### Core Schemas | Schema | Storage | SignatureMode | Closeable | delegation\_schema | Status | |--------|---------|---------------|-----------|-------------------|--------| | FeedbackV1 | Compressed | DualSignature | No | DelegateV1 | ✅ MVP | | FeedbackPublicV1 | Compressed | CounterpartySigned | No | None | ✅ MVP | | ValidationV1 | Compressed | DualSignature | No | DelegateV1 | ✅ MVP | | ReputationScoreV3 | Regular | CounterpartySigned | Yes | None | ✅ MVP | | DelegateV1 | Regular | AgentOwnerSigned | Yes | None | ✅ MVP | **SignatureMode determines payload signature requirements:** | Mode | Signatures | Use Case | |------|------------|----------| | DualSignature | 2 | Feedback, Validation (blind feedback model) | | CounterpartySigned | 1 | FeedbackPublic, ReputationScore (counterparty/provider signs) | | AgentOwnerSigned | 1 | DelegateV1 (agent owner or delegate signs) | > **Note**: `SingleSigner` was split into `CounterpartySigned` and `AgentOwnerSigned` to distinguish who must sign. This enables delegation for AgentOwnerSigned schemas while preventing it for CounterpartySigned schemas. ### FeedbackV1 Schema Uses universal base layout (131 bytes) + JSON content for extensibility. | Field | Offset | Description | |-------|--------|-------------| | layout\_version | 0 | `1` (current layout version) | | task\_ref | 1-32 | CAIP-220 tx hash or task identifier | | agent\_mint | 33-64 | Agent mint address | | counterparty | 65-96 | Client pubkey | | outcome | 97 | 0=Negative, 1=Neutral, 2=Positive | | data\_hash | 98-129 | Agent's blind commitment (`keccak256(request \|\| response)`) | | content\_type | 130 | 1=JSON (recommended), 0=None, 2=UTF-8, 5=Encrypted | | content | 131+ | JSON with optional fields (see below) | **JSON Content Fields** (all optional): ```json { "value": 85, // ERC-8004 signed fixed-point value "valueDecimals": 0, // decimal places (0-18) "tag1": "helpful", // first tag dimension "tag2": "fast", // second tag dimension "endpoint": "https://api.example.com", // endpoint reviewed "m": "Great service!" // message/comment } ``` **Size**: 131 bytes minimum (empty content), typical 180-250 bytes with JSON content. **Fixed offset benefit**: `outcome` at offset 97 enables Photon memcmp filtering by feedback sentiment. **ERC-8004 field mapping**: `value` + `valueDecimals` map directly to ERC-8004's `int128 value` + `uint8 valueDecimals`. `tag1`/`tag2` map to ERC-8004's `tag1`/`tag2` string fields. The `outcome` field provides categorical filtering (Negative/Neutral/Positive) while `value` provides granular numeric feedback. ### FeedbackPublicV1 Schema Public feedback that anyone can submit about an agent without agent participation. Uses CounterpartySigned mode (counterparty signature only, no agent signature required). | Field | Offset | Description | |-------|--------|-------------| | layout\_version | 0 | `1` (current layout version) | | task\_ref | 1-32 | CAIP-220 tx hash or task identifier | | agent\_mint | 33-64 | Agent mint address | | counterparty | 65-96 | Feedback author pubkey | | outcome | 97 | 0=Negative, 1=Neutral, 2=Positive | | data\_hash | 98-129 | Zero-filled (CounterpartySigned mode, no blind commitment) | | content\_type | 130 | 1=JSON (recommended), 0=None, 2=UTF-8, 5=Encrypted | | content | 131+ | JSON with optional fields (same as FeedbackV1) | **Key difference from FeedbackV1**: No agent signature required. Anyone can submit feedback about any agent. The agent does not participate in the blind feedback model. **Use cases**: * Public reviews where agent participation is not required * Third-party assessments or ratings * Community-sourced feedback **Trade-off**: Without agent signature, there's no cryptographic proof the agent actually served the referenced task. Trust depends on the `task_ref` being verifiable through other means (e.g., on-chain payment transaction). ### ValidationV1 Schema Uses universal base layout (131 bytes) + JSON content for validation details. | Field | Offset | Description | |-------|--------|-------------| | layout\_version | 0 | `1` (current layout version) | | task\_ref | 1-32 | Task reference | | agent\_mint | 33-64 | Agent mint address | | counterparty | 65-96 | Validator pubkey | | outcome | 97 | 0=Fail, 1=Inconclusive, 2=Pass | | data\_hash | 98-129 | Agent's work commitment | | content\_type | 130 | 1=JSON (recommended), 0=None, 5=Encrypted | | content | 131+ | JSON with validation details (see below) | **JSON Content Fields** (all optional): ```json { "type": "tee", // Validation type: tee/zkml/reexecution/consensus "confidence": 95, // 0-100 confidence score "report": "..." // Validation report/details } ``` **Size**: 131 bytes minimum (empty content), typical 150-200 bytes with JSON content. **Fixed offset benefit**: `outcome` at offset 97 enables Photon memcmp filtering by validation result. **Validation types**: `tee` (TEE attestation), `zkml` (ZK-ML proof), `reexecution` (deterministic replay), `consensus` (multi-validator agreement). ### ReputationScoreV3 Schema Provider-computed scores using `StorageType::Regular` for direct on-chain queryability. Uses CounterpartySigned mode (provider signature only). Content is stored as VecU8 (SAS type 13) with a 4-byte LE length prefix. | Field | Offset | Size | Description | |-------|--------|------|-------------| | layout\_version | 0 | 1 | `1` (current layout version) | | task\_ref | 1 | 32 | Deterministic: `keccak256(counterparty \|\| agent_mint)` | | agent\_mint | 33 | 32 | Agent mint address being scored | | counterparty | 65 | 32 | Provider (reputation scorer) | | outcome | 97 | 1 | Provider's categorical assessment (0=Poor, 1=Average, 2=Good) | | data\_hash | 98 | 32 | Zero-filled (CounterpartySigned mode, no blind commitment) | | content\_type | 130 | 1 | 1=JSON (recommended) | | content\_len | 131 | 4 | VecU8 length prefix (little-endian u32) | | content | 135 | N | JSON with score details (see below) | **JSON Content Fields** (all optional): ```json { "score": 85, // 0-100 normalized score "methodology": "weighted_average", // Scoring algorithm identifier "feedbackCount": 42, // Number of feedbacks analyzed "validationCount": 5 // Number of validations analyzed } ``` **SAS Schema Layout**: `[0, 4, 4, 4, 4, 4, 4, 0, 4, 4, 0, 13]` (12 fields, VecU8 for content). **Size**: 135 bytes minimum (empty content), typical 160-270 bytes with JSON content. **Semantics**: One ReputationScore per (provider, agent) pair. Providers update by closing old attestation and creating new one with same deterministic nonce. On-chain creation time is tracked via SAS attestation metadata. ### DelegateV1 Schema Authorization attestation allowing a delegate to sign on behalf of an agent owner. Uses `StorageType::Regular` for on-chain queryability and `SignatureMode::AgentOwnerSigned` with `delegation_schema: None` (owner only, no recursive delegation). | Field | Offset | Description | |-------|--------|-------------| | layout\_version | 0 | `1` (current layout version) | | task\_ref | 1-32 | Reserved (zeros) | | agent\_mint | 33-64 | Agent mint address | | counterparty | 65-96 | Delegate pubkey (who receives authorization) | | outcome | 97 | Reserved (0) | | data\_hash | 98-129 | Delegator pubkey (owner at delegation time) | | content\_type | 130 | 0=None | | content | 131+ | Empty | **SAS Schema Definition:** ```typescript export const DELEGATE_SAS_SCHEMA: SASSchemaDefinition = { name: "DelegateV1", description: "Delegation authorization for hot wallet signing", // Layout types: u8=0, pubkey=7, blob=9 layout: [0, 7, 7, 7, 0, 7, 0, 9], fieldNames: ["layout_version", "task_ref", "agent_mint", "counterparty", "outcome", "data_hash", "content_type", "content"], }; ``` **Address Derivation (deterministic nonce)**: ```rust // One delegation per (schema, delegate, agent) tuple // Using schema pubkey as domain separator enables future delegation versions let nonce = keccak256(delegate_schema.as_ref(), delegate, agent_mint); ``` **Expiration**: Uses SAS attestation `expiry` field. `0` = no expiration. **Revocation**: Owner calls `close_regular_attestation` to revoke. **Size**: 131 bytes (no content). > **Why SAS for delegation?** Reuses existing infrastructure: built-in expiration, existing close mechanism, existing query patterns. Tradeoff: ~5-10k CU for delegate verification vs ~300 CU for custom PDA, but only applies when delegate signs (owner signing is ~100 CU fast path). Squads doesn't solve this — it requires human approval per action, incompatible with automated attestation signing. ### Delegation Authorization Flow ``` ┌─────────────────────────────────────────────────────────────────┐ │ DELEGATION LIFECYCLE │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 1. GRANT (Owner only): │ │ Owner calls create_regular_attestation(DelegateV1, ...) │ │ • delegation_schema: None → owner-only check │ │ • Creates SAS attestation with deterministic nonce │ │ • data_hash = delegator (current owner) │ │ │ │ 2. USE: │ │ Delegate calls create_*_attestation(Feedback/etc, ...) │ │ • delegation_schema: Some(DelegateV1) → check delegation │ │ • verify_agent_authorization(): │ │ - If signer == owner → OK (fast path, ~100 CU) │ │ - Else lookup DelegateV1 attestation │ │ - Verify: counterparty == signer (delegate) │ │ - Verify: agent_mint account matches data │ │ - Verify: data_hash == current owner (transfer safety) │ │ - Verify: not expired │ │ - If valid → OK (slow path, ~5-10k CU) │ │ │ │ 3. REVOKE (Owner only): │ │ Owner calls close_regular_attestation(DelegateV1 PDA) │ │ • delegation_schema: None → owner-only check │ │ • Closes attestation, rent reclaimed │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` ### Address Derivation **Compressed Attestations (Light Protocol):** ```rust let nonce = keccak256(&[task_ref, sas_schema, agent_mint, counterparty].concat()); let (address, seed) = derive_address( &[b"attestation", sas_schema, agent_mint, &nonce], &address_tree_pubkey, &program_id ); ``` **Note**: Including `counterparty` in the nonce ensures unique addresses per (task, agent, counterparty) tuple, preventing address collisions when different counterparties attest to the same agent for the same task. **Regular Attestations (SAS):** ```rust // Nonce is deterministic: one ReputationScore per (provider, agent) pair // Note: For ReputationScore, counterparty = provider (the reputation scorer) let nonce = keccak256(&[counterparty, agent_mint]); // counterparty = provider // SAS PDA derivation let (attestation_pda, _) = Pubkey::find_program_address( &[b"attestation", sati_credential, sas_schema, &nonce.to_bytes()], &SAS_PROGRAM_ID ); ``` **Delegation Attestations (SAS):** ```rust // Use schema pubkey as domain separator - enables future delegation versions let nonce = keccak256(delegate_schema.as_ref(), delegate, agent_mint); // SAS PDA derivation let (delegation_pda, _) = Pubkey::find_program_address( &[b"attestation", sati_credential, delegate_schema, &nonce.to_bytes()], &SAS_PROGRAM_ID ); ``` **Deterministic nonce** ensures one delegation per (schema, delegate, agent) tuple. Using schema pubkey as domain separator allows multiple delegation schema versions to coexist. ### Content Types The `content_type` field determines how to interpret the variable-length `content` field: | Code | Type | Content | Use Case | |------|------|---------|----------| | 0 | None | Empty | Just use outcome (no extended content) | | 1 | JSON | Inline JSON object | Structured feedback with metadata | | 2 | UTF-8 | Plain text | Simple text feedback | | 3 | IPFS | CIDv1 (~36 bytes) | Large content stored off-chain | | 4 | Arweave | Transaction ID (32 bytes) | Permanent off-chain storage | | 5 | Encrypted | X25519-XChaCha20-Poly1305 payload | End-to-end encrypted content | | 6-15 | Reserved | — | Future content types | **On-chain validation**: `content_type ≤ 15`. Values 6-15 are reserved for future use without program upgrades. **Size limit**: `MAX_CONTENT_SIZE = 512 bytes`. Enforced on-chain. For larger content, use IPFS/Arweave. **Examples:** ```json // content_type=1 (JSON), ~80 bytes {"value":85,"valueDecimals":0,"tag1":"helpful","m":"Fast and accurate"} // content_type=2 (UTF-8), ~30 bytes "Excellent service, would recommend" // content_type=0 (None) // Empty content, just use outcome ``` **Design rationale**: Simple feedback doesn't need IPFS. Inline JSON/UTF-8 is directly readable by indexers without external fetches. ### Encrypted Content (ContentType = 5) End-to-end encrypted content using X25519-XChaCha20-Poly1305. Only the intended recipient can decrypt. **Wire Format:** | Offset | Size | Field | Description | |--------|------|-------|-------------| | 0 | 1 | Version | Protocol version (0x01) | | 1 | 32 | Ephemeral Public Key | X25519 public key for ECDH | | 33 | 24 | Nonce | XChaCha20 nonce | | 57 | variable | Ciphertext | Encrypted content + 16-byte Poly1305 tag | **Size Constraints:** * Minimum overhead: 73 bytes (1 + 32 + 24 + 16) * Maximum plaintext: 439 bytes (512 - 73) * Total must fit within `MAX_CONTENT_SIZE = 512 bytes` **Key Derivation:** 1. **Recipient Key**: Convert Ed25519 Solana keypair to X25519 using the standard birational map: ``` x25519_private = Ed25519_to_X25519_private(ed25519_seed) // RFC 8032 compatible x25519_public = Ed25519_to_X25519_public(ed25519_public) // Montgomery form conversion ``` Note: This uses the standard curve conversion, not HKDF. Solana wallets can derive encryption keys deterministically. 2. **Shared Secret**: Ephemeral ECDH key exchange: ``` shared_secret = X25519(ephemeral_private, recipient_public) ``` 3. **Encryption Key**: Derive from shared secret via HKDF: ``` encryption_key = HKDF-SHA256(shared_secret, salt=ephemeral_public, info="sati-v1", len=32) ``` **Encryption Process:** 1. Generate ephemeral X25519 keypair 2. Compute shared secret via ECDH 3. Derive encryption key via HKDF 4. Generate random 24-byte nonce 5. Encrypt plaintext with XChaCha20-Poly1305 6. Serialize: `version || ephemeral_public || nonce || ciphertext` 7. Zero ephemeral private key **Decryption Process:** 1. Deserialize payload components 2. Derive recipient's X25519 private key from Ed25519 3. Compute shared secret via ECDH 4. Derive encryption key via HKDF 5. Decrypt and verify with XChaCha20-Poly1305 **Semantics:** * Only the recipient (agent) can decrypt content * Base fields (outcome, counterparty, task\_ref) remain unencrypted and queryable * Content field contains the serialized encrypted payload * Forward secrecy: ephemeral keypair per encryption **Example:** ```typescript import { encryptContent, deriveEncryptionKeypair } from '@cascade-fyi/sati-sdk'; // Derive agent's encryption public key const { publicKey } = deriveEncryptionKeypair(agentEd25519Seed); // Encrypt feedback content const plaintext = JSON.stringify({ value: 85, valueDecimals: 0, m: "Private feedback" }); const encrypted = encryptContent( new TextEncoder().encode(plaintext), publicKey ); // Use in FeedbackData const feedback = { contentType: ContentType.Encrypted, // 5 content: serializeEncryptedPayload(encrypted), // ... other fields remain unencrypted }; ``` **Privacy Guarantees:** * Content confidentiality: Only recipient can read * Forward secrecy: Compromise of long-term key doesn't expose past messages * Integrity: Poly1305 tag prevents tampering **Limitations:** * Metadata visible: counterparty, outcome, timestamps remain public * Ciphertext length reveals approximate plaintext length * No key rotation mechanism (uses wallet-derived keys) *** ## Storage & Indexing ### Light Protocol (ZK Compression) Stores Feedback and Validation attestations as merkle tree leaves (~200x cheaper than accounts). | Aspect | Value | |--------|-------| | Cost per attestation | ~0.00001 SOL | | On-chain storage | 32-byte merkle root | | Verification | ZK proof (~100K CU) | | Programs | Light System, Account Compression, Noop | **SATI uses**: CPI to Light System for create/close, Photon for queries. ### Photon Indexing (Compressed) Reconstructs compressed accounts from Noop logs. Free via Helius RPC. | Method | Purpose | |--------|---------| | `getCompressedAccountsByOwner` | Query by owner + filters | | `getValidityProof` | Get ZK proof for on-chain verification | | `getCompressedAccountProof` | Merkle proof for escrow | **Filters**: `sas_schema` (offset 0), `agent_mint` (offset 32), `outcome` (offset 68 + 97 = 165, within data field) ### SAS (Regular Storage) Stores ReputationScore and Delegation as standard Solana accounts for direct on-chain queryability. | Aspect | Value | |--------|-------| | Cost per attestation | ~0.002 SOL (rent) | | On-chain storage | Full account data | | Verification | Direct account read | | Program | SAS (`22zoJMtdu4tQc2PzL74ZUT7FrwgB1Udec8DdW4yw4BdG`) | **SATI uses**: CPI to SAS for create/close, standard RPC for queries. ### RPC Queries (Regular) Regular attestations use standard Solana RPC methods: | Method | Purpose | |--------|---------| | `getAccountInfo` | Fetch single attestation by PDA | | `getProgramAccounts` | Query by filters (schema, agent\_mint) | **Filters**: `credential` (offset 33), `schema` (offset 65), custom data filters **Tradeoff**: Regular attestations cost ~200x more but are directly queryable on-chain by other programs (useful for escrow, governance, delegation verification). *** ## SDK Interface Package: `@cascade-fyi/sati-sdk` ### Types ```typescript enum Outcome { Negative = 0, Neutral = 1, Positive = 2 } // Tags are free-form strings (max 32 chars each) // Common examples: "quality", "speed", "reliability", "communication", "value", "accuracy" type Tag = string; ``` ### Methods | Category | Method | Returns | |----------|--------|---------| | **Registry** | `registerAgent(params)` | `{ mint, memberNumber, signature }` | | **Identity** | `loadAgent(mint)` | `AgentIdentity` | | | `listAgentsByOwner(owner)` | `AgentIdentity[]` | | | `listAllAgents()` | `AgentIdentity[]` | | | `getAgentByMemberNumber(n)` | `AgentIdentity \| null` | | | `updateAgentMetadata(params)` | `{ signature }` | | | `transferAgent(params)` | `{ signature }` | | **Delegation** | `createDelegation(params)` | `{ address, signature }` | | | `revokeDelegation(params)` | `{ signature }` | | | `getDelegation(delegate, tokenAccount)` | `Delegation \| null` | | | `listDelegations(tokenAccount)` | `Delegation[]` | | | `listDelegationsByDelegate(delegate)` | `Delegation[]` | | | `verifyDelegation(delegate, tokenAccount)` | `boolean` | | **Compressed** | `createFeedback(params)` | `{ address, signature }` | | | `createFeedbackBatch(params[])` | `{ addresses, signature }` | | | `createValidation(params)` | `{ address, signature }` | | | `closeCompressedAttestation(params)` | `{ signature }` | | **Regular** | `createReputationScore(params)` | `{ address, signature }` | | | `updateReputationScore(params)` | `{ address, signature }` | | | `closeRegularAttestation(params)` | `{ signature }` | | **Query (Compressed)** | `listFeedbacks(filter)` | `PaginatedAttestations` | | | `listValidations(filter)` | `PaginatedAttestations` | | | `getAttestationWithProof(address)` | `{ attestation, proof }` | | **Query (Regular)** | `getReputationScore(provider, agentMint)` | `ReputationScoreData \| null` | | | `listReputationScores(agentMint, sasSchema)` | `ReputationScoreData[]` | | **Verify** | `verifyProof(proof, expectedRoot?)` | boolean | | | `verifySignatures(attestation)` | `SignatureVerificationResult` | | **Setup** | `setupSASSchemas(params)` | `SASDeploymentResult` | | | `registerSchemaConfig(params)` | `{ signature }` | | | `getSchemaConfig(sasSchema)` | `SchemaConfig \| null` | | **Signing** | `computeDataHash(request, response)` | `Uint8Array` | | | `computeDataHashFromStrings(request, response)` | `Uint8Array` | | | `computeInteractionHash(schema, taskRef, dataHash)` | `Uint8Array` | | | `buildCounterpartyMessage(schemaName, data)` | `string` | | | `zeroDataHash()` | `Uint8Array` (32 zeros for OwnerSigned) | | **Validation** | `validateBaseLayout(data)` | `void` (throws on invalid) | | | `validateFeedbackContent(content)` | `void` (throws on invalid) | | | `validateReputationScoreContent(content)` | `void` (throws on invalid) | | **Identity** | `linkEvmAddress(params)` | `{ signature }` | ### Query Types ```typescript interface PaginatedAttestations { items: T[]; cursor: string | null; // For pagination, null if no more results } interface AttestationFilter { sasSchema: Address; // Required - schema address from loadDeployedConfig() agentMint?: Address; // Optional filter by agent counterparty?: Address; // Optional filter (applied client-side) outcome?: Outcome; // Optional filter (applied client-side) limit?: number; // Max results per page cursor?: string; // Pagination cursor from previous response } interface ParsedFeedbackAttestation { address: Uint8Array; raw: CompressedAccount; attestation: CompressedAttestation; data: FeedbackData; } ``` ### createFeedback Example ```typescript // SDK builds Ed25519 instructions from provided signatures automatically await sati.createFeedback({ tokenAccount, counterparty: clientPubkey, outcome: Outcome.Positive, taskRef: paymentTxHash, // CAIP-220 format agentSignature: agentSig, // Signs interaction_hash counterpartySignature: clientSig, // Signs SIWS message }); // Note: SDK constructs Ed25519 precompile instruction(s) that precede // the SATI instruction. The program extracts signatures from Ed25519 ix. ``` ### createDelegation Example ```typescript // Owner grants delegation to hot wallet await sati.createDelegation({ tokenAccount: agentMint, delegate: hotWalletPubkey, expiresAt: Math.floor(Date.now() / 1000) + 86400 * 30, // 30 days ownerSignature: ownerSig, // Owner signs to authorize delegation // delegator (data_hash) is set automatically to current owner }); // Hot wallet can now sign feedback on behalf of agent await sati.createFeedback({ tokenAccount: agentMint, counterparty: clientPubkey, outcome: Outcome.Positive, taskRef: paymentTxHash, agentSignature: hotWalletSig, // Delegate signs instead of owner counterpartySignature: clientSig, // Client signs SIWS // SDK automatically detects signer != owner, fetches delegation attestation }); ``` > **SDK Auto-Lookup**: When signer differs from agent owner, SDK automatically derives the delegation PDA, fetches it, validates expiration, and includes it in the transaction. Caller does not need to explicitly provide delegation attestation. *** ## Security ### On-Chain Guarantees | Property | Enforcement | |----------|-------------| | Layout version | Verified == 1 (reject unknown versions) | | Signature validity | Ed25519 verification (precompile) | | Blind feedback | Agent signs before outcome known | | Agent authorization | ATA ownership OR valid delegation | | Counterparty binding | Verify signature using pubkey from `data[65..97]` | | Self-attestation prevention | `agent_mint ≠ counterparty` | | Duplicate prevention | Deterministic address from task\_ref | | Outcome range | Verified ∈ {0, 1, 2} before storage | | Content type range | Verified ≤ 15 before storage (0-5 defined, 6-15 reserved) | | Closeable enforcement | Schema config controls whether close is allowed | ### Close Authorization Close authorization follows the principle: **the signing party controls closure**. | Schema | Closeable | Who Can Close | Rationale | |--------|-----------|---------------|-----------| | FeedbackV1 | No | — | Permanent record | | FeedbackPublicV1 | No | — | Permanent record | | ValidationV1 | No | — | Permanent record | | ReputationScoreV3 | Yes | Provider (counterparty) only | Provider created it; agent cannot delete unfavorable scores | | DelegateV1 | Yes | Agent owner only | Owner controls their own delegations | > **Note**: For single-signature modes, only the signing party can close. For DualSignature schemas (if closeable in future), either party could close since both consented to creation. Delegates cannot close attestations -- only the original signing party (agent owner for DelegateV1, provider for ReputationScoreV3). ### Delegation Permissions **What delegates CAN do:** * Sign `create_compressed_attestation` for schemas with `delegation_schema: Some(DelegateV1)` * Sign `create_regular_attestation` for schemas with `delegation_schema: Some(DelegateV1)` **What delegates CANNOT do:** * Create sub-delegations (DelegateV1 has `delegation_schema: None`) * Close attestations (`close_*_attestation` requires owner) * Revoke their own delegation (requires owner) * Transfer the agent NFT (Token-2022 requires owner signature) * Update agent metadata (Token-2022 requires updateAuthority) > **Note**: Delegation scope is all-or-nothing for attestation signing. A delegate authorized for one schema can sign for ALL schemas that allow delegation. Granular per-schema delegation is not supported in v1.0. ### Delegation Security | Property | Enforcement | |----------|-------------| | Owner-only delegation grant | `delegation_schema: None` for DelegateV1 → signer must be owner | | No recursive delegation | DelegateV1 has `delegation_schema: None` → delegates cannot create sub-delegates | | Transfer invalidation | `delegator` field checked against current owner → delegation invalid after NFT transfer | | Expiration enforcement | SAS attestation `expiry` checked in `verify_agent_authorization()` | | Deterministic nonce | One delegation per (schema, delegate, agent) tuple → no duplicate delegations | **Attack Mitigations:** | Attack | Mitigation | |--------|------------| | Delegate persists after NFT sale | `delegator == current_owner` check invalidates old delegations | | Expired delegation use | Explicit expiry check in verification | | Schema/PDA confusion | Verify attestation PDA matches expected derivation before deserializing | | Account confusion | Deserialize and verify ALL fields (delegate, agent\_mint, delegator) | | Recursive delegation | DelegateV1's `delegation_schema: None` prevents delegates from delegating | ### Off-Chain Validation (SDK/Indexer) | Property | Enforcement | |----------|-------------| | task\_ref format | SDK validates CAIP-220 | | JSON content structure | SDK validates schema-specific fields | | Score range (0-100) | SDK validates for Feedback/ReputationScore | | Confidence range (0-100) | SDK validates for Validation | | Tag length (max 32 chars) | SDK validates in JSON content | | Timestamp filtering | Client-side filtering via Photon `slotCreated` | **Rationale**: Schema-specific validation is performed by SDK, not on-chain. This allows new schemas to be registered without program upgrades. The program validates only universal base layout fields. ### Trust Model | Component | Trust Assumption | |-----------|-----------------| | SATI Programs | Correctly verify signatures and delegation | | Light Protocol | State tree integrity | | Photon | Accurate indexing | | SDK | Validates formats, constructs hashes | ### Known Limitations * **Sybil resistance**: Prevents self-attestation but not multiple wallets. Reputation providers implement sybil-resistant scoring. * **No pending state**: The protocol has no concept of "pending" validation or feedback. Attestations only exist once both signatures are collected and submitted. There is no on-chain way to check if a validation is in progress but not yet complete. * **Timestamp trust**: The `timestamp` field is set by the submitter and not verified against on-chain time. It should be trusted for ordering purposes only, not as cryptographic proof of time. * **Delegation race condition**: Between revocation and transaction landing, a delegate could complete one more operation. This is inherent to blockchain finality. * **Stale delegations after transfer**: When an agent NFT transfers, old delegations become invalid but their PDAs remain. New owner must call `close_regular_attestation` to reclaim rent before creating new delegations for the same delegate. *** ## Deployment ### Addresses | Component | Address | |-----------|---------| | SATI Program | `satiRkxEiwZ51cv8PRu8UMzuaqeaNU9jABo6oAFMsLe` | | SAS Program | `22zoJMtdu4tQc2PzL74ZUT7FrwgB1Udec8DdW4yw4BdG` | | TokenGroup Mint | `satiG7i9iyFxjq23sdyeLB4ibAHf6GXCARuosGeqane` | | SAS Credential | Derived at deployment from authority + "SATI" | | Lookup Table | Derived at deployment from authority + slot | **SAS Schemas**: * FeedbackV1 * FeedbackPublicV1 * ValidationV1 * ReputationScoreV3 * DelegateV1 **Note**: Registry Config PDA derived from `["registry"]`. SAS Credential, Schema PDAs, SchemaConfig PDAs, and Lookup Table addresses are deterministic but generated at deployment time. See `packages/sdk/src/deployed/{network}.json` for deployed addresses. ### Governance **Authority lifecycle**: Launch (multisig) → Stable (multisig) → Immutable (renounced) * Registry authority controls `update_registry_authority()` only * Upgrade authority controls program deployment * Both independently renounceable **Schema governance**: Versioned, not upgraded. New schema = new version (e.g., FeedbackV2). *** ## ERC-8004 Compatibility SATI implements the [ERC-8004: Trustless Agents](https://eips.ethereum.org/EIPS/eip-8004) specification on Solana with enhancements for cost efficiency and security. ### Compatibility Matrix | ERC-8004 Feature | SATI | Notes | |------------------|------|-------| | **Identity** | | | | Agent registration | ✅ | Registry program → Token-2022 NFT | | `tokenId` (auto-incrementing) | ✅ | TokenGroupMember.member\_number | | `ownerOf(tokenId)` | ✅ | Token account holder | | `transferFrom()` | ✅ | Direct Token-2022 transfer | | `setApprovalForAll()` | ✅ | Token delegate | | `tokenURI` / registration file | ✅ | TokenMetadata.uri | | On-chain metadata | ✅ | TokenMetadata.additionalMetadata | | **Reputation** | | | | `giveFeedback()` | ✅ | Compressed attestation via Light Protocol | | `revokeFeedback()` | ✅ | close\_compressed\_attestation() | | `appendResponse()` | ✅ | FeedbackResponse schema (deferred) | | `getSummary()` | ✅ | Photon indexer queries | | `readFeedback()` | ✅ | Fetch compressed attestation | | **Validation** | | | | `validationRequest()` | ✅ | Validation schema | | `validationResponse()` | ✅ | Validation schema with response score | | **Cross-Chain** | | | | Wallet display | ✅ | Phantom, Solflare, Backpack | | DID support | ✅ | additionalMetadata\["did"] | | CAIP-2/CAIP-10 | ✅ | Chain-agnostic identifiers | ### ERC-8004 January 2026 Spec Update The January 2026 ERC-8004 specification update **removed `feedbackAuth` entirely**: > "The new spec removes `feedbackAuth` entirely... leans harder on filtering by reviewer/clientAddress and off-chain aggregation for Sybil/spam mitigation" This means ERC-8004 now uses an **open feedback model** where anyone can submit feedback about any agent without the agent's involvement. ### Authorization Model Comparison | Aspect | ERC-8004 (Jan 2026) | SATI Dual-Signature | |--------|---------------------|---------------------| | Agent authorization | None required | Agent signs with response (blind) | | Proof of interaction | None | Cryptographic (task\_ref = payment tx) | | Sybil resistance | Off-chain filtering | Both parties must sign same task\_ref | | Selective participation | N/A (open model) | Blocked — agent signs before knowing outcome | | Gas cost | Client pays | Agent pays (bundled into service) | **Security implications:** | Attack Vector | ERC-8004 (new) | SATI | |---------------|----------------|------| | Sybil boosting (fake positive reviews) | Possible | Blocked — requires agent signature | | Competitor attacks (fake negatives) | Possible | Blocked — requires agent signature | | Selective participation | N/A | Blocked — agent signs before knowing outcome | | Proof of interaction | None | Cryptographic (task\_ref = payment tx) | SATI's blind feedback model provides stronger guarantees than ERC-8004's open model. The tradeoff is complexity: SATI requires agent participation in the signature flow. ### CAIP and DID Support SATI uses [Chain Agnostic Improvement Proposals](https://github.com/ChainAgnostic/CAIPs) for cross-chain interoperability: **CAIP-2 (Blockchain ID)**: `namespace:reference` | Chain | CAIP-2 Identifier | |-------|-------------------| | Solana Mainnet | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` | | Solana Devnet | `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1` | | Ethereum Mainnet | `eip155:1` | | Base | `eip155:8453` | **CAIP-10 (Account ID)**: `chain_id:account_address` ``` solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:7S3P4HxJpyyigGzodYwHtCxZyUQe9JiBMHyRWXArAaKv eip155:1:0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb7 ``` **DID Support** (via additionalMetadata): ```typescript ["did", "did:web:agent.example.com"] // Web-based DID ["did", "did:pkh:solana:5eykt4...:7S3P4..."] // PKH (blockchain account) ["did", "did:key:z6Mkf..."] // Key-based DID ``` See [CAIP Standards](https://github.com/ChainAgnostic/CAIPs) for full format specifications. *** ## Cross-Chain ### Registration File Schema The registration file is an off-chain JSON document referenced by the on-chain `uri` field. SATI's schema merges ERC-8004 requirements with Metaplex/Phantom standards, ensuring agents display correctly in Solana wallets while maintaining cross-chain compatibility. #### Field Reference | Field | Source | Required | Description | |-------|--------|----------|-------------| | `type` | ERC-8004 | Yes | Schema identifier | | `name` | Both | Yes | Agent name | | `description` | Both | Yes | Agent description | | `image` | Both | Yes | Primary image URL | | `properties.files` | Metaplex | Yes\* | Image with MIME type for wallet display | | `properties.category` | Metaplex | No | Asset category | | `external_url` | Metaplex | No | Project website | | `services` | ERC-8004 | No | Service endpoints (A2A, MCP, etc.) | | `registrations` | ERC-8004 | No | Cross-chain registration entries | | `supportedTrust` | ERC-8004 | No | Supported trust mechanisms | | `active` | SATI | No | Operational status | | `x402Support` | ERC-8004 | No | x402 payment support | \*Required for Phantom wallet image rendering #### Example ```json { "type": "https://eips.ethereum.org/EIPS/eip-8004#registration-v1", "name": "myAgentName", "description": "AI assistant with x402 payment support", "image": "https://example.com/agent.png", "properties": { "files": [{ "uri": "https://example.com/agent.png", "type": "image/png" }], "category": "image" }, "external_url": "https://myagent.example.com", "services": [ { "name": "A2A", "endpoint": "https://agent.example/agent-card.json", "version": "0.3.0" }, { "name": "MCP", "endpoint": "https://mcp.agent.example/", "version": "2025-06-18" }, { "name": "agentWallet", "endpoint": "solana:5eykt4...:7S3P4..." } ], "registrations": [ { "agentId": "sati:mainnet:ABC123mint", "agentRegistry": "solana:5eykt4...:satiRkx..." }, { "agentId": 22, "agentRegistry": "eip155:1:0x..." } ], "supportedTrust": ["reputation"], "active": true, "x402Support": true } ``` #### Image Requirements For proper display in Phantom, Solflare, and Solscan: 1. **Use `properties.files`** with explicit MIME type — this is what wallets read for image rendering 2. **Match URIs** — `properties.files[0].uri` should equal `image` field 3. **Supported formats**: `image/png`, `image/jpeg`, `image/gif`, `image/webp`, `image/svg+xml` 4. **Recommended specs**: 512×512 to 1024×1024 pixels, under 1MB **Why both `image` and `properties.files`?** * `image` — ERC-8004 standard field, used by cross-chain consumers * `properties.files` — Metaplex standard, used by Solana wallets for rendering Including both ensures compatibility with both ecosystems. #### Consumer Compatibility | Consumer | Reads | Ignores | |----------|-------|---------| | Phantom/Solflare | name, description, image, properties.files, external\_url | type, services, registrations, supportedTrust, active, x402Support | | Solscan | name, description, image, properties.files | Same as wallets | | ERC-8004 clients | type, name, description, image, services, registrations, supportedTrust | properties, external\_url, active, x402Support | | SATI SDK | All fields | — | Custom fields from either standard are preserved but ignored by consumers that don't understand them. #### Cross-Chain Identity via `registrations[]` The `registrations` array lists all on-chain registrations for the same logical agent, enabling cross-chain identity linking. **Note**: This array can be null or empty when first creating the registration file. The typical workflow is: 1. Create registration file with `registrations: []` 2. Register agent on-chain (returns mint address) 3. Update registration file with actual registration entry 4. (Optional) Call `link_evm_address` to prove EVM address ownership 5. (Optional) Register on additional chains and update file This is necessary because the mint address isn't known until after registration completes. Cross-chain identity enables: * **Same agent identity** across Solana, Ethereum, Base, etc. * **Verifiable cross-chain resolution** — verify registration file hash matches on-chain uri * **No on-chain bridging required** — off-chain linking via content-addressed storage ### SATI Identifier Format ``` sati:: ``` | Format | Example | |--------|---------| | SATI (Solana) | `sati:mainnet:ABC123mintPubkey` | | ERC-8004 (EVM) | `22` (tokenId on specific registry) | | Registry address | `solana:5eykt4...:satiRkx...` (CAIP-10) | See [CAIP Standards](https://github.com/ChainAgnostic/CAIPs) and [ERC-8004](https://eips.ethereum.org/EIPS/eip-8004) for full format specifications. *** ## EVM Address Linking Enables SATI agents to cryptographically prove ownership of EVM addresses via secp256k1 signature verification. This creates verifiable cross-chain identity links. ### Instruction: `link_evm_address` | Field | Type | Description | |-------|------|-------------| | `evm_address` | \[u8; 20] | Ethereum address (20 bytes) | | `chain_id` | String | CAIP-2 chain identifier (e.g., "eip155:1", "eip155:8453") | | `signature` | \[u8; 64] | secp256k1 signature (r || s) | | `recovery_id` | u8 | Recovery ID (0 or 1) | ### Message Format The EVM wallet signs a domain-separated hash: ``` Domain: SATI:evm_link:v1 Hash: keccak256(domain || agent_mint || evm_address || chain_id) ``` ### Verification Flow 1. Client computes message hash with agent mint, EVM address, and chain ID 2. EVM wallet signs the hash (produces 64-byte signature + recovery ID) 3. Call `link_evm_address` instruction with signature 4. Program recovers public key via Solana's `secp256k1_recover` syscall 5. Derive Ethereum address from recovered public key (keccak256, last 20 bytes) 6. Verify recovered address matches provided `evm_address` 7. Emit `EvmAddressLinked` event as proof ### Storage **On-chain (Event):** `EvmAddressLinked` event emitted for indexing. **Off-chain:** Agent updates `registrations[]` in registration file per ERC-8004 convention. ### Use Cases * **ERC-8004 linking**: Prove an ERC-8004 agent (Ethereum) controls a SATI agent (Solana) * **Cross-chain identity**: Verifiable proof of EVM address ownership for any agent * **Multi-chain presence**: Same agent identity across Solana + EVM chains ### Constraints * Agent owner must sign the transaction (holds agent NFT) * One EVM address can link to multiple SATI agents (consistent with ERC-8004) * Multiple chain IDs can be linked per agent (e.g., both Ethereum and Base) *** ## Design Rationale ### Why Agent-Subsidized Feedback? Web2 reviews work because they're **free** (Google, Yelp, Amazon = $0). On-chain costs kill participation. **Solution**: The party who benefits pays. Agents benefit from reputation → agents pay (~$0.002 bundled into service). ### Why Token-2022 for Identity? | Aspect | SAS Attestation | Token-2022 NFT | |--------|-----------------|----------------| | Wallet display | Not shown | Phantom, Solflare | | Transfer | Close + recreate | Standard transfer | | Auto ID | Manual | TokenGroupMember.member\_number | | Collections | None | TokenGroup | ### Why Light Protocol? | Approach | Cost | Indexing | |----------|------|----------| | Regular accounts | ~0.002 SOL | RPC (free) | | Custom merkle | ~0.00001 SOL | Build indexer | | **Light Protocol** | ~0.00001 SOL | **Photon (free)** | No custom indexer needed. ZK proofs enable escrow verification. ### Why Blind Feedback? If agent signs AFTER seeing outcome, they can refuse negative feedback. By signing with response (blind to outcome), agents commit to reputation participation before knowing the sentiment. ### Why Delegation? Hot/cold wallet separation: agent must sign every response (blind feedback), but hot wallet shouldn't control NFT transfers/metadata. Delegation allows hot wallet to sign attestations while cold wallet retains ownership. See DelegateV1 schema for implementation details. *** ## Future Features | Feature | Status | Notes | |---------|--------|-------| | EVM attestation signing | Deferred | secp256k1 signatures for agents and counterparties | | Certification schema | Deferred | Third-party certs when demand exists | | Third-party credentials | Deferred | Platform model when demand exists | | Agent→Agent delegation | Future | New data type for agent hierarchies | | Escrow integration | Future | ZK proofs for automatic release | | Batch reputation updates | Future | Provider updates multiple agents atomically | ### EVM Attestation Signing (Deferred) Enable ERC-8004 agents and clients to sign attestations with their existing EVM wallets (secp256k1), eliminating the need for separate Solana wallets. Vision: * **Agents**: Sign `interaction_hash` with linked EVM address (via EIP-712 typed data) * **Counterparties**: Sign feedback with same wallet used for payment on Base/Ethereum * **Program changes**: Add `SignatureData` enum supporting Ed25519 and Secp256k1 variants, store linked addresses in TokenMetadata, verify secp256k1 via `secp256k1_recover` syscall Will be implemented when ERC-8004 agents express demand for cross-chain signing. ### Certification System (Deferred) Third-party certifications (security audits, compliance, capability verification) are deferred until demand exists. For MVP, certifications can be modeled as ReputationScores where: * Provider = certified auditor/authority * Score = 100 (certified) or 0 (not certified) * content\_ref = link to certificate details When added, Certification may use a new `CredentialAuthority` SignatureMode with whitelisted certifiers, or remain open like ReputationScore with trust delegated to consumers. ### Third-Party Credential System (Deferred) The spec supports external projects registering their own SAS credentials with SATI for permissionless attestation creation and unified indexing. This "SATI as platform" model adds complexity without immediate value — will be added when third parties express demand. *** ## References * [ERC-8004: Trustless Agents](https://eips.ethereum.org/EIPS/eip-8004) * [Token-2022 Program](https://github.com/solana-program/token-2022) * [Solana Attestation Service](https://github.com/solana-foundation/solana-attestation-service) * [Light Protocol](https://github.com/Lightprotocol/light-protocol) * [Light Protocol Docs](https://www.zkcompression.com/) * [Photon RPC](https://docs.helius.dev/compression-and-das-api/photon-api) * [x402 Protocol](https://github.com/coinbase/x402) --- --- url: /advanced/transaction-sizes.md description: Solana transaction size constraints and how SATI optimizes for them --- # Transaction Sizes Understanding Solana's transaction size constraints and how SATI optimizes for them. *** ## Solana Transaction Limits | Limit | Value | Notes | |-------|-------|-------| | **Raw transaction size** | 1232 bytes | Hard limit, cannot be exceeded | | **Base64 encoded size** | 1644 bytes | What RPC APIs report | | **Signatures** | 64 bytes each | Typically 1 payer signature | | **Message header** | 3 bytes | Compact header format | The 1232-byte limit is the primary constraint for SATI attestations. *** ## Transaction Component Breakdown ### DualSignature Attestation (Feedback/Validation) This is the most size-constrained transaction type due to the SIWS message. | Component | Size (bytes) | Notes | |-----------|--------------|-------| | **Transaction header** | ~100 | Signatures, message header, blockhash | | **Ed25519 instruction** | ~504 | See breakdown below | | **CreateAttestation instruction** | ~180-250 | Depends on content size | | **Account addresses** | ~50 (with ALT) | ~500 without ALT | | **Agent ATA** | 32 | User-specific, never in global ALT | | **Light Protocol proof** | ~300 | Validity proof for compressed account | | **Total (typical)** | ~1180 | Leaves ~50-70 bytes for content | ### SingleSignature Attestation (ReputationScore) | Component | Size (bytes) | Notes | |-----------|--------------|-------| | **Transaction header** | ~100 | Same as above | | **Ed25519 instruction** | ~144 | Only interaction hash (32 bytes) | | **CreateAttestation instruction** | ~180-250 | Same | | **Account addresses** | ~50 (with ALT) | Same | | **Agent ATA** | 32 | User-specific, never in global ALT | | **Light Protocol proof** | ~300 | Same | | **Total (typical)** | ~850 | More headroom (~240 bytes for content) | *** ## Ed25519 Instruction Size The Ed25519 precompile instruction verifies signatures. Its size depends on the message being signed. ### Structure ``` Header (2 bytes): - num_signatures: u8 - padding: u8 Per signature (14 bytes offset struct + payload): - signature_offset: u16 - signature_instruction_index: u16 - pubkey_offset: u16 - pubkey_instruction_index: u16 - message_offset: u16 - message_size: u16 - message_instruction_index: u16 Payload per signature: - pubkey: 32 bytes - signature: 64 bytes - message: variable ``` ### DualSignature (2 signatures) | Part | Size | Notes | |------|------|-------| | Header | 2 | num\_signatures + padding | | Offset structs | 28 | 14 bytes × 2 signatures | | Agent pubkey | 32 | Ed25519 public key | | Agent signature | 64 | Ed25519 signature | | Agent message | 32 | `interaction_hash` (Keccak256) | | Counterparty pubkey | 32 | Ed25519 public key | | Counterparty signature | 64 | Ed25519 signature | | Counterparty message | ~250 | SIWS message (variable) | | **Total** | **~504** | | ### SingleSignature (1 signature) | Part | Size | Notes | |------|------|-------| | Header | 2 | | | Offset struct | 14 | | | Pubkey | 32 | | | Signature | 64 | | | Message | 32 | `interaction_hash` | | **Total** | **~144** | | *** ## SIWS Message Format The counterparty signs a human-readable "Sign-In With Solana" style message: ``` SATI Feedback Agent: 7HCCiuYUHptR1SXXHBRqkKUPb5G3hPvnKfy5v8n2cFmY Task: 3Dq8YxkzJ2mVvJf5RzDqNPPq8TmDvU9YzKDsQfLPJYvN Outcome: Positive Details: (none) Sign to create this attestation. ``` ### SIWS Message Size Calculation | Field | Size | Notes | |-------|------|-------| | Schema header | ~20 | "SATI Feedback\n\n" | | Agent line | ~53 | "Agent: " + 44 (base58 pubkey) + "\n" | | Task line | ~51 | "Task: " + 44 (base58 hash) + "\n" | | Outcome line | ~18 | "Outcome: Positive\n" | | Details line | ~17 | "Details: (none)\n" | | Footer | ~35 | "\nSign to create this attestation." | | **Total (no content)** | **~194** | Minimum SIWS message | | **With content** | ~194 + N | N = content string length | **Important**: The SIWS message includes the `Details` field which shows attestation content. Larger content = larger SIWS message = larger transaction. *** ## Address Lookup Tables (ALT) ALTs are **required** for DualSignature attestations to fit within the transaction size limit. ### How ALTs Work * Without ALT: Each account address = 32 bytes * With ALT: Each account address = 1 byte (index into table) * **Savings**: 31 bytes per address ### Required Addresses in ALT A typical DualSignature attestation needs ~20 addresses: | Category | Count | Without ALT | With ALT | |----------|-------|-------------|----------| | Light Protocol PDAs | 8 | 256 bytes | 8 bytes | | SATI program + PDAs | 4 | 128 bytes | 4 bytes | | Token-2022 + agent ATA | 2 | 64 bytes | 2 bytes | | System programs | 4 | 128 bytes | 4 bytes | | Schema config PDA | 1 | 32 bytes | 1 byte | | **Total** | **~19** | **~608 bytes** | **~19 bytes** | **Savings: ~589 bytes** — this is why ALTs are mandatory for DualSignature. ### What Must Be in the ALT For transactions to work, these addresses **must** be in the lookup table: 1. **Schema Config PDA** — Derived from the SAS schema address 2. **Agent ATA** — The agent's Token-2022 associated token account 3. **Light Protocol PDAs** — State trees, nullifier queues, cpiSigner, etc. 4. **System Programs** — Ed25519, Token-2022, System Program, etc. If any required address is missing, the transaction will exceed the size limit. *** ## Maximum Attestation Content Size ### Calculation Starting from the 1232-byte limit: ``` Available for content = 1232 - fixed_overhead - variable_overhead Fixed overhead (~882 bytes): - Transaction header: 100 - Ed25519 base (without message): 254 - CreateAttestation base: 100 - Account addresses (with ALT): 50 - Agent ATA (NEVER in ALT): 32 ← User-specific, unavoidable - Light Protocol proof: 300 - Misc (blockhash, etc.): 46 Variable overhead: - SIWS message base: ~194 bytes - Content appears TWICE: in data blob AND in SIWS message ``` **Note**: The agent's ATA (Associated Token Account) is derived from the agent's mint and owner. Since every user has a unique agent, their ATA can never be in a global lookup table. This adds 32 bytes of unavoidable overhead to every transaction. ### Maximum Content by Mode | Mode | SIWS Message | Max Content | Notes | |------|--------------|-------------|-------| | **DualSignature** | Yes (~194 + content) | **~75 bytes** | Content counted twice | | **SingleSignature** | No | **~250 bytes** | More headroom | ### DualSignature Content Limit Derivation ``` 1232 - 882 (fixed) = 350 bytes remaining 350 bytes must fit: - SIWS base message: 194 bytes - Content in SIWS: N bytes - Content in data blob: N bytes 350 = 194 + N + N 350 = 194 + 2N 2N = 156 N = 78 bytes Safe maximum: ~75 bytes (with margin) ``` ### SingleSignature Content Limit ``` 1232 - 882 (fixed) = 350 bytes remaining No SIWS message, so: - Content in data blob only: N bytes Safe maximum: ~250 bytes (with margin for proof variance) ``` *** ## Practical Recommendations ### For SDK Users 1. **Always use Address Lookup Tables** for DualSignature attestations 2. **Keep content under 70 bytes** for DualSignature mode (safe margin) 3. **Use ContentType.IPFS or ContentType.Arweave** for large content (store hash only) 4. **Pre-compute lookup table addresses** before transaction building ### SDK Constants and Validation The SDK provides constants and validation functions to enforce content size limits: ```typescript import { MAX_DUAL_SIGNATURE_CONTENT_SIZE, // 70 bytes MAX_COUNTERPARTY_SIGNED_CONTENT_SIZE, // 100 bytes MAX_AGENT_OWNER_SIGNED_CONTENT_SIZE, // 240 bytes getMaxContentSize, validateContentSize, SignatureMode, } from "@cascade-fyi/sati-sdk"; // Get max content size for a mode const maxDual = getMaxContentSize(SignatureMode.DualSignature); // 70 const maxCounterparty = getMaxContentSize(SignatureMode.CounterpartySigned); // 100 const maxOwner = getMaxContentSize(SignatureMode.AgentOwnerSigned); // 240 // Validate content before building transaction const content = new TextEncoder().encode('{"value":85}'); // Option 1: Throws on error (default) validateContentSize(content, SignatureMode.DualSignature); // Option 2: Returns result without throwing const result = validateContentSize(content, SignatureMode.DualSignature, { throwOnError: false }); if (!result.valid) { console.log(`Content too large: ${result.actualSize}/${result.maxSize} bytes`); console.log(result.error); // Includes suggestion to use IPFS/Arweave } ``` **Note**: `createFeedback()`, `buildFeedbackTransaction()`, and `createValidation()` automatically validate content size and throw an error if the limit is exceeded. ### For Content Design | Content Size | Recommendation | |--------------|----------------| | < 70 bytes | Store directly (JSON, UTF-8) for DualSignature | | < 100 bytes | Store directly for CounterpartySigned | | < 240 bytes | Store directly for AgentOwnerSigned | | > 240 bytes | Must use IPFS/Arweave reference | ### Content Examples That Fit **DualSignature (< 70 bytes):** ```json {"value":85,"tag1":"helpful"} ``` **CounterpartySigned (< 100 bytes):** ```json {"value":85,"valueDecimals":0,"tag1":"quality","tag2":"latency"} ``` **AgentOwnerSigned (< 240 bytes):** ```json {"score":92,"methodology":"weighted_average","feedbackCount":42,"validationCount":5} ``` *** ## Troubleshooting ### "Transaction too large" Error **Cause**: Transaction exceeds 1232 bytes **Solutions**: 1. Ensure lookup table contains all required addresses 2. Reduce content size 3. Use IPFS/Arweave for content, store only the hash ### "Invalid lookup table index" Error **Cause**: Transaction references an address by ALT index, but the address isn't in the table **Solutions**: 1. Verify the lookup table contains the schema config PDA 2. Verify the lookup table contains the agent's ATA 3. Recreate lookup table with all required addresses ### Size Debugging To debug transaction size, serialize and measure: ```typescript const txBytes = transaction.serialize(); console.log(`Transaction size: ${txBytes.length} bytes`); // Must be <= 1232 ``` *** ## References * [Solana Transaction Size Limits](https://solana.com/docs/core/transactions#transaction-size) * [Address Lookup Tables](https://solana.com/docs/advanced/lookup-tables) * [Ed25519 Instruction Format](https://docs.solanalabs.com/runtime/programs#ed25519-program) * [SATI Specification](/specification) --- --- url: /guides/x402-feedback.md description: Link feedback directly to x402 payment transactions --- # x402 Payment Feedback ::: warning Coming Soon This guide is under active development. SATI is designed as the feedback extension for [x402](https://x402.org) payments (see [PR #1024](https://github.com/coinbase/x402/pull/1024)), but the end-to-end integration guide is not yet ready. ::: ## What This Will Cover * Linking feedback attestations to x402 payment transaction hashes * The payment-as-taskRef model: the payment tx becomes the deterministic reference for feedback * Who pays for on-chain submission (agent pays for positive, client pays for negative) * Integrating SATI feedback into x402 seller and buyer flows ## In the Meantime * Read [How It Works](/how-it-works) to understand blind feedback and proof of participation * See the [Agent Marketplace](/guides/agent-marketplace) guide for the general feedback flow * Check the [sati-agent0-sdk reference](/reference/sati-agent0-sdk) for `giveFeedback` and `prepareFeedback` API details * Follow progress on [GitHub](https://github.com/cascade-protocol/sati)