# SDK Reference

Package: `@valiron/sdk`

## Installation

```bash
npm install @valiron/sdk
# or
pnpm add @valiron/sdk
```

## Constructor

```typescript
import { ValironSDK } from "@valiron/sdk";

const valiron = new ValironSDK(config?: ValironConfig);
```

### ValironConfig

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `apiKey` | string | — | API key (optional for read operations) |
| `endpoint` | string | `https://valiron-edge-proxy.onrender.com` | API base URL |
| `chain` | SupportedChain | `"ethereum"` | Default blockchain network |
| `timeout` | number | method-aware | Request timeout in milliseconds. Defaults are 10s generally, 15s for `gate()`/`proxy()`, and 30s for sandbox triggers. If set, this value overrides all method-aware defaults. |
| `debug` | boolean | `false` | Enable debug logging |
| `telemetry` | TelemetryConfig | — | Optional telemetry configuration |

### SupportedChain

```typescript
type SupportedChain =
  | "ethereum" | "monad" | "arbitrum" | "base" | "avalanche"
  | "celo" | "polygon" | "linea" | "abstract" | "bsc"
  | "gnosis" | "goat" | "mantle" | "megaeth" | "metis"
  | "optimism" | "scroll" | "skale_base" | "soneium"
  | "taiko" | "xlayer" | "base_sepolia" | "ethereum_sepolia"
  | "abstract_testnet" | "arbitrum_sepolia" | "avalanche_testnet"
  | "bsc_testnet" | "celo_testnet" | "linea_sepolia"
  | "mantle_testnet" | "megaeth_testnet" | "metis_sepolia"
  | "optimism_sepolia" | "polygon_amoy" | "scroll_sepolia"
  | "skale_base_sepolia" | "soneium_minato" | "taiko_hoodi"
  | "xlayer_testnet" | "hedera_testnet" | "arc_testnet"
  | "solana";
```

