# TypeScript SDK

The RailProtocol TypeScript SDK provides a typed interface for making agent payments, managing wallets, setting spend policies, and querying on-chain transaction history.

***

## Installation

```bash
npm install @railprotocol/sdk
```

Requires Node.js 18 or later. The SDK ships with full TypeScript declarations and no runtime dependencies beyond the Solana web3.js client.

***

## Initialization

```typescript
import { RailProtocol } from "@railprotocol/sdk";

const rail = new RailProtocol({
  agentId: "my-agent",
  apiKey: process.env.RAIL_API_KEY,
});
```

**Options:**

| Option               | Type                        | Default                        | Description                                                            |
| -------------------- | --------------------------- | ------------------------------ | ---------------------------------------------------------------------- |
| `agentId`            | string                      | —                              | The agent ID. Required. Must match a registered agent in your account. |
| `apiKey`             | string                      | —                              | Your RailProtocol API key. Required.                                   |
| `baseUrl`            | string                      | `https://api.railprotocol.org` | Override for self-hosted or staging environments.                      |
| `timeout`            | number                      | `30000`                        | Request timeout in milliseconds.                                       |
| `maxRetries`         | number                      | `3`                            | Retries on transient payment errors before throwing.                   |
| `protocolPreference` | `"auto" \| "x402" \| "mpp"` | `"auto"`                       | Override per-agent protocol preference set via policies.               |

***

## `rail.pay()`

Make a payment to an x402 or MPP endpoint. Protocol is detected automatically.

```typescript
const result = await rail.pay({
  endpoint: "https://api.dune.com/v1/query/1234/results",
  method: "GET",
  headers: { "X-Custom": "value" },  // optional
  body: { key: "value" },            // optional, for POST/PUT
  idempotencyKey: "run-abc-step-1",  // optional
});
```

**Response: `PaymentResult`**

```typescript
interface PaymentResult {
  status: number;              // HTTP status from the paid API
  protocol: "x402" | "mpp";   // Protocol used
  amountPaid: { usdc: number };
  txHash: string;              // Solana transaction hash
  solanaExplorer: string;      // Direct link to the on-chain record
  data: unknown;               // Parsed JSON response body
  headers: Headers;            // Response headers from the API
  sessionId?: string;          // MPP session ID if established or reused
}
```

**Example:**

```typescript
const result = await rail.pay({
  endpoint: "https://api.dune.com/v1/query/1234/results",
  method: "GET",
});

console.log(result.protocol);     // "x402"
console.log(result.amountPaid);   // { usdc: 0.002 }
console.log(result.txHash);       // "5yJ8kXtg2xp..."
console.log(result.data);         // { rows: [...], metadata: {...} }
```

***

## `rail.wallet`

Manage the agent's Solana wallet.

### `rail.wallet.get()`

```typescript
const wallet = await rail.wallet.get();
// {
//   agentId: "my-agent",
//   address: "7xKXtg2xpAGMq8KLwpSodE9LFKq8Z1EscezSShpump",
//   balance: { usdc: 42.50 },
//   network: "solana-mainnet"
// }
```

### `rail.wallet.balance()`

```typescript
const { usdc } = await rail.wallet.balance();
console.log(usdc);  // 42.50
```

### `rail.wallet.fund()`

Initiate a top-up from your account's linked funding source.

```typescript
await rail.wallet.fund({ usdc: 100 });
```

### `rail.wallet.withdraw()`

Withdraw USDC to a Solana address.

```typescript
await rail.wallet.withdraw({
  to: "9zXKtg2xpAGMq8KLwpSodE9LFKq8Z1EscezSShpump",
  usdc: 20,
});
```

***

## `rail.policies`

Manage spend policies.

### `rail.policies.set()`

```typescript
await rail.policies.set({
  dailyBudget: { usdc: 100 },
  perCallLimit: { usdc: 1.00 },
  allowedDomains: ["api.dune.com", "api.browserbase.com", "fal.ai"],
  blockedDomains: [],
  rateLimit: { calls: 500, window: "1h" },
  protocolPreference: "auto",
});
```

All fields are optional. Omitted fields retain their current values.

### `rail.policies.get()`

```typescript
const policy = await rail.policies.get();
// {
//   dailyBudget: { usdc: 100 },
//   perCallLimit: { usdc: 1.00 },
//   allowedDomains: ["api.dune.com", ...],
//   rateLimit: { calls: 500, window: "1h" },
//   protocolPreference: "auto",
//   usedToday: { usdc: 12.40 }
// }
```

### `rail.policies.clear()`

Remove all spend policies from the agent.

```typescript
await rail.policies.clear();
```

***

## `rail.transactions`

Query on-chain payment records.

### `rail.transactions.list()`

```typescript
const txs = await rail.transactions.list({
  limit: 50,
  protocol: "x402",         // optional filter
  domain: "api.dune.com",   // optional filter
  since: "24h",             // optional time filter
});

for (const tx of txs) {
  console.log(tx.txHash, tx.amountPaid.usdc, tx.protocol);
}
```

### `rail.transactions.get()`

