@zeroxyz/sdk 0.9.0 → 0.11.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 CHANGED
@@ -4,6 +4,28 @@ All notable changes to `@zeroxyz/sdk` will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) — with the pre-1.0 caveat that minor versions may include breaking changes.
6
6
 
7
+ ## 0.11.0
8
+
9
+ ### Added
10
+
11
+ - **`client.wallet.import()`** — link an existing self-custody wallet to the session user by private key (`{ privateKey, makePrimary? }` → the linked wallet). 409s when the wallet is already linked to this or another profile. New export `ImportWalletOptions`. (ZERO-478)
12
+ - **`client.wallet.setPrimary()` / `client.wallet.remove()`** — session-mode multi-wallet management: switch the primary payer wallet, or unlink a wallet (rejected for the primary wallet or a non-zero balance). New exports `SetPrimaryWalletOptions` / `RemoveWalletOptions`. (ZERO-478)
13
+ - **`UserWalletDto.importedAt`** — set on wallets linked via `wallet.import()`, absent/null otherwise. (ZERO-478)
14
+
15
+ ## 0.10.0
16
+
17
+ ### Changed
18
+
19
+ - **BREAKING: `client.wallet.address` is now an async method** (was a sync getter). In account mode it resolves the configured account's address locally (no network, same value as before); in session mode — where the getter used to return `null` — it now resolves the user's managed (Privy-embedded) wallet, provisioning one on first call. It throws instead of returning `null` when the client has no credentials. Migration: `client.wallet.address` → `await client.wallet.address()`. (ZERO-433)
20
+ - **BREAKING: `client.wallet.migrateAuthorization()` removed** along with the `MigrateAuthorization` / `MigrateAuthorizationResponse` types. It existed solely for the CLI's legacy BYO→managed sweep (`zero wallet migrate`), which is removed — usage was near zero and net-new users never need it (ZERO-465). The server route still exists; its removal is tracked separately.
21
+
22
+ ### Added
23
+
24
+ - **`client.auth.agent`** — the auth.md agent-account lifecycle (AuthKit Agents), moved from the CLI so any consumer can mint and claim autonomous agent accounts: `signup()` (anonymous registration → assertion exchange → Zero session, all in one call), plus the granular `register()`, `exchangeAssertion()`, `exchangeAtZero()`, `startClaim()`, `completeClaim()`, and `endpoints()` (RFC 9728/8414 discovery, cached). Failures on the AuthKit-hosted surface throw the new `ZeroAgentAuthError` (`status`, `body`, `serverCode`). Exported types: `AgentSignupResult`, `AgentRegistration`, `AgentToken`, `AgentZeroSession`, `AgentClaimAttempt`, `AgentVerifiedIdentity`, `CompleteClaimResult`, `AgentAuthEndpoints`. (ZERO-433)
25
+ - **`client.ping()`** — polls `/health` until the API is ready (bounded attempts, never throws). For warming a cold container before dependent work. (ZERO-433)
26
+ - **`BugReports.create()` generates an idempotency key by default** when `idempotencyKey` is omitted; pass `contextSeed` to make the auto-key deterministic per context. Previously only the CLI deduplicated bug reports. (ZERO-433)
27
+ - Pure helpers lifted from the CLI so all consumers share them: `inferSchema(value)`, `tryParseJson(text)`, `urlMatchesTemplate(url, template)`, plus schema-rendering helpers (`sampleValueFor`, `placeholderFor`, `paramAttrs`, `schemaTypeLabel`, `constraintHint`) and `centsToDollars(cents)`. A number of further exports (display formatters, chain constants, `detectAgentHost`, redaction helpers) are marked `@internal` — they exist so Zero's own surfaces render identically and aren't part of the supported partner contract. (ZERO-433)
28
+
7
29
  ## 0.9.0
8
30
 
9
31
  ### Added
