@agent-fuel/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,30 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@agent-fuel/sdk` are documented here. Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this package follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
4
+
5
+ ## [0.1.0] — 2026-05-27
6
+
7
+ First public release. Six-method surface for AI agents to read reputation, pay services from a credit vault, and stream live events — plus an x402 fetch wrapper.
8
+
9
+ ### Added
10
+
11
+ - `AgentFuel` class. Constructor: `{ agent: Keypair, owner?: Pubkeyish, cluster, rpc, apiBase? }`. Lazy-initializes Anchor `Program` instances on first chain read.
12
+ - `fuel.spend({ service, amountUsdc, owner? })` — pays a service from the agent's vault. Local six-check policy guardrail (frozen / zero / whitelist / per-tx / hourly window / lifetime) mirrors `programs/credit_vault/src/policy.rs`. On-chain `VaultError` codes map back to the same typed exceptions so a single `instanceof SpendPolicyError` catches both pre-flight and chain rejections. Prepends an idempotent ATA-create for the recipient so callers never need to pre-flight whether the service has a USDC account.
13
+ - Read methods, all returning snake_case-keyed structs that mirror the on-chain layouts and the backend's REST shapes:
14
+ - `fuel.getScore(agent?)` — REST `/reputation/:agent`.
15
+ - `fuel.getVaultBalance(ref?)` — Anchor account fetch on `CreditVault`, with a derived `balance` field.
16
+ - `fuel.getPolicy(ref?)` — Anchor account fetch on `SpendPolicy`. Default-padding whitelist entries stripped.
17
+ - `fuel.checkService(serviceAuthority)` — Anchor account fetch on `ServiceRegistry`, name decoded from UTF-8.
18
+ - `fuel.onEvent(callback, options?)` — WebSocket subscription per agent. Reconnect with exponential backoff (`1 → 2 → 4 … cap 30 s`); status flow `connecting → open → reconnecting → closed`. Isomorphic: prefers `globalThis.WebSocket`, falls back to `ws` on Node 18–21 via lazy import.
19
+ - `paymentRequired(fuel, options?)` — fetch-shaped wrapper for HTTP 402. Parses `X-Payment-Required` header (or 402 body), accepts both `recipient`/`amountUsdc` and `payTo`/`maxAmountRequired` vocabularies, fires `spend()`, retries once with `X-Payment: <signature>`. Single retry by design.
20
+ - Typed errors: `AgentFuelError` base, `AccountNotFoundError`, `HttpError`, `OwnerNotConfiguredError`, `PaymentParseError`, and `SpendPolicyError` with six concrete subclasses (`VaultFrozenError`, `ZeroAmountError`, `NotWhitelistedError`, `PerTxLimitExceededError`, `HourlyLimitExceededError`, `LifetimeLimitExceededError`).
21
+ - PDA helpers: `vaultPda(owner, agent)`, `policyPda(vault)`, `serviceRegistryPda(authority)`.
22
+ - SPL token helpers: `TOKEN_PROGRAM_ID`, `ASSOCIATED_TOKEN_PROGRAM_ID`, `getAssociatedTokenAddress`, `createAssociatedTokenAccountIdempotentInstruction`.
23
+ - IDLs vendored at [`src/idl/`](src/idl/) and re-exported as sub-paths (`@agent-fuel/sdk/idl/reputation`, `@agent-fuel/sdk/idl/credit-vault`).
24
+ - Runnable example at [`examples/x402-quickstart/`](examples/x402-quickstart/) with a dry-run mode (no Solana) and a devnet mode.
25
+
26
+ ### Notes
27
+
28
+ - Published with npm provenance via GitHub Actions on tag push.
29
+ - Peer dependencies: `@coral-xyz/anchor ^0.31` and `@solana/web3.js ^1.95`.
30
+ - Node engines: `>=18.18`.
package/README.md ADDED
@@ -0,0 +1,196 @@
1
+ # @agent-fuel/sdk
2
+
3
+ TypeScript SDK for [Agent Fuel](https://github.com/TODO/agent_fuel) — credit vault + reputation primitives for AI agents on Solana.
4
+
5
+ > **Status:** `0.1.0-alpha.0`. Full read/write/stream surface live. Publishing to npm lands in the next slice ([phases.md](../../docs/phases.md)).
6
+
7
+ ## The six functions
8
+
9
+ | Function | Returns | What it does |
10
+ | --- | --- | --- |
11
+ | `fuel.spend({ service, amountUsdc })` | `{ signature }` | Pays a service from the agent's vault. Local six-check policy guardrail, then a single signed transaction. Idempotently creates the service's USDC ATA if needed. |
12
+ | `fuel.getScore(agent?)` | `ReputationLookup` | Public reputation snapshot via REST. Score is `null` for unscored agents. |
13
+ | `fuel.getVaultBalance(ref?)` | `CreditVaultAccount` | On-chain credit vault state with a derived `balance` field. Defaults to the agent's own vault when `owner` was passed to the constructor. |
14
+ | `fuel.getPolicy(ref?)` | `SpendPolicyAccount` | On-chain spend policy: per-tx / hourly / lifetime caps, whitelist, freeze flag. |
15
+ | `fuel.checkService(serviceAuthority)` | `ServiceRegistryAccount` | Service registry lookup by the registering wallet's pubkey. |
16
+ | `fuel.onEvent(callback, options?)` | `Subscription` | WebSocket stream of events for an agent. Auto-reconnect with backoff. |
17
+
18
+ Plus one fetch wrapper for the x402 protocol — see [Paying with x402](#paying-with-x402) below.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ npm install @agent-fuel/sdk @solana/web3.js @coral-xyz/anchor
24
+ ```
25
+
26
+ `@solana/web3.js` and `@coral-xyz/anchor` are peer dependencies; pin them yourself so your bot and the SDK share the same RPC client.
27
+
28
+ ## Usage
29
+
30
+ ```ts
31
+ import { Keypair, PublicKey } from "@solana/web3.js";
32
+ import { AgentFuel } from "@agent-fuel/sdk";
33
+
34
+ const agent = Keypair.generate(); // load yours from a keystore
35
+
36
+ const fuel = new AgentFuel({
37
+ agent,
38
+ owner: new PublicKey("..."), // the wallet that funded your vault
39
+ cluster: "devnet",
40
+ rpc: "https://api.devnet.solana.com",
41
+ apiBase: "http://localhost:8080", // your Agent Fuel backend
42
+ });
43
+ ```
44
+
45
+ `owner` is optional. When you pass it, the agent-side methods (`getVaultBalance`, `getPolicy`, `spend`) default to your own vault — call them with no args. Pass `owner` per call only when inspecting someone else's vault. Calling a method that needs the owner without configuring one anywhere throws `OwnerNotConfiguredError`.
46
+
47
+ ## Read methods
48
+
49
+ ```ts
50
+ // Public reputation snapshot (REST, no auth). Omit the arg to read your own.
51
+ const score = await fuel.getScore();
52
+ const otherScore = await fuel.getScore(otherAgentPubkey);
53
+
54
+ // On-chain credit vault state. With `owner` set on the constructor, no args
55
+ // reads your own vault. Pass a ref to inspect someone else's.
56
+ const vault = await fuel.getVaultBalance();
57
+ console.log(vault.balance, vault.frozen);
58
+ const otherVault = await fuel.getVaultBalance({ owner: otherOwner, agent: otherAgent });
59
+
60
+ // Same shape for the policy.
61
+ const policy = await fuel.getPolicy();
62
+
63
+ // Service registry lookup by the service's authority pubkey.
64
+ const service = await fuel.checkService(serviceAuthorityPubkey);
65
+ ```
66
+
67
+ All methods throw `AccountNotFoundError` when the target doesn't exist on-chain (or returns 404 from the backend), and `HttpError` for non-2xx REST responses. Field names mirror the on-chain layout in `snake_case` so they line up with the backend's REST responses.
68
+
69
+ ## Paying a service
70
+
71
+ ```ts
72
+ const { signature } = await fuel.spend({
73
+ service: serviceAuthorityPubkey,
74
+ amountUsdc: 250_000, // micro-USDC (0.25 USDC)
75
+ });
76
+ ```
77
+
78
+ `spend()` fetches the current vault + policy and applies the same six-check ladder the on-chain program enforces ([programs/credit_vault/src/policy.rs](../../programs/credit_vault/src/policy.rs#L11)) before submitting. Each policy failure surfaces as a typed error so the caller can branch on it without parsing strings:
79
+
80
+ | Error | When |
81
+ | --- | --- |
82
+ | `VaultFrozenError` | `vault.frozen === true` |
83
+ | `ZeroAmountError` | `amountUsdc <= 0` |
84
+ | `NotWhitelistedError` | Whitelist is set and `service` isn't in it |
85
+ | `PerTxLimitExceededError` | `amountUsdc > policy.per_tx_limit_usdc` |
86
+ | `HourlyLimitExceededError` | Rolling 9 000-slot window would exceed `policy.hourly_limit_usdc` |
87
+ | `LifetimeLimitExceededError` | `vault.total_spent + amountUsdc > policy.lifetime_limit_usdc` |
88
+
89
+ All six inherit from `SpendPolicyError` for a single catch-all. The same exceptions are thrown for chain-side failures too — if the on-chain `VaultError` lands between the pre-flight and the transaction (concurrent spend, window roll-over), the SDK maps the Anchor error code back to the matching typed error so your `try/catch` doesn't have to branch on where the rejection came from.
90
+
91
+ The service's USDC associated token account is created on-demand — the SDK prepends an idempotent ATA-create instruction before every `spend`, so callers don't need to pre-flight whether the service has ever received USDC. The agent pays the rent (~0.002 SOL).
92
+
93
+ ## Live events
94
+
95
+ ```ts
96
+ const sub = fuel.onEvent(
97
+ (frame) => {
98
+ // frame.event_name is one of: Spent, Claimed, ScoreComputed, Deposited, ...
99
+ console.log(frame.event_name, frame.payload);
100
+ },
101
+ { onStatus: (s) => console.log("ws:", s) },
102
+ );
103
+
104
+ // later
105
+ sub.close();
106
+ ```
107
+
108
+ `onEvent()` opens a WebSocket to `/ws/agents/:pk` on the configured `apiBase`, parses each JSON frame into a typed `LiveEventFrame`, and fires `callback` for every event the backend broadcasts for that agent. Connection status flows through `onStatus`: `connecting → open → reconnecting → open → … → closed`. Reconnect uses exponential backoff (1 s → 2 s → 4 s → …, capped at 30 s) and the subscription survives transient network failures until you call `sub.close()`.
109
+
110
+ By default it subscribes to your own `agent`. Pass `{ agent }` to watch a different agent.
111
+
112
+ Runtime note: the SDK prefers `globalThis.WebSocket` (browsers, Node 22+) and falls back to the `ws` package for older Node, resolved lazily so browser bundlers don't pull `ws` in.
113
+
114
+ ## Paying with x402
115
+
116
+ `paymentRequired(fuel, options?)` returns a `fetch`-shaped function that transparently handles HTTP 402:
117
+
118
+ ```ts
119
+ import { paymentRequired } from "@agent-fuel/sdk";
120
+
121
+ const fetchWithPayments = paymentRequired(fuel, {
122
+ onPaymentRequired: (req) => console.log("paying", req.amountUsdc, "to", req.recipient),
123
+ onPaid: (sig) => console.log("signature:", sig),
124
+ });
125
+
126
+ const res = await fetchWithPayments("https://data.example/feed");
127
+ const body = await res.json();
128
+ ```
129
+
130
+ Flow:
131
+
132
+ 1. The wrapped request runs.
133
+ 2. If the response is `402`, the SDK parses the `X-Payment-Required` header (JSON; or the response body as a fallback) for `recipient` and `amountUsdc` — `payTo`/`maxAmountRequired` are accepted as aliases for x402-spec servers.
134
+ 3. `fuel.spend()` is called with those values. All six policy guardrails apply; any failure throws the matching `SpendPolicyError` and the request is *not* retried.
135
+ 4. The original request is retried once with `X-Payment: <signature>`.
136
+
137
+ A second `402` propagates to the caller (no infinite loop). Malformed payment payloads throw `PaymentParseError`.
138
+
139
+ There's a runnable example under [`examples/x402-quickstart/`](./examples/x402-quickstart/) with both a dry-run mode (no Solana, 5-second smoke) and a real-devnet mode.
140
+
141
+ ## Program IDs
142
+
143
+ Re-exported from the vendored IDLs (see [`src/idl/`](src/idl/)):
144
+
145
+ ```ts
146
+ import { PROGRAM_IDS } from "@agent-fuel/sdk";
147
+
148
+ PROGRAM_IDS.reputation; // PublicKey
149
+ PROGRAM_IDS.creditVault; // PublicKey
150
+ ```
151
+
152
+ ## IDL access
153
+
154
+ Raw IDLs are exposed under sub-paths for downstream tooling (e.g. Anchor's `Program` constructor):
155
+
156
+ ```ts
157
+ import reputationIdl from "@agent-fuel/sdk/idl/reputation";
158
+ import creditVaultIdl from "@agent-fuel/sdk/idl/credit-vault";
159
+ ```
160
+
161
+ ## Development
162
+
163
+ ```bash
164
+ npm install
165
+ npm run typecheck
166
+ npm run lint
167
+ npm run build # emits dist/ (ESM + CJS + .d.ts) via tsup
168
+ ```
169
+
170
+ ### Refreshing the IDLs
171
+
172
+ The IDLs under `src/idl/` are committed copies — the SDK build, CI, and downstream consumers never depend on `anchor build`. When the Anchor programs change, re-vendor manually and commit:
173
+
174
+ ```bash
175
+ # at repo root
176
+ anchor build
177
+ # in clients/sdk/
178
+ npm run vendor-idl
179
+ git add src/idl/
180
+ ```
181
+
182
+ ### Releasing
183
+
184
+ Releases are tag-driven. The [`SDK Release`](../../.github/workflows/sdk-release.yml) workflow publishes to npm with provenance on any `sdk-v*` tag push, and refuses to publish if the tag, `package.json` version, and `CHANGELOG.md` don't all agree.
185
+
186
+ ```bash
187
+ # 1. bump version in package.json + add a `## [x.y.z]` section to CHANGELOG.md
188
+ # 2. commit
189
+ git commit -am "release(sdk): v0.1.1"
190
+ # 3. tag + push
191
+ git tag sdk-v0.1.1
192
+ git push --follow-tags
193
+ # 4. (after publish lands) bump the pin in clients/web/package.json
194
+ ```
195
+
196
+ The workflow needs the repo secret `NPM_TOKEN` (an automation token with publish rights to the `@agent-fuel` scope). Provenance attestation is handled by GitHub OIDC — no extra config needed beyond the `id-token: write` permission already in the workflow.