> **Solana:** When using `chain: "solana"`, agent IDs are base-58 Metaplex Core
> asset pubkeys (not numeric). Wallet addresses are also base-58.
> See [CHAINS.md](./CHAINS.md#solana) for details.

---

## Methods

### checkAgent(agentId, options?)

Quick routing check. Returns only the route decision string.

```typescript
const route = await valiron.checkAgent(agentId: string, options?: {
  chain?: SupportedChain;
}): Promise<RouteDecision>;
```

**Returns**: `"prod"` | `"prod_throttled"` | `"sandbox"` | `"sandbox_only"`

**Example**:
```typescript
const route = await valiron.checkAgent("25459");
if (route === "prod") {
  // Agent is trusted — proceed with full access
}
```

**Solana example**:
```typescript
const valiron = new ValironSDK({ chain: "solana" });

// By asset pubkey
const route = await valiron.checkAgent("DFkntsJ9aTCHkK88m6WZVQEvLdLNi4X1LVUgRMqetFPM");

// By sequential ID
const route = await valiron.checkAgent("1226");
```

---

### getAgentProfile(agentId, options?)

Full trust profile combining on-chain identity, reputation, and routing.

```typescript
const profile = await valiron.getAgentProfile(agentId: string, options?: {
  chain?: SupportedChain;
}): Promise<AgentProfile>;
```

**Returns**: `AgentProfile`

```typescript
interface AgentProfile {
  agentId: string;
  identity: AgentIdentity;
  onchainReputation: OnchainReputation;
  routing: {
    finalRoute: RouteDecision;
    meetsThreshold: boolean;
  };
  chain: ChainContext;
  timestamp: string;
}
```

**Example**:
```typescript
const profile = await valiron.getAgentProfile("25459");
console.log(profile.routing.finalRoute);       // "prod"
console.log(profile.routing.meetsThreshold);   // true
console.log(profile.onchainReputation.averageScore); // 92.5
```

**Solana example**:
```typescript
const valiron = new ValironSDK({ chain: "solana" });
const profile = await valiron.getAgentProfile("1226"); // sequential ID works

console.log(profile.identity.name);                      // "NoahScout_Bot.noah"
console.log(profile.onchainReputation.solana?.trustTier); // "trusted"
console.log(profile.onchainReputation.solana?.qualityScore); // 85
```

---

### getWalletProfile(wallet, options?)

Reverse-lookup a wallet address to get its trust profile.

```typescript
const profile = await valiron.getWalletProfile(wallet: string, options?: {
  chain?: SupportedChain;
}): Promise<WalletProfile>;
```

**Example**:
```typescript
const profile = await valiron.getWalletProfile("0x52ce...");
console.log(profile.routing.finalRoute);
```

**Solana example**:
```typescript
const valiron = new ValironSDK({ chain: "solana" });
const profile = await valiron.getWalletProfile("VSAnVX4MQjEXreHc92iHxWuXPQZ1tjwiKccpFLCgLM9");
```

---

### resolveWallet(wallet, options?)

Lightweight wallet → agentId resolution without the full trust profile. Useful when you only need to know which agent owns a wallet (e.g. after extracting a wallet from an x402 payment header).

Resolution chain: Redis → in-memory cache → Agent0 subgraph → null.

Signature: `resolveWallet(wallet: string, options?: { chain?: SupportedChain }): Promise<WalletResolution>`

**Returns**: `WalletResolution`

```typescript
interface WalletResolution {
  wallet: string;
  agentId: string | null;
  source: "redis" | "subgraph" | "cache" | "none";
  chainId?: number | null;
  agentName?: string | null;
  timestamp: string;
}
```

**Example**:
```typescript
const resolution = await valiron.resolveWallet("0x52ce...");
if (resolution.agentId) {
  const profile = await valiron.getAgentProfile(resolution.agentId);
}
```

**Solana example**:
```typescript
const valiron = new ValironSDK({ chain: "solana" });
const resolution = await valiron.resolveWallet("VSAnVX4MQjEXreHc92iHxWuXPQZ1tjwiKccpFLCgLM9");
// resolution.source will be "solana" instead of "redis" | "subgraph"
```

---

### triggerSandboxTest(agentId, options?)

Run sandbox tests against an agent and compute a trust evaluation.

```typescript
const result = await valiron.triggerSandboxTest(agentId: string, options?: {
  chain?: SupportedChain;
}): Promise<SandboxResult>;
```

**Returns**: `SandboxResult`

```typescript
interface SandboxResult {
  ok: boolean;
  agentId: string;
  wallet: string;
  tier: MoodysRating;          // "AAA" | "AA" | "A" | "BAA" | "BA" | "B" | "CAA" | "CA" | "C"
  riskLevel: "GREEN" | "YELLOW" | "RED";
  meetsThreshold: boolean;
  testSummary: object;         // Opaque test metrics (structure is proprietary)
  chain: ChainContext;
  message: string;
}
```

**Example**:
```typescript
const result = await valiron.triggerSandboxTest("25459");
console.log(result.tier);            // "AAA"
console.log(result.riskLevel);       // "GREEN"
console.log(result.meetsThreshold);  // true
```

> **Auto-sandbox:** The middleware (`createValironGate`, `valironFastifyPlugin`, `createValironNextMiddleware`, `valironGateCheck`) automatically triggers `triggerSandboxTest()` in the background for new on-chain agents (those with `totalFeedback === 0`). The agent receives a 403 with `{ error: "Agent pending evaluation", retryAfterMs: 30000 }` while evaluation runs. No manual trigger is needed.

---

### triggerKeyAgentSandbox(agentAddress)

Run sandbox tests against a key-based (Web2) agent and compute a trust evaluation. Key agents don't have on-chain endpoints, so this uses the `sandbox_relay` mode internally.

```typescript
const result = await valiron.triggerKeyAgentSandbox(agentAddress: string): Promise<KeyAgentSandboxResult>;
```

**Returns**: `KeyAgentSandboxResult`

```typescript
interface KeyAgentSandboxResult {
  ok: boolean;
  agentAddress: string;
  valironScore: number;
  tier: MoodysRating;
  riskLevel: "GREEN" | "YELLOW" | "RED";
  message: string;
}
```

**Example**:
```typescript
const result = await valiron.triggerKeyAgentSandbox("0x1234...abcd");
console.log(result.tier);            // "A"
console.log(result.riskLevel);       // "GREEN"
console.log(result.valironScore);    // 85
```

> **Auto-sandbox:** The middleware automatically triggers `triggerKeyAgentSandbox()` in the background for new key agents (those with `verified === true` but `score === null`). The agent receives a 403 with `{ error: "Agent pending evaluation", retryAfterMs: 30000 }` while evaluation runs.

---

### gate(agentId, options?)

Trust gate — single call for allow/deny decisions. Combines on-chain reputation and sandbox tests. Designed for payment protocol protection (x402, etc.).

```typescript
const result = await valiron.gate(agentId: string, options?: {
  ttlMs?: number;       // default: 86400000 (24h)
  chain?: SupportedChain;
}): Promise<GateResult>;
```

**Returns**: `GateResult`

```typescript
interface GateResult {
  allow: boolean;
  tier: MoodysRating;
  riskLevel: "GREEN" | "YELLOW" | "RED";
  meetsThreshold: boolean;
  route: RouteDecision;
  agentId: string;
  wallet: string;
  chain: ChainContext;
  sandboxRan: boolean;
  cached: boolean;
}
```

**Example**:
```typescript
const result = await valiron.gate("25459");

if (result.allow) {
  // proceed with payment
} else {
  console.log(`Agent denied — tier: ${result.tier}, risk: ${result.riskLevel}`);
}
```

---

### getAgentSnapshot(agentId, options?)

Get the latest behavioral snapshot hash for an agent. Returns an opaque hash of the most recent behavioral evaluation — designed for hash-chain commitment systems that need to commit behavioral state on-chain without accessing raw metrics.

```typescript
const snapshot = await valiron.getAgentSnapshot(agentId: string, options?: {
  chain?: SupportedChain;
}): Promise<AgentSnapshotSummary>;
```

**Returns**: `AgentSnapshotSummary`

```typescript
interface AgentSnapshotSummary {
  agentId: string;
  snapshotHash: string;          // deterministic SHA-256 hash of behavioral evaluation (opaque)
  previousHash: string;          // hash from prior evaluation, or "0x0" for first
  encryptedDataUri: string | null; // IPFS CID of encrypted snapshot data, if available
  timestamp: string | null;      // ISO 8601 timestamp of evaluation
  interactionCount: number;      // total interactions in the evaluation window
}
```

**Example**:
```typescript
const snapshot = await valiron.getAgentSnapshot("25459");
console.log(snapshot.snapshotHash);       // "0x3a7f..."
console.log(snapshot.previousHash);       // "0x0" (genesis) or "0x8b2c..."
console.log(snapshot.interactionCount);   // 15
```

**Notes**:
- The `snapshotHash` is computed internally by Valiron from behavioral metrics. It is opaque — you cannot reverse it to raw metrics.
- Each hash references the `previousHash`, forming a chain. First evaluation gets `"0x0"`.
- Trigger a sandbox evaluation first if no snapshot exists (returns 404 otherwise).

---

### createValironGate(config)

Express-compatible middleware factory. Automatically gates requests based on agent trust.

Uses the public `getAgentProfile()` endpoint under the hood (not the protected `gate()` endpoint), so it works from any server context — no agent challenge-response auth required.

**Express 4 safe**: The returned middleware wraps the async handler with `Promise.resolve(handler(...)).catch(next)` so async rejections are properly forwarded to Express's error handler. This prevents race conditions when chained with other async middleware (e.g. x402's `paymentMiddleware`).

```typescript
import { ValironSDK, createValironGate } from "@valiron/sdk";

const gate = createValironGate(config: {
  sdk: ValironSDK;
  minScore?: number;          // default: 65
  trustSignals?: Array<"8004" | "sandbox" | "world" | "icebreaker">;
  cacheTtlMs?: number;        // default: 30000 (30s client-side cache)
  extractAgentId?: (req: Request) => string | null;
  extractAgentAddress?: (req: Request) => string | null;
  onAllow?: (req: Request, result: GateResult) => void;
  onDeny?: (req: Request, res: Response, result: GateResult) => void;
});
```

**Default behavior**:
- Extracts agent ID from `x-agent-id` header (on-chain) or `x-agent-address` header (key-based)
- Returns 401 if no agent identity is found (with link to identity skill)
- Returns 403 if agent is denied
- Returns 503 if the trust gate is unreachable (fail-closed)
- Caches profiles in memory for 30s to survive x402 retry flows

**Example**:
```typescript
const valiron = new ValironSDK({ chain: "monad" });

// Basic gate
app.use("/api/paid", createValironGate({ sdk: valiron }));

// Custom threshold + longer cache
app.use("/api/premium", createValironGate({
  sdk: valiron,
  minScore: 85,
  trustSignals: ["8004", "sandbox"],
  cacheTtlMs: 60_000, // 60s
}));

// Custom agent ID extraction
app.use("/api/custom", createValironGate({
  sdk: valiron,
  extractAgentId: (req) => req.query.agent_id as string,
  onDeny: (req, res, result) => {
    res.status(403).json({
      error: "Insufficient trust",
      currentTier: result.tier,
      riskLevel: result.riskLevel,
    });
  },
}));
```

