Skip to content
Back to skills

sati-sdk

ASecurity

Build with SATI (Solana Agent Trust Infrastructure) - on-chain agent identity, verifiable reputation, and blind feedback on Solana. Use when registering AI agents on-chain via Token-2022 NFTs, giving or searching feedback, querying agent reputation, building registration files (ERC-8004), encrypting attestation content, or integrating SATI into TypeScript/Node.js projects. Covers: CLI onboarding (create-sati-agent), agent registration, feedback (give/search), reputation summaries, agent searc...

  • 18 stars
  • 0 votes
  • 0 copies
  • 5 views
  • Added May 26, 2026
blockchaintypescriptrustgobashreactnodegitapifrontenddocumentation

Works with

  • cli
  • api
  • mcp

Security analysis

A92/100
  • mediumUses curl or wget to download content
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 19 files and shows the line behind each finding

Scanned May 27, 2026

npx -y skills add cascade-protocol/sati --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of sati-sdk?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for sati-sdk
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/cascade-protocol-sati-sdk/badge)](https://www.skillsdirectory.com/skills/cascade-protocol-sati-sdk)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: sati-sdk
description: "Build with SATI (Solana Agent Trust Infrastructure) - on-chain agent identity, verifiable reputation, and blind feedback on Solana. Use when registering AI agents on-chain via Token-2022 NFTs, giving or searching feedback, querying agent reputation, building registration files (ERC-8004), encrypting attestation content, or integrating SATI into TypeScript/Node.js projects. Covers: CLI onboarding (create-sati-agent), agent registration, feedback (give/search), reputation summaries, agent search/discovery, validation attestations, EVM address linking, content encryption, and metadata uploading. Triggers on SATI, sati-sdk, create-sati-agent, agent registration solana, agent reputation, blind feedback, compressed attestation, Light Protocol attestation, ERC-8004 registration file, agent identity NFT, register agent CLI."
---

# SATI

Solana Agent Trust Infrastructure. Agents get Token-2022 NFT identities, accumulate verifiable feedback via ZK-compressed attestations (Light Protocol), and can be discovered on-chain.

Program ID (all networks): `satiRkxEiwZ51cv8PRu8UMzuaqeaNU9jABo6oAFMsLe`

## Quick Start (CLI)

Fastest path - zero to registered agent in ~5 minutes:

```bash
npx create-sati-agent init      # Creates agent-registration.json + keypair
# Edit agent-registration.json with your agent details
npx create-sati-agent publish    # Publishes to devnet (free, auto-funded)
```

Mainnet:

```bash
npx create-sati-agent publish --network mainnet  # ~0.003 SOL
```

All commands: `init`, `publish`, `search`, `info [MINT]`, `give-feedback`, `transfer <MINT>`. All support `--help`, `--json`, `--network devnet|mainnet`.

### agent-registration.json

The registration file follows the [ERC-8004 Registration standard](https://github.com/erc-8004/best-practices/blob/main/Registration.md):

```json
{
  "type": "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
  "name": "MyAgent",
  "description": "AI assistant that does X for Y",
  "image": "https://example.com/avatar.png",
  "properties": {
    "files": [{"uri": "https://example.com/avatar.png", "type": "image/png"}],
    "category": "image"
  },
  "services": [
    {
      "name": "MCP",
      "endpoint": "https://myagent.com/mcp",
      "version": "2025-06-18",
      "mcpTools": ["search", "summarize", "analyze"],
      "mcpPrompts": ["data-analysis"],
      "mcpResources": ["knowledge-base"]
    },
    {
      "name": "A2A",
      "endpoint": "https://myagent.com/.well-known/agent-card.json",
      "a2aSkills": ["natural_language_processing/information_retrieval_synthesis/question_answering"]
    }
  ],
  "supportedTrust": ["reputation"],
  "active": false,
  "x402Support": false,
  "registrations": []
}
```

Service types (see [ERC-8004 best practices](https://github.com/erc-8004/best-practices) for detailed guidance):
- `MCP` - Model Context Protocol. Fields: `mcpTools` (tool names as strings), `mcpPrompts`, `mcpResources`. The `version` field is the MCP spec version your server supports (e.g., `"2025-06-18"`).
- `A2A` - Agent-to-Agent. Fields: `a2aSkills` (OASF skill paths). Endpoint should point to your agent card JSON.
- `OASF` - Open Agent Skills Framework. Fields: `skills`, `domains`.
- `ENS`, `DID`, `agentWallet` - Identity services.

> **Note:** When publishing via CLI (`npx create-sati-agent publish`), the CLI auto-discovers MCP tools by calling your MCP endpoint. Your MCP server must be running and reachable during publish. If your server requires auth, you'll see a non-blocking reachability warning - you can safely ignore it and list tools manually in the JSON.

### Mainnet deployment flow

```bash
npx create-sati-agent init                          # 1. Create template + keypair
npx create-sati-agent publish                        # 2. Test on devnet (free, default)
npx create-sati-agent info <MINT> --network devnet   # 3. Verify
npx create-sati-agent publish --network mainnet      # 4. Go live (~0.003 SOL)
npx create-sati-agent transfer <MINT> \
  --new-owner <SECURE_WALLET> --network mainnet      # 5. Move to hardware wallet
```

### CLI feedback

```bash
npx create-sati-agent give-feedback \
  --agent <MINT> --tag1 starred --value 85 --network mainnet
```

Feedback tag conventions:

| tag1 | value range | meaning |
|------|-------------|---------|
| `starred` | 0-100 | Overall rating |
| `reachable` | 0 or 1 | Health check (1 = reachable) |
| `uptime` | 0-100 | Uptime percentage |
| `responseTime` | ms | Latency in milliseconds |
| `successRate` | 0-100 | Success percentage |

### Monitoring agent health

Automate health checks with a cron job or scheduled task:

```bash
# Check if endpoint is reachable and report to SATI
curl -sf https://myagent.com/mcp > /dev/null && \
  npx create-sati-agent give-feedback --agent <MINT> --tag1 reachable --value 1 --network mainnet || \
  npx create-sati-agent give-feedback --agent <MINT> --tag1 reachable --value 0 --network mainnet
```

### Reputation badge

Add a reputation badge to your README:

```markdown
![SATI Reputation](https://sati.cascade.fyi/api/badge/<YOUR_MINT>?network=mainnet)
```

Or link to your dashboard page:

```markdown
[Reputation](https://sati.cascade.fyi/agent/<YOUR_MINT>)
```

---

## SDK (Programmatic)

`@cascade-fyi/sati-sdk` is the primary SDK for all SATI integrations.

> **Building a read-only integration?** For explorers, dashboards, and data ingestion, the [REST API](#rest-api) requires no wallet or Solana dependencies. Use the SDK only when you need to write on-chain (register agents, give feedback, publish scores).

```bash
npm install @cascade-fyi/sati-sdk
# Peer deps:
npm install @solana/kit @solana-program/token-2022
```

### Initialize

```typescript
import { Sati, createSatiUploader, address } from "@cascade-fyi/sati-sdk";
import { createKeyPairSignerFromBytes } from "@solana/kit";

const sati = new Sati({ network: "mainnet" });
// Options: network, rpcUrl, wsUrl, photonRpcUrl, onWarning, transactionConfig, feedbackCacheTtlMs
```

Load a wallet:

```typescript
import { readFileSync } from "node:fs";
const bytes = new Uint8Array(JSON.parse(readFileSync("wallet.json", "utf8")));
const payer = await createKeyPairSignerFromBytes(bytes);
```

### 1. Register an Agent

#### Quick (fluent builder)

```typescript
const builder = sati.createAgentBuilder("MyAgent", "AI assistant", "https://example.com/avatar.png");
builder
  .setMCP("https://mcp.example.com", "2025-06-18", { tools: ["search"] })
  .setA2A("https://a2a.example.com/.well-known/agent-card.json")
  .setX402Support(true)
  .setActive(true);

const result = await builder.register({
  payer,
  uploader: createSatiUploader(), // Zero-config IPFS upload
});
// result.mint - agent NFT address, result.memberNumber, result.signature
```

#### Direct

```typescript
import { buildRegistrationFile, createSatiUploader } from "@cascade-fyi/sati-sdk";

const regFile = buildRegistrationFile({
  name: "MyAgent",
  description: "AI assistant",
  image: "https://example.com/avatar.png",
  services: [{ name: "MCP", endpoint: "https://mcp.example.com" }],
  active: true,
});

const uploader = createSatiUploader();
const uri = await uploader.upload(regFile);

const result = await sati.registerAgent({
  payer,
  name: "MyAgent",
  uri,
  nonTransferable: false, // default: false. Set true for soulbound (non-transferable) agents.
});
```

Uploaders: `createSatiUploader()` (zero-config, uses hosted IPFS via `sati.cascade.fyi`) or `createPinataUploader(jwt)`.

### 2. Give Feedback

#### Public feedback (simple)

`giveFeedback` uses the **FeedbackPublicV1** schema (CounterpartySigned mode) - the reviewer signs and submits in one call. No agent co-signature required.

```typescript
import { Outcome } from "@cascade-fyi/sati-sdk";

const { signature, attestationAddress } = await sati.giveFeedback({
  payer,                              // Reviewer wallet (pays + signs)
  agentMint: address("Agent..."),     // Agent to review
  outcome: Outcome.Positive,          // Positive | Negative | Neutral (default: Neutral)
  value: 87,                          // Numeric score (optional)
  valueDecimals: 0,                   // Decimal places for value
  tag1: "starred",                    // Primary dimension
  tag2: "chat",                       // Secondary dimension (optional)
  message: "Great response time",     // Human-readable (optional)
  endpoint: "https://agent.example",  // Endpoint reviewed (optional)
  taskRef: txHashBytes,               // 32-byte task reference (optional, e.g. payment tx hash)
});
```

> **x402 payment linking:** The `taskRef` field accepts a 32-byte reference to link feedback to a specific transaction. x402 integration details (converting tx signatures to 32-byte refs, querying feedback by payment) are under active development.

#### Blind feedback (dual-signature)

For proof-of-participation, use the **FeedbackV1** schema (DualSignature mode). The agent signs a blind commitment *before* knowing the outcome. Use the lower-level `createFeedback()` method with both `agentSignature` and `counterpartyMessage`. See the specification for the full blind feedback flow.

> **Note:** For most integrations, `FeedbackPublicV1` (single-signer via `giveFeedback`) is sufficient. Blind feedback requires agent-side signing integration and is primarily for proof-of-participation use cases where you need cryptographic evidence that the agent participated in the interaction.

#### Browser wallet flow (two-step)

The platform server prepares a SIWS (Sign In With Solana) message, the user signs it in their browser wallet, and the platform submits the transaction.

Uses `@solana/wallet-adapter-react` (works with Phantom, Solflare, Backpack, and any wallet implementing the Wallet Standard `signMessage` feature).

```bash
npm install @solana/wallet-adapter-react @solana/wallet-adapter-wallets @solana/wallet-adapter-react-ui
```

**Server (API route):**

```typescript
import { Sati, Outcome, address, bytesToHex, hexToBytes } from "@cascade-fyi/sati-sdk";

const sati = new Sati({ network: "mainnet" });

// POST /api/prepare-feedback
async function handlePrepare(req) {
  const { walletAddress, agentMint, value, tag1, outcome } = req.body;

  const prepared = await sati.prepareFeedback({
    counterparty: address(walletAddress),
    agentMint: address(agentMint),
    outcome: outcome ?? Outcome.Positive,
    value,
    tag1,
  });

  // Store `prepared` server-side (e.g. in session or cache keyed by walletAddress + agentMint)
  await cache.set(`feedback:${walletAddress}:${agentMint}`, prepared);

  // Only send the SIWS message bytes to the frontend
  return { messageHex: bytesToHex(prepared.messageBytes) };
}

// POST /api/submit-feedback
async function handleSubmit(req) {
  const { walletAddress, agentMint, signatureHex } = req.body;

  const prepared = await cache.get(`feedback:${walletAddress}:${agentMint}`);
  const result = await sati.submitPreparedFeedback({
    payer: platformPayer,
    prepared,
    counterpartySignature: hexToBytes(signatureHex),
  });

  return { signature: result.signature, attestationAddress: result.attestationAddress };
}
```

**Frontend (React component):**

```tsx
import { useWallet } from "@solana/wallet-adapter-react";
import { hexToBytes, bytesToHex } from "@cascade-fyi/sati-sdk";

function FeedbackButton({ agentMint }: { agentMint: string }) {
  const { publicKey, signMessage, connected } = useWallet();

  async function handleFeedback() {
    if (!publicKey || !signMessage) return;

    // 1. Server prepares the SIWS message
    const { messageHex } = await fetch("/api/prepare-feedback", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        walletAddress: publicKey.toBase58(),
        agentMint,
        value: 85,
        tag1: "starred",
      }),
    }).then((r) => r.json());

    // 2. User signs with wallet (Phantom/Solflare popup)
    const messageBytes = hexToBytes(messageHex);
    const signature = await signMessage(messageBytes); // Returns Uint8Array (64-byte Ed25519)

    // 3. Server submits the transaction
    await fetch("/api/submit-feedback", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        walletAddress: publicKey.toBase58(),
        agentMint,
        signatureHex: bytesToHex(signature),
      }),
    });
  }

  return (
    <button onClick={handleFeedback} disabled={!connected}>
      Rate Agent
    </button>
  );
}
```

> **Note:** `signMessage` is `undefined` if the connected wallet doesn't support message signing. Always check `signMessage` before calling it. `PreparedFeedbackData` contains multiple `Uint8Array` fields (`messageBytes`, `taskRef`, `dataHash`, `content`). If you need to serialize the entire object to JSON (e.g. for a stateless API), convert all `Uint8Array` fields with `bytesToHex()` and restore with `hexToBytes()`. The recommended pattern above avoids this by keeping `prepared` server-side.

### 3. Search Feedback

`searchFeedback` queries only the **FeedbackPublicV1** schema. Use `searchAllFeedback` to query both FeedbackPublicV1 and FeedbackV1 (blind) schemas.

```typescript
// Search FeedbackPublicV1 for a specific agent
const feedbacks = await sati.searchFeedback({
  agentMint: address("Agent..."),
  tag1: "starred",
  minValue: 70,
  outcome: Outcome.Positive,  // Filter by outcome (optional)
  includeTxHash: true,
});
// Returns: ParsedFeedback[] with compressedAddress, outcome, value, tag1, tag2, message, createdAt

// Search FeedbackPublicV1 across all agents (omit agentMint)
const allPublic = await sati.searchFeedback({});

// Search BOTH schemas (FeedbackPublicV1 + FeedbackV1)
const combined = await sati.searchAllFeedback({
  agentMint: address("Agent..."),
});
```

To distinguish blind (FeedbackV1) from public (FeedbackPublicV1) in raw results, compare `attestation.sasSchema` against `sati.feedbackSchema` vs `sati.feedbackPublicSchema`.

**Bulk ingestion (for indexers/scoring providers):**

```typescript
import { parseFeedbackContent } from "@cascade-fyi/sati-sdk";

// Auto-paginating async iterator across both schemas
for await (const page of sati.listAllFeedbacks({ agentMint: address("Agent...") })) {
  for (const item of page.items) {
    // item.data: { taskRef, agentMint, counterparty, dataHash, outcome, contentType, content }
    // item.raw.slotCreated (bigint) for on-chain slot
    // item.address is Uint8Array - decode with getAddressDecoder() from @solana/kit:
    // import { getAddressDecoder } from "@solana/kit";
    // const [address] = getAddressDecoder().read(item.address, 0);
    const parsed = parseFeedbackContent(item.data.content, item.data.contentType);
    // parsed: { value, valueDecimals, tag1, tag2, m (message), endpoint, reviewer, feedbackURI, feedbackHash }
  }
}

// Omit agentMint to iterate ALL feedback across all agents
for await (const page of sati.listAllFeedbacks()) { /* ... */ }
```

Note: `searchFeedback`/`searchAllFeedback` return `ParsedFeedback[]` (fully parsed). `listAllFeedbacks` returns raw `ParsedAttestation` pages where content is still bytes - use `parseFeedbackContent(item.data.content, item.data.contentType)` to extract fields. `createdAt` timestamps in `ParsedFeedback` are approximate - derived from Solana slot numbers using ~400ms/slot estimate.

**Incremental sync (scoring providers):** There is no `sinceSlot` filter - Photon RPC does not support slot-range queries on compressed accounts. For incremental updates, track `item.raw.slotCreated` locally and skip items below your last-processed slot on each full fetch. At current volumes this is efficient; for higher scale, use a Solana transaction log indexer (Helius webhooks, Yellowstone gRPC) to stream new attestation events.

### 4. Reputation Summary

```typescript
const summary = await sati.getReputationSummary(address("Agent..."));
// { count: 42, averageValue: 85.3 }

// Filter by tags:
const filtered = await sati.getReputationSummary(address("Agent..."), "starred", "chat");
```

Note: `getReputationSummary` queries both FeedbackPublicV1 and FeedbackV1 schemas. In the SDK, `count` only includes entries with a `value` field (entries without `value` are excluded from both count and average). The REST API differs: its `count` includes all feedback entries regardless of `value`, while `summaryValue` averages only entries that have `value` set. The REST API returns integer `summaryValue`/`summaryValueDecimals` instead of the SDK's float `averageValue`.

### 5. Agent Discovery

```typescript
// Load single agent (on-chain data only - no description, image, or services)
const agent = await sati.loadAgent(address("Mint..."));
// AgentIdentity: { mint, owner, name, uri, memberNumber, nonTransferable }
// For rich metadata (description, image, services, active), fetch the registration file:
const regFile = await fetchRegistrationFile(agent.uri);
// regFile: { name, description, image, services, active, x402Support, supportedTrust, ... }

// Load multiple agents in batch (single batched RPC call)
const agents = await sati.loadAgents([mint1, mint2, mint3]);
// Returns: (AgentIdentity | null)[] - null for invalid/missing mints

// Get agent by member number (1-indexed)
const first = await sati.getAgentByMemberNumber(1n);

// Search agents with filters
const results = await sati.searchAgents({
  endpointTypes: ["MCP"],
  active: true,
  includeFeedbackStats: true,
  limit: 50,
});
// AgentSearchResult[]: { identity, registrationFile, feedbackStats }

// List all agents with pagination (lighter than searchAgents - no registration file fetch)
// Default limit: 100, offset: 0, order: "newest"
const page = await sati.listAllAgents({ limit: 20, offset: 0, order: "newest" });
// { agents: AgentIdentity[], totalAgents: bigint }

// List by owner
const myAgents = await sati.listAgentsByOwner(address("Owner..."));

// Registry stats
const stats = await sati.getRegistryStats();
// { totalAgents, groupMint, authority, isImmutable }
```

### 6. Update Agent Metadata

```typescript
// Via builder
builder.updateInfo({ description: "Updated description" });
builder.setMCP("https://new-mcp.example.com");
await builder.update({ payer, owner: ownerKeypair, uploader: createSatiUploader() });

// Direct
await sati.updateAgentMetadata({
  payer,
  owner: ownerKeypair,
  mint: address("Mint..."),
  updates: { name: "NewName", uri: "ipfs://Qm..." },
});
```

### 7. Link EVM Address

Cross-chain identity linking via secp256k1 signature:

```typescript
await sati.linkEvmAddress({
  payer,
  agentMint: address("Mint..."),
  evmAddress: "0x1234...abcd",
  chainId: "eip155:8453", // Base
  signature: secp256k1Sig, // 64 bytes: r || s
  recoveryId: 0,
});
```

> **Note:** EVM links are stored as Anchor events only (not in on-chain accounts). There is no SDK query method to read past links - you need a Solana transaction log indexer (Helius, Yellowstone) to retrieve them.

### 8. Content Encryption

X25519-XChaCha20-Poly1305 for private feedback:

```typescript
import {
  deriveEncryptionKeypair,
  encryptContent,
  decryptContent,
  serializeEncryptedPayload,
  deserializeEncryptedPayload,
} from "@cascade-fyi/sati-sdk";

// Derive from Ed25519 keypair
const encKeys = deriveEncryptionKeypair(ed25519PrivateKeyBytes);
const encrypted = encryptContent(plaintext, recipientX25519PublicKey);
const bytes = serializeEncryptedPayload(encrypted);
// ... store bytes as attestation content ...
const decrypted = decryptContent(deserializeEncryptedPayload(bytes), recipientPrivateKey);
```

### 9. Registration File (ERC-8004)

```typescript
import {
  buildRegistrationFile,
  validateRegistrationFile,
  fetchRegistrationFile,
  getImageUrl,
} from "@cascade-fyi/sati-sdk";

// Validate untrusted data
const result = validateRegistrationFile(untrustedData);
if (!result.ok) console.error(result.errors);

// Fetch from URI (IPFS/HTTP)
const regFile = await fetchRegistrationFile("ipfs://Qm...");
const imageUrl = getImageUrl(regFile);
```

See the [ERC-8004 registration best practices](https://github.com/erc-8004/best-practices/blob/main/Registration.md) for guidance on name, image, description, and services.

### 10. Reputation Scoring (on-chain)

For scoring providers publishing computed scores back on-chain (ReputationScoreV3 schema):

```typescript
import { ContentType, parseReputationScoreContent } from "@cascade-fyi/sati-sdk";

// Publish/update a score (idempotent - closes existing + creates new in one tx)
await sati.updateReputationScore({
  payer,
  provider: providerKeypair,     // Scoring provider's KeyPairSigner
  sasSchema: sati.reputationScoreSchema,
  satiCredential: sati.credential,
  agentMint: address("Agent..."),
  outcome: Outcome.Positive,
  contentType: ContentType.JSON,
  content: new TextEncoder().encode(JSON.stringify({ score: 85, factors: { ... } })),
});

// Read existing scores for an agent
const scores = await sati.listReputationScores(
  address("Agent..."),
  sati.reputationScoreSchema,
);
for (const score of scores) {
  const parsed = parseReputationScoreContent(score.content, score.contentType);
  // parsed: { score, factors, ... }
}

// Get a specific provider's score
const score = await sati.getReputationScore(
  address("Provider..."),
  address("Agent..."),
  sati.credential,
  sati.reputationScoreSchema,
);
```

### Platform integration notes

**Ownership model:** `registerAgent({ payer, owner })` - the `payer` pays gas, the `owner` receives the NFT. A platform can register agents on behalf of operators. Only the `owner` can update metadata. Reputation stays with the mint address (portable across owners).

**Outcome enum values:** `Negative = 0`, `Neutral = 1`, `Positive = 2`. Use `getOutcomeLabel(outcome)` for display strings.

### REST API

The dashboard at `sati.cascade.fyi` exposes a public REST API. See the [REST API reference](https://github.com/cascade-protocol/sati/blob/main/docs/reference/rest-api.md) for full endpoint documentation. Key endpoints:

- `GET /api/agents` - list/search agents (supports `name`, `owner`, `endpointTypes`, `order`, pagination)
- `GET /api/agents/:mint` - single agent with reputation summary
- `GET /api/feedback/:mint` - feedback for an agent (paginated with `limit`/`offset`)
- `GET /api/feedback` - global feedback across all agents (paginated)
- `GET /api/reputation/:mint` - reputation summary with tag/reviewer filters
- `GET /api/stats` - registry statistics (`totalAgents`, `groupMint`, etc.)
- `GET /api/scores/:mint` - reputation scores from scoring providers (ReputationScoreV3)
- `GET /api/badge/:mint` - SVG reputation badge for README embedding
- `POST /api/feedback` - submit feedback without a wallet (server acts as counterparty, rate limited per IP)

The agents list supports `includeReputation=true` to get reputation inline per agent (slower but avoids N+1 requests). Filter params like `endpointTypes` are case-sensitive (use `MCP`, not `mcp`).

> **SDK ↔ REST API field mapping:** `counterparty` (SDK) = `clientAddress` (REST). `averageValue` (SDK, float) = `summaryValue`/`summaryValueDecimals` (REST, integer). Outcome: `Outcome.Positive` = `2`, `Outcome.Neutral` = `1`, `Outcome.Negative` = `0`. Use `getOutcomeLabel(outcome)` in SDK for display strings.

> **Note:** EVM address links (from `linkEvmAddress`) are not queryable via REST API - they are stored as Anchor events only. Retrieving them requires a Solana transaction log indexer (Helius webhooks, Yellowstone gRPC).

### Configuration

```typescript
const sati = new Sati({
  network: "mainnet",           // "mainnet" | "devnet" | "localnet"
  rpcUrl: "https://...",        // Custom Solana RPC (optional)
  photonRpcUrl: "https://...",  // Photon/Helius RPC for Light Protocol queries (optional)
  onWarning: (w) => console.warn(w.code, w.message),
  feedbackCacheTtlMs: 30_000,  // Cache TTL (default 30s, 0 to disable)
  transactionConfig: {
    priorityFeeMicroLamports: 50_000, // Default on mainnet
    computeUnitLimit: 400_000,
    maxRetries: 2,                    // Blockhash expiration retries
  },
});
```

**RPC endpoints**: By default, the SDK routes all RPC calls through hosted proxies at `sati.cascade.fyi` (backed by Helius), rate-limited to ~120 req/min per IP. For production workloads, provide your own Helius or Triton RPC URLs via `rpcUrl` and `photonRpcUrl` to get higher limits.

### Key Types

| Type | Description |
|------|-------------|
| `AgentIdentity` | On-chain agent: mint, owner, name, uri, memberNumber (`bigint`), nonTransferable, additionalMetadata |
| `RegistrationFile` | ERC-8004 metadata with services, trust mechanisms |
| `GiveFeedbackParams` | Simplified feedback input (FeedbackPublicV1) |
| `ParsedFeedback` | Feedback with value, tags, message, createdAt, counterparty |
| `FeedbackContent` | Raw content: value, valueDecimals, tag1, tag2, m (message), endpoint, reviewer, feedbackURI, feedbackHash |
| `ReputationSummary` | Aggregated count + averageValue |
| `AgentSearchResult` | Identity + registrationFile + optional feedbackStats |
| `Outcome` | Enum: Positive (2), Negative (0), Neutral (1) |
| `MetadataUploader` | Interface for pluggable storage (IPFS, Arweave, etc.) |

### Error Handling

```typescript
import { SatiError, DuplicateAttestationError, AgentNotFoundError } from "@cascade-fyi/sati-sdk";

try {
  await sati.giveFeedback(params);
} catch (e) {
  if (e instanceof DuplicateAttestationError) {
    // Same taskRef + counterparty + agent already exists
  }
}
```

## Costs

| Operation | Cost |
|-----------|------|
| Agent registration | ~0.003 SOL |
| Agent transfer | ~0.0005 SOL |
| Feedback attestation | ~0.00001 SOL (compressed) |
| Reputation score (ReputationScoreV3) | ~0.002 SOL (regular SAS, not compressed) |
| Devnet | Free (auto-funded faucet) |

## Common Issues

- **Blockhash expired** - Solana transactions must land within ~60 seconds. Retry the command/call.
- **Insufficient funds (mainnet)** - Send ~0.01 SOL to your wallet address. CLI shows the address on failure.
- **Permission denied on update** - Wrong keypair. Use `--keypair /path/to/original.json` with the CLI, or ensure the correct `owner` KeyPairSigner in SDK.
- **Feedback schema not deployed** - Make sure you're on the right network. Schemas are deployed on both devnet and mainnet.
- **Rate limited (429)** - The hosted RPC proxies are rate-limited to ~120 req/min per IP. For production, provide your own RPC via `rpcUrl` and `photonRpcUrl`.

Files in this skill

  • .nvmrc8 B
  • .size-limit.json150 B
  • Anchor.toml510 B
  • CONTRIBUTING.md5.6 KB
  • Cargo.toml574 B
  • NOTICE121 B
  • SECURITY.md1.7 KB
  • SKILL.md25.9 KB
  • biome.json1.3 KB
  • docs/erc-8004.md10.2 KB
  • docs/getting-started.md5.3 KB
  • docs/how-it-works.md7.4 KB
  • docs/index.md2.2 KB
  • docs/package.json255 B
  • docs/specification.md67.2 KB
  • examples/.env.example819 B
  • examples/biome.json154 B
  • examples/package.json364 B
  • examples/tsconfig.json237 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.