```typescript
const tx = await rail.transactions.get("5yJ8kXtg2xp...");
// {
//   txHash: "5yJ8kXtg2xp...",
//   agentId: "my-agent",
//   protocol: "x402",
//   amountPaid: { usdc: 0.002 },
//   endpoint: "api.dune.com/v1/query/1234/results",
//   policyCheck: "passed",
//   parentTxHash: null,
//   solanaExplorer: "https://explorer.solana.com/tx/5yJ8kXtg2xp...",
//   timestamp: "2026-05-09T14:23:01Z"
// }
```

### `rail.transactions.getChain()`

Resolve the full payment chain for a multi-agent transaction tree.

```typescript
const chain = await rail.transactions.getChain(rootTxHash);

for (const tx of chain) {
  console.log(`${tx.agentId} paid ${tx.amountPaid.usdc} USDC (depth: ${tx.depth})`);
}
```

***

## `rail.agents`

Invoke sub-agents and manage agent registrations.

### `rail.agents.invoke()`

Invoke an external agent endpoint with a budget cap.

```typescript
const result = await rail.agents.invoke({
  agentEndpoint: "https://research-agent.yourplatform.com/run",
  task: {
    query: "Summarize Solana TVL trends for Q1 2026",
  },
  budget: { usdc: 2.00 },
});

console.log(result.output);        // agent's response
console.log(result.totalCost);     // { usdc: 0.84 }
console.log(result.paymentChain);  // ["5yJ8kX...", "3xH7jY...", ...]
```

### `rail.agents.list()`

```typescript
const agents = await rail.agents.list();

for (const agent of agents) {
  console.log(agent.agentId, agent.balance.usdc);
}
```

***

## Error handling

The SDK throws typed errors for all failure cases:

```typescript
import {
  RailProtocolError,
  PolicyViolationError,
  InsufficientFundsError,
  DomainNotAllowedError,
  PaymentProtocolError,
  PaymentError,
  AuthenticationError,
} from "@railprotocol/sdk";

try {
  const result = await rail.pay({
    endpoint: "https://api.service.com/data",
    method: "GET",
  });
} catch (err) {
  if (err instanceof PolicyViolationError) {
    console.error("Policy blocked payment:", err.reason);
    // "per_call_limit_exceeded: requested 2.50 USDC, limit is 1.00 USDC"
  } else if (err instanceof InsufficientFundsError) {
    console.error("Wallet balance too low. Current:", err.currentBalance.usdc);
  } else if (err instanceof DomainNotAllowedError) {
    console.error("Domain not on allowlist:", err.domain);
  } else if (err instanceof PaymentProtocolError) {
    console.error("Unrecognized payment protocol at:", err.endpoint);
  } else if (err instanceof PaymentError) {
    console.error("Payment failed after retries:", err.message);
  } else if (err instanceof AuthenticationError) {
    console.error("Invalid API key");
  }
}
```

**Error types:**

| Class                    | When thrown                             |
| ------------------------ | --------------------------------------- |
| `PolicyViolationError`   | Payment rejected by spend policy        |
| `InsufficientFundsError` | Wallet does not have enough USDC        |
| `DomainNotAllowedError`  | Endpoint domain not on agent allowlist  |
| `PaymentProtocolError`   | 402 response with unrecognized protocol |
| `PaymentError`           | Payment failed after all retries        |
| `AuthenticationError`    | Invalid or missing API key              |
| `RailProtocolError`      | Base class for all SDK errors           |

***

## Full example: autonomous research agent

```typescript
import { RailProtocol, PolicyViolationError } from "@railprotocol/sdk";

const rail = new RailProtocol({
  agentId: "research-agent",
  apiKey: process.env.RAIL_API_KEY,
});

async function research(topic: string): Promise<string> {
  // Step 1: Fetch on-chain data
  let chainData;
  try {
    const dataResult = await rail.pay({
      endpoint: "https://api.dune.com/v1/query/1234/results",
      method: "GET",
    });
    chainData = dataResult.data;
  } catch (err) {
    if (err instanceof PolicyViolationError) {
      console.warn("Data API payment blocked:", err.reason);
      chainData = null;
    } else {
      throw err;
    }
  }

  // Step 2: Run inference
  const inferenceResult = await rail.pay({
    endpoint: "https://api.fal.ai/v1/inference",
    method: "POST",
    body: {
      topic,
      context: chainData,
    },
  });

  console.log(`Total cost: ${inferenceResult.amountPaid.usdc} USDC`);
  console.log(`Solana record: ${inferenceResult.solanaExplorer}`);

  return inferenceResult.data.summary;
}
```

***

## Full example: Next.js API route with streaming

```typescript
// app/api/agent/route.ts
import { RailProtocol } from "@railprotocol/sdk";
import { NextRequest } from "next/server";

const rail = new RailProtocol({
  agentId: process.env.AGENT_ID!,
  apiKey: process.env.RAIL_API_KEY!,
});

export async function POST(req: NextRequest) {
  const { endpoint, body } = await req.json();

  const result = await rail.pay({ endpoint, method: "POST", body });

  return Response.json({
    data: result.data,
    cost: result.amountPaid,
    txHash: result.txHash,
    protocol: result.protocol,
  });
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.railprotocol.org/sdk/typescript.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