---

### dispose()

Flush pending telemetry and clean up resources.

```typescript
await valiron.dispose(): Promise<void>;
```

Call this when shutting down long-running processes.

---

## Proxy Gateway (Pro)

Forward requests through Valiron's edge proxy with automatic trust gating, logging, and SSRF protection.

### proxy(request)

```typescript
const result = await valiron.proxy(request: ProxyRequest): Promise<ProxyResponse>;
```

#### ProxyRequest

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `agentId` | string | yes | Agent to gate |
| `targetUrl` | string | yes | Your origin URL to forward to |
| `method` | string | no | HTTP method (default: `GET`) |
| `headers` | Record<string, string> | no | Headers to forward |
| `body` | string | no | Request body |

#### ProxyResponse

| Field | Type | Description |
|-------|------|-------------|
| `statusCode` | number | HTTP status from your origin |
| `responseHeaders` | Record<string, string> | Response headers |
| `responseBody` | string | Response body |
| `responseMs` | number | Round-trip latency |
| `costCharged` | string | USD cost charged |
| `route` | string | Route decision applied |
| `tierResolved` | string | Trust tier used |

**Example**:
```typescript
const result = await valiron.proxy({
  agentId: "25459",
  targetUrl: "https://your-api.com/data",
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ query: "example" }),
});
```

> **SSRF Protection:** The proxy blocks requests to private IPs (10.x, 172.16-31.x, 192.168.x), localhost, link-local addresses, and cloud metadata endpoints (169.254.169.254).

---

## Key-Based Agents

For Web2 agents that don't have on-chain identities, Valiron supports key-based authentication using EIP-191 challenge-response.

**How it works:**
1. Agent sends a request with `x-agent-address: 0x...` header
2. Valiron returns a challenge nonce
3. Agent signs the nonce with their private key
4. Valiron verifies the signature and creates a `KeyAgentProfile`

The SDK auto-detects key-based agents from the `x-agent-address` header. No configuration needed.

### Sandbox Evaluation for Key Agents

New key agents (verified but not yet scored) are automatically evaluated when they first hit a gated endpoint. The middleware triggers `triggerKeyAgentSandbox()` in the background and returns 403 with `retryAfterMs: 30000`. After evaluation completes, the agent receives a trust score; middleware allows the request when `verified === true` and the score meets the endpoint's `minScore`.

You can also trigger evaluation manually:

```typescript
const result = await valiron.triggerKeyAgentSandbox("0x1234...abcd");
```

Or via HTTP:

```bash
curl -X POST https://valiron-edge-proxy.onrender.com/operator/trigger-sandbox-key/0x1234...abcd
```

### KeyAgentProfile