package/README.md CHANGED
@@ -118,6 +118,14 @@ await client.fetch(cap.url, {
118
118
 
119
119
  If you'd rather inspect the schema yourself, the SDK exports the pure helpers it uses: `extractInputEnvelope(bodySchema, method)` (the query-param / body schema nodes), `unwrapTransportEnvelope(exampleRequest)` (the inner request from a stored example), and `normalizeTransportEnvelopeRequest(url, { method, body })` (the `{ url, method, body }` the wire request should use).
120
120
 
121
+ Additional utilities exported from `@zeroxyz/sdk`:
122
+
123
+ - `inferSchema(value)` — deterministic structural walk of a JSON value to a JSON-schema-ish draft (types + field names, no values). Useful for logging anonymized request/response shapes.
124
+ - `tryParseJson(text)` — returns parsed JSON or `null` on failure (no throw).
125
+ - `urlMatchesTemplate(url, template)` — returns `true` when a URL matches a path template like `https://api.example.com/users/{id}`. Mirrors the server-side `templateToRegex` logic.
126
+
127
+ A few further exports (display formatters, chain constants) exist so Zero's own surfaces (CLI, MCP plugin) render identically; they're marked `@internal` and aren't part of the supported partner contract.
128
+
121
129
  ```ts
122
130
  const result = await client.fetch(url, {
123
131
  method: "POST",
@@ -178,7 +186,7 @@ for (const warning of result.warnings ?? []) {
178
186
  | `payment` | `PaymentResult \| null` | `{ protocol, chain, txHash, amount, asset, … }` when a payment settled; `null` on a free call. |
179
187
  | `runId` | `string \| null` | The recorded run, for `runs.review()`. **`null` unless you pass `capabilityId`** (and a wallet is configured). |
180
188
  | `latencyMs` | `number` | End-to-end call latency. |
181
- | `upstreamError` | `UpstreamError?` | Present on `server_error` — the capability's own 4xx/5xx detail. |
189
+ | `upstreamError` | `UpstreamError?` | Present on `server_error` — the capability's own 4xx/5xx detail (`status`, `message`, `snippetHash`). Carries a `fixHint` string for caller-fixable 4xx shapes (wrong HTTP method, unparseable JSON body, unrecognized/extra fields, content-type mismatch, missing required field). |
182
190
  | `warnings` | `string[]?` | Non-fatal notes (truncated body, MPP close failure, …) — keyed by `FETCH_WARNINGS`. |
183
191
  | `runTrackingSkipped` | `string[]?` | Why a run wasn't recorded (e.g. no `capabilityId`) — keyed by `FETCH_SKIP_REASONS`. |
184
192
 
@@ -200,8 +208,9 @@ The wallet you construct the client with **is** the agent's identity — paid ca
200
208
  The same two primitives serve both:
201
209
 
202
210
  ```ts
203
- // The configured address (null in session mode until the first paid call resolves it).
204
- client.wallet.address;
211
+ // The address this client acts as. Account mode resolves locally; session
212
+ // mode resolves the user's primary managed wallet (provisioning on first call).
213
+ await client.wallet.address();
205
214
 
206
215
  // Balance in USDC. Sums Base + Tempo by default; pass a chain for just one.
207
216
  const { amount } = await client.wallet.balance(); // e.g. "12.50"
@@ -212,23 +221,37 @@ const onBase = await client.wallet.balance({ chain: "base" });
212
221
  const url = await client.wallet.fundingUrl({ amount: "10" });
213
222
  ```
214
223
 
215
- Poll `balance()` to decide when to top up. Then either open `fundingUrl()` yourself to refill the shared wallet — from an ops dashboard, a low-balance alert, or alongside a direct USDC transfer to `wallet.address` — or hand the link to the end user. Either way the link is **single-use** and burns when opened, so generate it at the moment of funding and don't pre-open one you intend to give to someone else.
224
+ Poll `balance()` to decide when to top up. Then either open `fundingUrl()` yourself to refill the shared wallet — from an ops dashboard, a low-balance alert, or alongside a direct USDC transfer to the `wallet.address()` result — or hand the link to the end user. Either way the link is **single-use** and burns when opened, so generate it at the moment of funding and don't pre-open one you intend to give to someone else.
216
225
 
217
- In **session mode** (acting for a Zero user) the wallet is Zero-managed, and a couple more methods apply:
226
+ In **session mode** (acting for a Zero user) wallets are managed server-side, and a few more methods apply:
218
227
 
219
228
  ```ts
220
229
  await client.wallet.list(); // every wallet linked to the session user
221
230
  await client.wallet.provision(); // create the user's managed wallet (idempotent)
231
+
232
+ // Link an existing self-custody wallet by private key (0x prefix optional).
233
+ // Pass makePrimary to have it pay for the user's calls from now on.
234
+ const imported = await client.wallet.import({
235
+ privateKey: "0x…",
236
+ makePrimary: true,
237
+ });
238
+
239
+ // Switch which linked wallet is primary, or unlink one. remove() rejects the
240
+ // primary wallet and any wallet holding a balance — reassign / drain first.
241
+ await client.wallet.setPrimary({ walletAddress: imported.walletAddress });
242
+ await client.wallet.remove({ walletAddress: "0x…" });
222
243
  ```
223
244
 
224
- To move funds from a user's own (BYO) wallet into their Zero-managed wallet, sign an EIP-3009 `ReceiveWithAuthorization` and relay it with `client.wallet.migrateAuthorization(authorization)` Zero pays the gas and sweeps the USDC to Base.
245
+ `wallet.address()` covers the common case (resolve or provision the primary managed wallet); reach for the methods above when you need the full wallet objects, explicit provisioning, or multi-wallet management. Imported wallets carry an `importedAt` timestamp on the returned wallet objects.
225
246
 
226
247
  ## Namespaces
227
248
 
228
249
  Beyond `search()` and `fetch()`, the client groups the rest of the API into resource namespaces:
229
250
 
230
251
  ```ts
231
- // Search — ranked capabilities for a query (no auth required)
252
+ // Search — ranked capabilities for a query (no auth required).
253
+ // No implicit price cap: omitting maxCost returns results at any price.
254
+ // Pass a value to cap costs (e.g. "0.05" for a tight cap, "0" for free-only).
232
255
  const { capabilities } = await client.search("translate text to french", {
233
256
  maxCost: "0.05", // optional: only results at or under this price
234
257
  protocol: "x402", // optional: "x402" | "mpp"
@@ -238,7 +261,9 @@ const { capabilities } = await client.search("translate text to french", {
238
261
  const cap = await client.capabilities.get("cap_abc"); // by uid
239
262
  const cap2 = await client.capabilities.get("z_Ab12cd.1"); // by search token
240
263
 
241
- // Wallet — balance + one-time funding URL. See "Wallet & funding" above.
264
+ // Wallet — balance, one-time funding URL, and (session mode) multi-wallet
265
+ // management: list / provision / import / setPrimary / remove. See
266
+ // "Wallet & funding" above.
242
267
  const { amount } = await client.wallet.balance();
243
268
  const fundUrl = await client.wallet.fundingUrl({ amount: "10" });
244
269
 
@@ -272,8 +297,33 @@ await client.auth.logout(refreshToken); // revoke the session server-side
272
297
  const device = await client.auth.device.start();
273
298
  console.log(device.verificationUri, device.userCode);
274
299
  const grant = await client.auth.device.poll(device.deviceCode);
300
+
301
+ // Agent accounts (auth.md) — fully autonomous signup, no browser or human.
302
+ // Persist registration.claimToken + registration.identityAssertion securely:
303
+ // they are returned exactly once. `session` is a Zero session for the account.
304
+ const { registration, session } = await client.auth.agent.signup();
305
+
306
+ // A human can claim ownership of the account later. startClaim() returns a
307
+ // hosted URL; the page shows the HUMAN a pairing code (the reverse of the
308
+ // device flow) which they read back for completeClaim().
309
+ if (registration.claimToken) {
310
+ const attempt = await client.auth.agent.startClaim({
311
+ claimToken: registration.claimToken,
312
+ email: "human@example.com",
313
+ });
314
+ console.log(attempt.verificationUri);
315
+ const claimed = await client.auth.agent.completeClaim({
316
+ claimToken: registration.claimToken,
317
+ userCode: "1234-5678", // read back by the human
318
+ });
319
+ // claimed.status: "ok" | "not_confirmed" | "invalid_code" | "expired" —
320
+ // on "ok", exchange the fresh assertion for a post-claim session:
321
+ // exchangeAssertion(claimed.identityAssertion) → exchangeAtZero(accessToken)
322
+ }
275
323
  ```
276
324
 
325
+ Errors from the AuthKit-hosted agent-auth surface throw `ZeroAgentAuthError` (`status`, `body`, and `serverCode` — the server's own error code, e.g. `claim_expired`).
326
+
277
327
  ## Multi-tenant servers
278
328
 
279
329
  A server that signs as a different end-user wallet on each request shouldn't build a fresh `ZeroClient` every call. Pass the wallet per call, or derive a reusable sub-client: