@balchemyai/agent-sdk 0.2.1 → 0.2.4

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/README.md CHANGED
@@ -1,18 +1,35 @@
1
1
  # @balchemyai/agent-sdk
2
2
 
3
- TypeScript SDK for Balchemy external AI agents. Handles onboarding (SIWE wallet-based or walletless Identity flow), MCP tool access, token lifecycle management, and real-time SSE event streaming.
3
+ TypeScript SDK for external agents that connect to Balchemy through MCP. Use it
4
+ to onboard an agent, request scoped access, call tools, refresh tokens, and
5
+ consume SSE events.
4
6
 
5
- ## Installation
7
+ Balchemy does not ask an inner model to make trade decisions. Your outer LLM
8
+ chooses tools. Balchemy checks policy, executes through the authorized path, and
9
+ keeps the record.
10
+
11
+ ## Install
6
12
 
7
13
  ```sh
8
14
  npm install @balchemyai/agent-sdk
9
15
  ```
10
16
 
11
- ## Auth Paths
17
+ ## Create a Client
18
+
19
+ ```ts
20
+ import { BalchemyAgentSdk } from "@balchemyai/agent-sdk";
21
+
22
+ const sdk = new BalchemyAgentSdk({
23
+ apiBaseUrl: "https://api.balchemy.ai/api",
24
+ });
25
+ ```
26
+
27
+ `apiBaseUrl` includes `/api` and has no trailing slash.
12
28
 
13
- ### Path 1 SIWE (wallet-based)
29
+ ## Auth Path 1: SIWE
14
30
 
15
- Use this when your agent controls a Solana or EVM wallet and can sign messages.
31
+ Use this path when the agent controls a wallet and can sign an off-chain
32
+ message.
16
33
 
17
34
  ```ts
18
35
  import { BalchemyAgentSdk } from "@balchemyai/agent-sdk";
@@ -21,24 +38,21 @@ const sdk = new BalchemyAgentSdk({
21
38
  apiBaseUrl: "https://api.balchemy.ai/api",
22
39
  });
23
40
 
24
- // 1. Request a nonce and SIWS message
25
- const { message, nonce } = await sdk.requestSiweNonce({
41
+ const { message } = await sdk.requestSiweNonce({
26
42
  address: "YOUR_WALLET_ADDRESS",
27
43
  chainId: 8453,
28
- domain: "youragent.example.com",
29
- uri: "https://youragent.example.com",
44
+ domain: "agent.example.com",
45
+ uri: "https://agent.example.com",
30
46
  statement: "Sign in to Balchemy",
31
47
  });
32
48
 
33
- // 2. Sign `message` with your wallet (off-chain, using your signing library)
34
49
  const signature = await wallet.signMessage(message);
35
50
 
36
- // 3. Onboard
37
51
  const response = await sdk.onboardWithSiwe({
38
52
  message,
39
53
  signature,
40
- agentId: "your-agent-unique-id",
41
- scope: "trade", // "read" | "trade"
54
+ agentId: "agent-example",
55
+ scope: "trade",
42
56
  });
43
57
 
44
58
  const mcp = sdk.connectMcp({
@@ -47,9 +61,10 @@ const mcp = sdk.connectMcp({
47
61
  });
48
62
  ```
49
63
 
50
- ### Path 2 Identity / Walletless
64
+ ## Auth Path 2: Walletless Identity
51
65
 
52
- Use this when your agent has an HMAC-signed identity token (for balchemy native) or ES256 JWT (for external providers) from a provider registered with Balchemy.
66
+ Use this path when the platform operator has registered an identity provider and
67
+ the agent holds a valid HMAC identity token or ES256 JWT.
53
68
 
54
69
  ```ts
55
70
  import { BalchemyAgentSdk } from "@balchemyai/agent-sdk";
@@ -59,9 +74,9 @@ const sdk = new BalchemyAgentSdk({
59
74
  });
60
75
 
61
76
  const response = await sdk.onboardWithIdentity({
62
- provider: "your-registered-provider-id",
63
- identityToken: "YOUR_PROVIDER_JWT",
64
- agentId: "your-agent-unique-id",
77
+ provider: "registered-provider-id",
78
+ identityToken: "PROVIDER_JWT",
79
+ agentId: "agent-example",
65
80
  chainId: 8453,
66
81
  scope: "trade",
67
82
  });
@@ -72,54 +87,54 @@ const mcp = sdk.connectMcp({
72
87
  });
73
88
  ```
74
89
 
75
- ## Using the MCP Client
90
+ If the endpoint returns `FEATURE_DISABLED`, the backend needs
91
+ `AGENT_WALLETLESS_ONBOARDING_ENABLED=true`.
92
+
93
+ ## MCP Calls
76
94
 
77
95
  ```ts
78
- // List available tools
79
96
  const { tools } = await mcp.listTools();
80
97
 
81
- // Natural language query
82
- const reply = await mcp.askBot({ message: "What is the price of SOL?" });
98
+ const reply = await mcp.askBot({
99
+ message: "What is the price of SOL?",
100
+ });
83
101
 
84
- // Execute an agent instruction
85
- const result = await mcp.agentExecute({
86
- instruction: "Find a low-risk setup on Base with 50 USDC",
102
+ const research = await mcp.agentExecute({
103
+ instruction: "Find low-risk Base setups with deep liquidity.",
87
104
  });
88
105
 
89
- // EVM quote (read-only, no wallet interaction)
90
106
  const quote = await mcp.evmQuote({
91
107
  chainId: 8453,
92
- sellToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
93
- buyToken: "0x4200000000000000000000000000000000000006", // WETH on Base
94
- sellAmount: "50000000", // 50 USDC (6 decimals)
108
+ sellToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
109
+ buyToken: "0x4200000000000000000000000000000000000006",
110
+ sellAmount: "50000000",
95
111
  });
96
112
 
97
- // EVM swap (submit: false = pending order, submit: true = on-chain execution)
98
- const swap = await mcp.evmSwap({
113
+ const draftSwap = await mcp.evmSwap({
99
114
  chainId: 8453,
100
115
  sellToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
101
- buyToken: "0x4200000000000000000000000000000000000006",
116
+ buyToken: "0x4200000000000000000000000000000000000006",
102
117
  sellAmount: "50000000",
103
- submit: true,
118
+ submit: false,
104
119
  });
105
120
  ```
106
121
 
107
- ## Tool Exposure and Scope
108
-
109
- By default the MCP endpoint exposes a curated agent-facing tool set for chat, execution, setup, behavior rules, portfolio/status, and subscriptions.
122
+ `submit: false` creates a draft/pending order. Use execution only when the agent
123
+ has the right scope and the product flow expects on-chain action.
110
124
 
111
- The broader internal catalog is only exposed when the platform flag `MCP_EXPOSE_GRANULAR_TOOLS=true` is enabled on the bot. Contact the Balchemy team if your integration needs granular tool access beyond the default agent-facing surface.
112
-
113
- Tool scopes:
125
+ ## Scope
114
126
 
115
127
  | Scope | Access |
116
- |-------|--------|
117
- | `"read"` | Read-only tools — market data, portfolio views, research |
118
- | `"trade"` | All read tools + trade execution tools |
128
+ | --- | --- |
129
+ | `read` | Market data, portfolio views, research, status |
130
+ | `trade` | Read tools plus trade execution tools |
119
131
 
120
- Pass `scope` during onboarding to receive an MCP key with the appropriate permissions.
132
+ The default MCP endpoint exposes a curated agent-facing surface for chat,
133
+ execution, setup, behavior rules, portfolio/status, and subscriptions. Granular
134
+ internal tools stay hidden unless the platform flag
135
+ `MCP_EXPOSE_GRANULAR_TOOLS=true` is enabled for the bot.
121
136
 
122
- ## Error Handling
137
+ ## Errors
123
138
 
124
139
  All SDK methods throw `AgentSdkError` on failure.
125
140
 
@@ -127,64 +142,77 @@ All SDK methods throw `AgentSdkError` on failure.
127
142
  import { AgentSdkError } from "@balchemyai/agent-sdk";
128
143
  import type { AgentSdkErrorCode } from "@balchemyai/agent-sdk";
129
144
 
130
- try {
131
- const result = await mcp.agentExecute({ instruction: "..." });
132
- } catch (err: unknown) {
133
- if (err instanceof AgentSdkError) {
134
- const code: AgentSdkErrorCode = err.code;
135
- // "auth_error" | "policy_error" | "rate_limit" | "provider_auth_error"
136
- // | "network_error" | "execution_error" | "invalid_response"
137
- console.error(`[${code}] ${err.message}`, err.details);
145
+ type SdkFailure = {
146
+ code: AgentSdkErrorCode;
147
+ message: string;
148
+ details: unknown;
149
+ };
150
+
151
+ function toSdkFailure(err: unknown): SdkFailure | null {
152
+ if (!(err instanceof AgentSdkError)) {
153
+ return null;
138
154
  }
155
+
156
+ return {
157
+ code: err.code,
158
+ message: err.message,
159
+ details: err.details,
160
+ };
139
161
  }
140
162
  ```
141
163
 
164
+ Known codes include `auth_error`, `policy_error`, `rate_limit`,
165
+ `provider_auth_error`, `network_error`, `execution_error`, and
166
+ `invalid_response`.
167
+
142
168
  ## Tool Response Helpers
143
169
 
144
170
  ```ts
145
- import { getToolText, parseToolJson, isToolError } from "@balchemyai/agent-sdk";
171
+ import { getToolText, isToolError, parseToolJson } from "@balchemyai/agent-sdk";
146
172
 
147
173
  const response = await mcp.agentPortfolio();
148
174
 
149
- if (isToolError(response)) {
150
- console.error("Tool returned an error:", getToolText(response));
151
- } else {
152
- const data = parseToolJson(response); // T | null
153
- console.log(data);
154
- }
175
+ const portfolio = isToolError(response)
176
+ ? { error: getToolText(response) }
177
+ : { data: parseToolJson(response) };
155
178
  ```
156
179
 
157
- ## Token Management
180
+ ## Token Store
158
181
 
159
182
  ```ts
160
183
  import { TokenStore } from "@balchemyai/agent-sdk";
161
184
 
162
185
  const store = new TokenStore({
163
- // Called when the stored token nears expiry — return a fresh OnboardingResponse
164
- refreshFn: async () => {
165
- return sdk.onboardWithIdentity({ ... });
166
- },
186
+ refreshFn: async () =>
187
+ sdk.onboardWithIdentity({
188
+ provider: "registered-provider-id",
189
+ identityToken: "PROVIDER_JWT",
190
+ agentId: "agent-example",
191
+ chainId: 8453,
192
+ scope: "trade",
193
+ }),
167
194
  });
168
195
 
169
196
  await store.set(response);
170
- const token = await store.get(); // auto-refreshes if expiry < threshold
197
+ const token = await store.get();
171
198
  ```
172
199
 
200
+ The store refreshes when the token is near expiry.
201
+
173
202
  ## Identity Access Token
174
203
 
175
- The `OnboardingResponse` includes an `identityAccess` field when the platform issues a short-lived access token alongside the MCP key.
204
+ `OnboardingResponse` includes `identityAccess` when the platform issues a
205
+ short-lived identity access token beside the MCP key.
176
206
 
177
207
  ```ts
178
208
  import type { IdentityAccess } from "@balchemyai/agent-sdk";
179
209
 
180
210
  const access: IdentityAccess | undefined = response.identityAccess;
181
- if (access) {
182
- console.log(access.scope); // "read" | "trade"
183
- console.log(access.expiresAt); // ISO timestamp
184
- }
211
+ const scope = access?.scope;
212
+ const expiresAt = access?.expiresAt;
185
213
  ```
186
214
 
187
- ## SSE Event Streaming
215
+ ## SSE Events
188
216
 
189
217
  ```ts
190
218
  import { SseEventStream } from "@balchemyai/agent-sdk";
@@ -196,34 +224,34 @@ const stream = new SseEventStream(
196
224
  { reconnectDelayMs: 2000, maxReconnects: 0 }
197
225
  );
198
226
 
199
- // Async iterator
200
- for await (const event of stream) {
201
- const e: SseEvent = event;
202
- console.log(e.event, e.data);
227
+ async function readEvents() {
228
+ for await (const event of stream) {
229
+ handleEvent(event);
230
+ }
203
231
  }
204
232
 
205
- // Or callback-based
206
- const unsubscribe = stream.subscribe(
207
- (event) => console.log(event),
208
- (err) => console.error(err)
209
- );
210
- // later: unsubscribe();
233
+ function handleEvent(event: SseEvent) {
234
+ return event;
235
+ }
211
236
  ```
212
237
 
213
238
  ## Identity Token Revocation
214
239
 
215
240
  ```ts
216
- // Revoke a token by JTI
217
- await sdk.revokeIdentityToken({ jti: "the-token-jti", ttlSeconds: 86400 });
241
+ await sdk.revokeIdentityToken({
242
+ jti: "token-jti",
243
+ ttlSeconds: 86400,
244
+ });
218
245
 
219
- // Check revocation status
220
- const { revoked } = await sdk.getIdentityTokenRevokeStatus({ jti: "the-token-jti" });
246
+ const { revoked } = await sdk.getIdentityTokenRevokeStatus({
247
+ jti: "token-jti",
248
+ });
221
249
  ```
222
250
 
223
- ## Platform Endpoints Reference
251
+ ## Platform Endpoints
224
252
 
225
253
  | Endpoint | Path | Auth |
226
- |----------|------|------|
254
+ | --- | --- | --- |
227
255
  | MCP server | `POST /api/mcp/{publicId}` | `Authorization: Bearer <mcp-api-key>` |
228
256
  | SIWE nonce | `POST /api/nest/auth/evm/nonce` | Public |
229
257
  | SIWE onboarding | `POST /api/public/erc8004/onboarding/siwe` | Public |
@@ -233,45 +261,25 @@ const { revoked } = await sdk.getIdentityTokenRevokeStatus({ jti: "the-token-jti
233
261
  | MCP discovery | `GET /.well-known/mcp.json` | Public |
234
262
  | Agent directory | `GET /api/nest/agents/verified/page` | Public |
235
263
 
236
- > Note: the JWKS endpoint is served at `/.well-known/jwks.json` (root-relative, **not** under `/api`).
237
-
238
- ## Platform Operator Setup
264
+ JWKS is root-relative. Do not prefix it with `/api`.
239
265
 
240
- To enable agent onboarding, the following environment variables must be configured on the backend:
266
+ ## Operator Setup
241
267
 
242
268
  | Variable | Required | Description |
243
- |----------|----------|-------------|
244
- | `AGENT_WALLETLESS_ONBOARDING_ENABLED` | For walletless path | Set to `true` to enable the identity/walletless onboarding endpoint. Default: `false`. |
245
- | `SIWE_DOMAIN_ALLOWLIST` | For SIWE path | Comma-separated list of allowed domains for SIWE message verification (e.g. `youragent.example.com,localhost`). |
246
- | `ERC8004_IDENTITY_PROVIDERS` | For walletless path | Comma-separated list of allowed identity provider IDs (e.g. `github-actions,openclaw`). |
247
- | `AGENT_IDENTITY_ISSUER_PRIVATE_KEY_PEM` | For identity tokens | ES256 private key (PEM or base64) for signing agent identity access tokens. Required if issuing short-lived identity tokens. |
248
- | `API_URL` | Always | Base API URL used to build MCP endpoint URLs in onboarding responses (e.g. `https://api.balchemy.ai/api`). |
249
-
250
- ## Getting Started (Quickstart)
251
-
252
- 1. Install the SDK:
253
- ```sh
254
- npm install @balchemyai/agent-sdk
255
- ```
256
-
257
- 2. Create an MCP API key for your bot via the Hub UI: `Hub > Your Bot > API Keys > Create Key`.
258
-
259
- 3. Choose an auth path:
260
- - **SIWE (wallet-based):** Your agent must control an EVM wallet and can sign messages.
261
- - **Walletless (identity token):** Your agent has an HMAC-signed identity token (balchemy native) or ES256 JWT (external provider) from a registered provider. The platform operator must set `AGENT_WALLETLESS_ONBOARDING_ENABLED=true`.
262
-
263
- 4. Run the relevant code example in the [Auth Paths](#auth-paths) section above.
264
-
265
- 5. Use the returned `mcp.endpoint` and `mcp.apiKey` to make MCP tool calls.
266
-
267
- 6. The MCP key is scoped — `"read"` for read-only tools, `"trade"` for trade execution tools. Choose at onboarding time via the `scope` parameter.
269
+ | --- | --- | --- |
270
+ | `AGENT_WALLETLESS_ONBOARDING_ENABLED` | Walletless path | Enables identity onboarding. Default: `false`. |
271
+ | `SIWE_DOMAIN_ALLOWLIST` | SIWE path | Comma-separated domains allowed in SIWE verification. |
272
+ | `ERC8004_IDENTITY_PROVIDERS` | Walletless path | Registered provider IDs. |
273
+ | `AGENT_IDENTITY_ISSUER_PRIVATE_KEY_PEM` | Identity tokens | ES256 private key for short-lived identity access tokens. |
274
+ | `API_URL` | Always | Base API URL used to build MCP endpoint URLs. |
268
275
 
269
276
  ## Notes
270
277
 
271
- - `agent_seed_request` is disabled on the platform. The `requestSeed()` method exists for backward compatibility but always throws a deterministic `AgentSdkError` with code `"execution_error"`.
272
- - `apiBaseUrl` must include the `/api` path segment and must **not** have a trailing slash.
273
- - The JWKS endpoint is at `/.well-known/jwks.json` (root-relative). Do not prefix with `/api`.
274
- - If walletless onboarding returns HTTP 403 with `code: "FEATURE_DISABLED"`, the platform operator needs to set `AGENT_WALLETLESS_ONBOARDING_ENABLED=true`.
278
+ - `agent_seed_request` is disabled on the platform.
279
+ - `requestSeed()` remains for compatibility and throws `AgentSdkError` with
280
+ code `execution_error`.
281
+ - MCP keys are scoped. Choose `read` or `trade` during onboarding.
282
+ - Walletless onboarding requires an operator-approved provider.
275
283
 
276
284
  ## Docs
277
285
 
@@ -279,4 +287,4 @@ To enable agent onboarding, the following environment variables must be configur
279
287
  - [Python parity backlog](docs/python-parity-backlog.md)
280
288
  - [Partner integration checklist](docs/partner-integration-checklist.md)
281
289
  - [Release policy](docs/release-policy.md)
282
- - Full API reference: [https://balchemy.ai/docs](https://balchemy.ai/docs)
290
+ - [Full API reference](https://balchemy.ai/docs)
@@ -88,6 +88,10 @@ const loop = AgentLoop.fromConfig('./agent.config.yaml');
88
88
  await loop.start();
89
89
 
90
90
  // Or inline config
91
+ const statusEvents: string[] = [];
92
+ const decisionEvents: unknown[] = [];
93
+ const errorEvents: string[] = [];
94
+
91
95
  const loop2 = new AgentLoop({
92
96
  mcpEndpoint: 'https://api.balchemy.ai/mcp/YOUR_PUBLIC_ID',
93
97
  apiKey: process.env.BALCHEMY_API_KEY!,
@@ -95,9 +99,9 @@ const loop2 = new AgentLoop({
95
99
  llmApiKey: process.env.ANTHROPIC_API_KEY!,
96
100
  llmModel: 'claude-haiku-4-5',
97
101
  maxDailyLlmCost: 5,
98
- onStatusChange: (s) => console.log('status', s.status),
99
- onDecision: (d) => console.log('decision', d),
100
- onError: (e) => console.error('error', e.message),
102
+ onStatusChange: (s) => statusEvents.push(s.status),
103
+ onDecision: (d) => decisionEvents.push(d),
104
+ onError: (e) => errorEvents.push(e.message),
101
105
  });
102
106
  await loop2.start();
103
107
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@balchemyai/agent-sdk",
3
- "version": "0.2.1",
3
+ "version": "0.2.4",
4
4
  "license": "MIT",
5
5
  "private": false,
6
6
  "description": "Balchemy external AI agent onboarding + MCP SDK — ERC-8004 onboarding, Solana/EVM trading, 100 tools via MCP",
@@ -38,7 +38,7 @@
38
38
  "homepage": "https://balchemy.ai",
39
39
  "repository": {
40
40
  "type": "git",
41
- "url": "https://github.com/balchemy/balchemy-agent",
41
+ "url": "git+https://github.com/balchemy/balchemy-agent.git",
42
42
  "directory": "packages/sdk"
43
43
  },
44
44
  "bugs": {