| Field | Type | Description |
|-------|------|-------------|
| `agentAddress` | string | Ethereum-style address used as the key-based agent identifier |
| `verified` | boolean | Whether the agent proved key ownership through challenge-response |
| `score` | number \| null | Behavioral score once sandbox evaluation has run |
| `tier` | string \| null | Moody's-style trust tier once scored |
| `riskLevel` | string \| null | GREEN / YELLOW / RED once scored |
| `route` | string \| null | Route decision once scored |
| `icebreaker` | object \| null | Optional ownership context from Icebreaker |
| `reasons` | string[] | Human-readable trust explanation |
| `timestamp` | string | ISO timestamp for the profile response |

---

## World ID Methods

Verify agent-human linkage via Worldcoin's zero-knowledge proof system.

### verifyWorldId(agentId, proof, options?)

Submit a World ID proof for verification.

```typescript
const result = await valiron.verifyWorldId(agentId: string, proof: WorldIdProof, options?: {
  chain?: SupportedChain;
}): Promise<WorldIdVerificationResult>;
```

**Example**:
```typescript
const result = await valiron.verifyWorldId("25459", {
  merkle_root: "0x...",
  nullifier_hash: "0x...",
  proof: "0x...",
  signal: "0x...",
});
```

### getWorldIdStatus(agentId, options?)

Check if an agent has a verified World ID.

```typescript
const status = await valiron.getWorldIdStatus(agentId: string, options?: {
  chain?: SupportedChain;
}): Promise<WorldIdStatus>;
// { verified: true, verifiedAt: "2025-01-15T...", level: "orb" }
```

### getWorldIdProfile(agentId, options?)

Get full World ID profile including verification details.

```typescript
const profile = await valiron.getWorldIdProfile(agentId: string, options?: {
  chain?: SupportedChain;
}): Promise<WorldIdAgentProfile>;
```

---

## Telemetry

The SDK collects opt-out usage telemetry to improve reliability. Events include SDK version, runtime, environment, method names, chain labels, latency, HTTP status/error buckets, route decisions, and coarse score/reputation buckets. Raw agent IDs, wallet addresses, API keys, and exact scores are not sent in SDK telemetry. Operator paywall usage logs are separate customer analytics and are authenticated with the operator API key.

### TelemetryConfig

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `enabled` | boolean | `true` | Enable/disable telemetry |
| `sampleRate` | number | `1.0` | Fraction of events to sample (0–1) |
| `onEvent` | function | — | Custom event handler |

**Example**:
```typescript
const valiron = new ValironSDK({
  telemetry: {
    enabled: false,         // Disable entirely
  },
});

// Or with custom sampling and handler
const valiron = new ValironSDK({
  telemetry: {
    sampleRate: 0.1,        // Sample 10% of events
    onEvent: (event) => {   // Custom handler
      myAnalytics.track(event);
    },
  },
});
```

---

## Middleware

All middleware adapters use `getAgentProfile()` (public endpoint) rather than
`gate()` (protected endpoint), so they work from any server context without
agent challenge-response auth. Results are cached client-side (default 30s)
to survive x402's two-request payment flow and avoid cold-start failures.

### createValironGate(config) — Express

See above for full documentation.

**Express 4 note**: The returned middleware uses a synchronous wrapper
(`Promise.resolve().catch(next)`) to ensure async errors are properly caught.
You do NOT need to wrap it yourself — it's safe to use directly with
`app.use()` even on Express 4.

### valironFastifyPlugin — Fastify

```typescript
import { ValironSDK, valironFastifyPlugin } from "@valiron/sdk";

const valiron = new ValironSDK({ chain: "monad" });

fastify.register(valironFastifyPlugin, {
  sdk: valiron,
  prefix: "/api/paid",
});
```

### createValironNextMiddleware — Next.js

```typescript
import { ValironSDK, createValironNextMiddleware } from "@valiron/sdk";

const valiron = new ValironSDK();
export default createValironNextMiddleware({
  sdk: valiron,
  paths: ["/api/paid"],
});
```

### valironGateCheck — Generic (Hono, Koa, etc.)

```typescript
import { ValironSDK, valironGateCheck } from "@valiron/sdk";

const valiron = new ValironSDK();

app.use(async (c, next) => {
  const gate = await valironGateCheck(valiron, {
    agentId: c.req.header("x-agent-id") ?? null,
    agentAddress: c.req.header("x-agent-address") ?? null,
  });
  if (!gate.allow) return c.json(gate.body, gate.status);
  c.set("valiron", gate.result);
  await next();
});
```

### Client-Side Caching

All middleware adapters include a built-in in-memory cache (default TTL: 30 seconds).
This is important for payment protocols like x402 that make two requests per payment
(discover 402 → retry with payment). Without caching, the second trust check can fail
due to cold-start latency on the edge proxy.

The Express gate exposes `cacheTtlMs` to override this TTL; set it to `0` to disable
caching for that middleware instance. `createValironNextMiddleware` and
`valironGateCheck` also accept `cacheTtlMs`.

### Middleware Error Responses

All middleware variants return consistent error responses:

| Status | When | Body |
|--------|------|------|
| 401 | No agent ID found in request | `{ error: "No agent identity provided" }` |
| 403 | Agent denied by trust gate | `{ error: "Access denied", tier, riskLevel, route }` |
| 403 | New agent pending evaluation | `{ error: "Agent pending evaluation", retryAfterMs: 30000 }` |
| 503 | Valiron service unavailable | `{ error: "Trust evaluation unavailable" }` |

> **Fail-closed:** When the Valiron service is unreachable, middleware returns 503 — it never fails open and never allows unverified agents through.

---

## Response Types

### GateResult

Returned by `gate()` and middleware.

| Field | Type | Description |
|-------|------|-------------|
| `allow` | boolean | Whether the agent is allowed access |
| `tier` | string | Trust tier (AAA–C) |
| `riskLevel` | string | GREEN / YELLOW / RED |
| `route` | string | `prod` / `prod_throttled` / `sandbox` / `sandbox_only` |
| `meetsThreshold` | boolean | Whether the agent meets the minimum trust threshold |
| `agentId` | string | The evaluated agent ID |
| `wallet` | string | Agent's wallet address |
| `chain` | object | Chain context |
| `sandboxRan` | boolean | Whether sandbox was executed |
| `cached` | boolean | Whether the result was served from cache |

### AgentProfile

Returned by `getAgentProfile()`.

| Field | Type | Description |
|-------|------|-------------|
| `agentId` | string | ERC-8004 token ID |
| `identity` | object | On-chain identity (name, wallet, endpoints, trustModels) |
| `onchainReputation` | object | Feedback count, average score, Solana ATOM metrics |
| `routing` | object | `{ finalRoute, meetsThreshold }` |
| `chain` | object | `{ name, chainId }` |
| `timestamp` | string | ISO timestamp |

### Request Headers

Headers attached to proxied/gated requests:

| Header | Description |
|--------|-------------|
| `x-agent-id` | The agent's ERC-8004 token ID |
| `x-agent-address` | The agent's wallet address (key-based agents) |
| `x-valiron-tier` | Resolved trust tier |
| `x-valiron-route` | Route decision |
| `x-valiron-risk` | Risk level |

### All Exports

```typescript
import {
  ValironSDK,
  ValironOperator,
  ValironClient,
  createValironGate,
  valironFastifyPlugin,
  createValironNextMiddleware,
  valironGateCheck,
  isValidAddress,
  generateChallenge,
} from "@valiron/sdk";
```

---

## Plans & Limits

| Feature | Free | Pro |
|---------|------|-----|
| Gated endpoints | 3 | Unlimited |
| Sandbox tests / day | 5 | Unlimited |
| Webhooks | — | ✓ |
| Proxy gateway | — | ✓ |
| Analytics & logs | — | ✓ |
| Evaluation history | — | ✓ |
| Custom tier thresholds | — | ✓ |

Pro plan requires an operator API key (`val_op_` prefix) from the [Operator Dashboard](./DASHBOARD.md).
