@zeroxyz/sdk 0.6.0 → 0.8.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.
@@ -9,8 +9,8 @@ import { wrapFetchWithPayment } from '@x402/fetch';
9
9
  import { Receipt, Challenge } from 'mppx';
10
10
  import { Mppx, tempo } from 'mppx/client';
11
11
  import { base, baseSepolia } from 'viem/chains';
12
- import { z } from 'zod';
13
12
  import { createHash } from 'crypto';
13
+ import { z } from 'zod';
14
14
 
15
15
  // src/auth/managed-account.ts
16
16
  var createManagedAccount = (client, address) => toAccount({
@@ -1213,1683 +1213,1696 @@ var Payments = class {
1213
1213
  };
1214
1214
  };
1215
1215
  };
1216
-
1217
- // package.json
1218
- var package_default = {
1219
- version: "0.6.0"};
1220
-
1221
- // src/version.ts
1222
- var SDK_VERSION = package_default.version;
1223
- var userWalletDtoSchema = z.object({
1224
- walletAddress: z.string(),
1225
- source: z.enum(["self_custody", "privy_embedded"]),
1226
- isPrimary: z.boolean(),
1227
- linkedAt: z.union([z.string(), z.date()]).nullable().optional(),
1228
- delegationGranted: z.boolean(),
1229
- signerQuorumId: z.string().nullable(),
1230
- // Per-transaction USDC cap in base units (6 dp). null = unlimited, 0 = free-only.
1231
- maxTransactionUsdcBaseUnits: z.number().int().nullable()
1232
- });
1233
- var welcomeBonusSummarySchema = z.object({
1234
- status: z.string(),
1235
- amountUsd: z.number()
1236
- });
1237
- var internalUserDtoSchema = z.object({
1238
- id: z.string(),
1239
- email: z.string().nullable(),
1240
- createdAt: z.union([z.string(), z.date()]).optional(),
1241
- lastLoginAt: z.union([z.string(), z.date()]).nullable().optional(),
1242
- onboardingCompleted: z.boolean().optional(),
1243
- delegationGranted: z.boolean().optional(),
1244
- wallets: z.array(userWalletDtoSchema).optional(),
1245
- balance: z.object({ amount: z.string(), asset: z.string() }).nullable().optional(),
1246
- welcomeBonus: welcomeBonusSummarySchema.nullable().optional()
1247
- });
1248
- var publicUserDtoSchema = z.object({
1249
- user: z.object({
1250
- id: z.string(),
1251
- email: z.string().nullable()
1252
- }),
1253
- walletAddress: z.string().nullable(),
1254
- balance: z.object({ amount: z.string(), asset: z.string() }).nullable(),
1255
- welcomeBonus: welcomeBonusSummarySchema.nullable()
1256
- });
1257
- var signResponseSchema = z.object({
1258
- signature: z.string().regex(/^0x[0-9a-fA-F]+$/, "must be 0x-prefixed hex"),
1259
- walletAddress: z.string().regex(/^0x[0-9a-fA-F]{40}$/, "must be 0x-prefixed 40-char hex address")
1260
- });
1261
-
1262
- // src/schemas/device.ts
1263
- var deviceStartResponseSchema = z.object({
1264
- deviceCode: z.string(),
1265
- userCode: z.string(),
1266
- verificationUri: z.string(),
1267
- pollInterval: z.number(),
1268
- expiresAt: z.number()
1269
- });
1270
- var devicePollResponseSchema = z.union([
1271
- // userCode + verificationUri are optional: older API deployments don't
1272
- // echo them on pending.
1273
- z.object({
1274
- error: z.literal("authorization_pending"),
1275
- userCode: z.string().optional(),
1276
- verificationUri: z.string().optional()
1277
- }),
1278
- z.object({ error: z.literal("expired_token") }),
1279
- z.object({
1280
- accessToken: z.string(),
1281
- refreshToken: z.string(),
1282
- expiresIn: z.number(),
1283
- user: internalUserDtoSchema
1284
- })
1285
- ]);
1286
- var sessionExchangeResponseSchema = z.object({
1287
- token: z.string(),
1288
- expiresAt: z.number()
1289
- });
1290
- var refreshResponseSchema = z.object({
1291
- accessToken: z.string(),
1292
- refreshToken: z.string()
1293
- });
1294
- var tryRefreshSession = async (client, callerSignal) => {
1295
- const credentials = client.getCredentials();
1296
- if (credentials.kind !== "session") return false;
1297
- if (!credentials.refreshToken) return false;
1298
- const url = `${client.baseUrl.replace(/\/$/, "")}/v1/auth/refresh`;
1299
- const timeoutSignal = AbortSignal.timeout(client.timeout);
1300
- const signal = callerSignal ? AbortSignal.any([callerSignal, timeoutSignal]) : timeoutSignal;
1301
- let response;
1302
- try {
1303
- response = await client.fetchImpl(url, {
1304
- method: "POST",
1305
- headers: { "content-type": "application/json" },
1306
- body: JSON.stringify({ refreshToken: credentials.refreshToken }),
1307
- signal
1308
- });
1309
- } catch {
1310
- return false;
1216
+ var isShortToken = (id) => id.startsWith("z_") && id.indexOf(".", 2) !== -1;
1217
+ var MAX_402_PROBE_BYTES = 1024 * 1024;
1218
+ var MAX_RESPONSE_BYTES = 10 * 1024 * 1024;
1219
+ var TEXT_CONTENT_TYPES = [
1220
+ "application/json",
1221
+ "application/xml",
1222
+ "application/javascript",
1223
+ "application/ecmascript",
1224
+ "application/x-www-form-urlencoded",
1225
+ "text/",
1226
+ "+json",
1227
+ "+xml"
1228
+ ];
1229
+ var MPP_SCHEME_TOKENS = /* @__PURE__ */ new Set(["payment", "mpp"]);
1230
+ var splitWwwAuthenticateChallenges = (header) => {
1231
+ const chunks = [];
1232
+ let buf = "";
1233
+ let inQuotes = false;
1234
+ for (let i = 0; i < header.length; i++) {
1235
+ const ch = header[i];
1236
+ if (inQuotes) {
1237
+ if (ch === "\\" && i + 1 < header.length) {
1238
+ buf += ch + header[i + 1];
1239
+ i++;
1240
+ continue;
1241
+ }
1242
+ if (ch === '"') inQuotes = false;
1243
+ buf += ch;
1244
+ continue;
1245
+ }
1246
+ if (ch === '"') {
1247
+ inQuotes = true;
1248
+ buf += ch;
1249
+ continue;
1250
+ }
1251
+ if (ch === ",") {
1252
+ chunks.push(buf);
1253
+ buf = "";
1254
+ continue;
1255
+ }
1256
+ buf += ch;
1311
1257
  }
1312
- if (!response.ok) return false;
1313
- let body;
1258
+ if (buf.length > 0) chunks.push(buf);
1259
+ return chunks;
1260
+ };
1261
+ var isTextContentType = (contentType) => {
1262
+ if (!contentType) return true;
1263
+ const lower = contentType.toLowerCase();
1264
+ return TEXT_CONTENT_TYPES.some((marker) => lower.includes(marker));
1265
+ };
1266
+ var redactUrl = (url) => {
1314
1267
  try {
1315
- body = await response.json();
1268
+ const u = new URL(url);
1269
+ return `${u.origin}${u.pathname}`;
1316
1270
  } catch {
1317
- return false;
1271
+ return "<invalid url>";
1318
1272
  }
1319
- const parsed = refreshResponseSchema.safeParse(body);
1320
- if (!parsed.success) return false;
1321
- await client.applyRefreshedTokens({
1322
- accessToken: parsed.data.accessToken,
1323
- refreshToken: parsed.data.refreshToken
1324
- });
1325
- return true;
1326
- };
1327
- var buildSessionAuthHeaders = (accessToken) => ({
1328
- authorization: `Bearer ${accessToken}`
1329
- });
1330
- var buildCanonicalMessage = (method, pathWithQuery, body, timestamp, nonce) => {
1331
- const bodyHash = createHash("sha256").update(body ?? "").digest("hex");
1332
- return `${method}:${pathWithQuery}:${bodyHash}:${timestamp}:${nonce}`;
1333
1273
  };
1334
- var buildAccountAuthHeaders = async (account, method, pathWithQuery, body) => {
1335
- const timestamp = Math.floor(Date.now() / 1e3).toString();
1336
- const nonce = crypto.randomUUID();
1337
- const message = buildCanonicalMessage(
1338
- method,
1339
- pathWithQuery,
1340
- body,
1341
- timestamp,
1342
- nonce
1274
+ var ECHO_DETECT_MIN_LEN = 16;
1275
+ var ECHO_DETECT_MAX_SCAN = 64 * 1024;
1276
+ var requestBodyEchoed = (candidate, requestBody) => {
1277
+ if (requestBody === void 0) return false;
1278
+ const asString = typeof requestBody === "string" ? requestBody : new TextDecoder("utf-8", { fatal: false }).decode(
1279
+ requestBody.byteLength > ECHO_DETECT_MAX_SCAN ? requestBody.subarray(0, ECHO_DETECT_MAX_SCAN) : requestBody
1343
1280
  );
1344
- const signature = await account.signMessage({ message });
1345
- return {
1346
- "x-zero-address": account.address,
1347
- "x-zero-timestamp": timestamp,
1348
- "x-zero-nonce": nonce,
1349
- "x-zero-signature": signature
1350
- };
1351
- };
1352
-
1353
- // src/transport/http.ts
1354
- var RETRYABLE_STATUS = /* @__PURE__ */ new Set([408, 502, 503, 504]);
1355
- var sleep = (ms, signal) => new Promise((resolve, reject) => {
1356
- if (signal?.aborted) {
1357
- reject(new DOMException("Aborted", "AbortError"));
1358
- return;
1359
- }
1360
- const timer = setTimeout(() => {
1361
- signal?.removeEventListener("abort", onAbort);
1362
- resolve();
1363
- }, ms);
1364
- const onAbort = () => {
1365
- clearTimeout(timer);
1366
- reject(new DOMException("Aborted", "AbortError"));
1367
- };
1368
- signal?.addEventListener("abort", onAbort, { once: true });
1369
- });
1370
- var buildPathWithQuery = (path, query) => {
1371
- const normalizedPath = path.startsWith("/") ? path : `/${path}`;
1372
- if (!query) return normalizedPath;
1373
- const params = new URLSearchParams();
1374
- for (const [key, value] of Object.entries(query)) {
1375
- if (value !== void 0) params.set(key, String(value));
1376
- }
1377
- const qs = params.toString();
1378
- return qs ? `${normalizedPath}?${qs}` : normalizedPath;
1379
- };
1380
- var joinUrl = (baseUrl, pathWithQuery) => {
1381
- const base3 = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
1382
- return `${base3}${pathWithQuery}`;
1383
- };
1384
- var buildHeaders = async (client, method, pathWithQuery, hasBody, bodyStr) => {
1385
- const headers = { ...client.defaultHeaders };
1386
- if (hasBody) headers["content-type"] = "application/json";
1387
- headers["x-zero-sdk-version"] = SDK_VERSION;
1388
- if (client.apiVersion) headers["x-zero-api-version"] = client.apiVersion;
1389
- const credentials = client.getCredentials();
1390
- if (credentials.kind === "session") {
1391
- Object.assign(headers, buildSessionAuthHeaders(credentials.accessToken));
1392
- } else if (credentials.kind === "account") {
1393
- const signed = await buildAccountAuthHeaders(
1394
- credentials.account,
1395
- method,
1396
- pathWithQuery,
1397
- bodyStr
1398
- );
1399
- Object.assign(headers, signed);
1281
+ if (asString.length < ECHO_DETECT_MIN_LEN) return false;
1282
+ const scanLen = Math.min(asString.length, ECHO_DETECT_MAX_SCAN);
1283
+ for (let i = 0; i + ECHO_DETECT_MIN_LEN <= scanLen; i++) {
1284
+ if (candidate.includes(asString.slice(i, i + ECHO_DETECT_MIN_LEN))) {
1285
+ return true;
1286
+ }
1400
1287
  }
1401
- return headers;
1288
+ return false;
1402
1289
  };
1403
- var parseErrorBody = async (response) => {
1290
+ var cancelBody2 = (response) => {
1404
1291
  try {
1405
- return await response.clone().json();
1292
+ response.body?.cancel().catch(() => {
1293
+ });
1406
1294
  } catch {
1407
- try {
1408
- return await response.text();
1409
- } catch {
1410
- return void 0;
1411
- }
1412
1295
  }
1413
1296
  };
1414
- var errorMessageFromBody = (body, status) => {
1415
- if (body !== null && typeof body === "object" && "error" in body && typeof body.error === "string") {
1416
- return body.error;
1297
+ var detectPaymentRequirement = async (response) => {
1298
+ if (response.status !== 402) return null;
1299
+ const x402Header = response.headers.get("payment-required") ?? response.headers.get("x-payment-required");
1300
+ if (x402Header) {
1301
+ cancelBody2(response);
1302
+ try {
1303
+ const decoded = JSON.parse(
1304
+ Buffer.from(x402Header, "base64").toString("utf8")
1305
+ );
1306
+ return { protocol: "x402", raw: decoded };
1307
+ } catch {
1308
+ return { protocol: "x402", raw: { encoded: x402Header } };
1309
+ }
1417
1310
  }
1418
- return `HTTP ${status}`;
1419
- };
1420
- var request = async (client, opts, schema) => {
1421
- const pathWithQuery = buildPathWithQuery(opts.path, opts.query);
1422
- const url = joinUrl(client.baseUrl, pathWithQuery);
1423
- const hasBody = opts.body !== void 0;
1424
- const bodyStr = hasBody ? JSON.stringify(
1425
- opts.body,
1426
- (_k, v) => typeof v === "bigint" ? v.toString() : v
1427
- ) : void 0;
1428
- const configuredFetch = buildConfiguredFetch(client);
1429
- const performOnce = async () => configuredFetch(url, {
1430
- method: opts.method,
1431
- headers: await buildHeaders(
1432
- client,
1433
- opts.method,
1434
- pathWithQuery,
1435
- hasBody,
1436
- bodyStr
1437
- ),
1438
- body: bodyStr,
1439
- signal: opts.signal
1440
- });
1441
- let refreshAttempted = false;
1442
- const executeWithRefresh = async () => {
1443
- const response = await performOnce();
1444
- if (response.status !== 401 || refreshAttempted) return response;
1445
- const credentials = client.getCredentials();
1446
- if (credentials.kind !== "session") return response;
1447
- refreshAttempted = true;
1448
- const refreshed = await client.refreshSessionOnce(opts.signal);
1449
- if (!refreshed) return response;
1450
- return await performOnce();
1451
- };
1452
- let lastNetworkError;
1453
- for (let attempt = 0; attempt <= client.maxRetries; attempt++) {
1454
- let response;
1311
+ const declaredLen = Number.parseInt(
1312
+ response.headers.get("content-length") ?? "",
1313
+ 10
1314
+ );
1315
+ const bodyFits = !Number.isFinite(declaredLen) || declaredLen <= MAX_402_PROBE_BYTES;
1316
+ if (bodyFits) {
1455
1317
  try {
1456
- response = await executeWithRefresh();
1457
- } catch (e) {
1458
- if (e instanceof ZeroError) throw e;
1459
- lastNetworkError = e;
1460
- if (attempt < client.maxRetries) {
1461
- try {
1462
- await sleep(2 ** attempt * 200, opts.signal);
1463
- } catch (sleepErr) {
1464
- throw new ZeroApiError("Request aborted by caller", {
1465
- status: 0,
1466
- cause: sleepErr
1467
- });
1318
+ const text = await readClippedText(response, MAX_402_PROBE_BYTES);
1319
+ if (text) {
1320
+ const parsed = JSON.parse(text);
1321
+ if (looksLikeX402Body(parsed)) {
1322
+ return { protocol: "x402", raw: parsed };
1468
1323
  }
1469
- continue;
1470
- }
1471
- throw new ZeroApiError("Network request failed", {
1472
- status: 0,
1473
- cause: e
1474
- });
1475
- }
1476
- if (response.ok) {
1477
- let text;
1478
- try {
1479
- text = await response.text();
1480
- } catch (e) {
1481
- throw new ZeroApiError("Response body read failed", {
1482
- status: response.status,
1483
- cause: e
1484
- });
1485
- }
1486
- let json;
1487
- try {
1488
- json = JSON.parse(text);
1489
- } catch {
1490
1324
  }
1491
- const parsed = schema.safeParse(json);
1492
- if (!parsed.success) {
1493
- throw new ZeroValidationError(
1494
- `Response did not match expected schema for ${opts.method} ${opts.path}`,
1495
- { cause: parsed.error, body: text }
1496
- );
1497
- }
1498
- return parsed.data;
1325
+ } catch {
1499
1326
  }
1500
- if (RETRYABLE_STATUS.has(response.status) && attempt < client.maxRetries) {
1501
- try {
1502
- await sleep(2 ** attempt * 200, opts.signal);
1503
- } catch (sleepErr) {
1504
- throw new ZeroApiError("Request aborted by caller", {
1505
- status: 0,
1506
- cause: sleepErr
1507
- });
1508
- }
1509
- continue;
1327
+ }
1328
+ const wwwAuth = response.headers.get("www-authenticate");
1329
+ if (wwwAuth) {
1330
+ const schemes = splitWwwAuthenticateChallenges(wwwAuth).map(
1331
+ (challenge) => challenge.trim().split(/\s+/)[0]?.toLowerCase() ?? ""
1332
+ );
1333
+ if (schemes.some((scheme) => MPP_SCHEME_TOKENS.has(scheme))) {
1334
+ cancelBody2(response);
1335
+ return { protocol: "mpp", raw: { "www-authenticate": wwwAuth } };
1510
1336
  }
1511
- const errorBody = await parseErrorBody(response);
1512
- const requestId = response.headers.get("x-request-id") ?? void 0;
1513
- throw new ZeroApiError(errorMessageFromBody(errorBody, response.status), {
1514
- status: response.status,
1515
- requestId,
1516
- body: errorBody
1517
- });
1518
1337
  }
1519
- throw new ZeroApiError("Exhausted retries", {
1520
- status: 0,
1521
- cause: lastNetworkError
1522
- });
1338
+ cancelBody2(response);
1339
+ return { protocol: "unknown", raw: {} };
1523
1340
  };
1524
-
1525
- // src/namespaces/auth-device.ts
1526
- var AuthDevice = class {
1527
- constructor(client) {
1528
- this.client = client;
1529
- }
1530
- start = (opts = {}) => request(
1531
- this.client,
1532
- {
1533
- method: "POST",
1534
- path: "/v1/auth/device/start",
1535
- signal: opts.signal
1536
- },
1537
- deviceStartResponseSchema
1538
- );
1539
- poll = async (deviceCode, opts = {}) => {
1540
- const parsed = await request(
1541
- this.client,
1542
- {
1543
- method: "POST",
1544
- path: "/v1/auth/device/poll",
1545
- body: { deviceCode },
1546
- signal: opts.signal
1547
- },
1548
- devicePollResponseSchema
1549
- );
1550
- if ("error" in parsed) {
1551
- return parsed.error === "authorization_pending" ? {
1552
- status: "pending",
1553
- userCode: parsed.userCode,
1554
- verificationUri: parsed.verificationUri
1555
- } : { status: "expired" };
1341
+ var readClippedBytes = async (response, max) => {
1342
+ if (response.body === null) return new Uint8Array(0);
1343
+ const reader = response.body.getReader();
1344
+ const chunks = [];
1345
+ let total = 0;
1346
+ try {
1347
+ while (total < max) {
1348
+ const { done, value } = await reader.read();
1349
+ if (done) break;
1350
+ if (!value) continue;
1351
+ chunks.push(value);
1352
+ total += value.byteLength;
1556
1353
  }
1557
- return {
1558
- status: "ok",
1559
- accessToken: parsed.accessToken,
1560
- refreshToken: parsed.refreshToken,
1561
- expiresIn: parsed.expiresIn,
1562
- user: parsed.user
1563
- };
1564
- };
1354
+ } finally {
1355
+ try {
1356
+ await reader.cancel();
1357
+ } catch {
1358
+ }
1359
+ }
1360
+ const merged = new Uint8Array(Math.min(total, max));
1361
+ let offset = 0;
1362
+ for (const c of chunks) {
1363
+ const take = Math.min(c.byteLength, max - offset);
1364
+ merged.set(c.subarray(0, take), offset);
1365
+ offset += take;
1366
+ if (offset >= max) break;
1367
+ }
1368
+ return merged;
1565
1369
  };
1566
- var userWalletsSchema = z.array(userWalletDtoSchema);
1567
- var normalizeMessage = (message) => {
1568
- if (typeof message === "string") return message;
1569
- if (typeof message.raw === "string") return message.raw;
1570
- return bytesToHex(message.raw);
1370
+ var readClippedText = async (response, max) => {
1371
+ const bytes = await readClippedBytes(response, max);
1372
+ return new TextDecoder().decode(bytes);
1571
1373
  };
1572
- var repackageSignFailure = (err, op) => {
1573
- if (err instanceof ZeroApiError && err.status === 402) {
1574
- const body = err.body;
1575
- const detail = body && typeof body.error === "string" ? `: ${body.error}` : "";
1576
- throw new ZeroPaymentError(
1577
- `Sign endpoint refused ${op} (HTTP 402)${detail}`,
1578
- { cause: err }
1579
- );
1374
+ var parseResponseBody = async (response, max) => {
1375
+ const contentType = response.headers.get("content-type");
1376
+ const bytes = await readClippedBytes(response, max + 1);
1377
+ const truncated = bytes.byteLength > max;
1378
+ const view = truncated ? bytes.subarray(0, max) : bytes;
1379
+ const buf = Buffer.from(view.buffer, view.byteOffset, view.byteLength);
1380
+ if (!isTextContentType(contentType)) {
1381
+ const base64 = buf.toString("base64");
1382
+ return {
1383
+ body: base64,
1384
+ bodyRaw: base64,
1385
+ bodyEncoding: "base64",
1386
+ truncated
1387
+ };
1580
1388
  }
1581
- throw err;
1582
- };
1583
- var Auth = class {
1584
- constructor(client) {
1585
- this.client = client;
1586
- this.device = new AuthDevice(client);
1389
+ const text = buf.toString("utf8");
1390
+ if (contentType?.toLowerCase().includes("json")) {
1391
+ try {
1392
+ return { body: JSON.parse(text), bodyRaw: text, truncated };
1393
+ } catch {
1394
+ }
1587
1395
  }
1588
- device;
1589
- // The internal (web-app facing) user profile. Richer than profile(): full
1590
- // wallet list with per-wallet delegation/signer/limit metadata.
1591
- me = (opts = {}) => request(
1592
- this.client,
1593
- { method: "GET", path: "/v1/users/me", signal: opts.signal },
1594
- internalUserDtoSchema
1595
- );
1596
- // The public (agent-facing) profile — identity + primary wallet address +
1597
- // balance + welcome-bonus, no per-wallet metadata. Same shape the MCP
1598
- // get_profile tool returns; rendered by `zero auth whoami`.
1599
- profile = (opts = {}) => request(
1600
- this.client,
1601
- { method: "GET", path: "/v1/users/me/profile", signal: opts.signal },
1602
- publicUserDtoSchema
1396
+ return { body: text, bodyRaw: text, truncated };
1397
+ };
1398
+ var UPSTREAM_ERROR_FIELDS = [
1399
+ "error",
1400
+ "message",
1401
+ "detail",
1402
+ "reason",
1403
+ "error_description"
1404
+ ];
1405
+ var UPSTREAM_ERROR_MAX_LEN = 200;
1406
+ var ERROR_SNIPPET_BYTES = 500;
1407
+ var clampUpstreamMessage = (value) => {
1408
+ const trimmed = value.trim();
1409
+ if (!trimmed) return void 0;
1410
+ return trimmed.length > UPSTREAM_ERROR_MAX_LEN ? `${trimmed.slice(0, UPSTREAM_ERROR_MAX_LEN)}\u2026` : trimmed;
1411
+ };
1412
+ var tryParseJson = (text) => {
1413
+ try {
1414
+ return JSON.parse(text);
1415
+ } catch {
1416
+ return null;
1417
+ }
1418
+ };
1419
+ var extractUpstreamErrorMessage = (body) => {
1420
+ const parsed = tryParseJson(body);
1421
+ if (parsed === null || typeof parsed !== "object") {
1422
+ return clampUpstreamMessage(body);
1423
+ }
1424
+ const record = parsed;
1425
+ for (const field of UPSTREAM_ERROR_FIELDS) {
1426
+ const value = record[field];
1427
+ if (typeof value === "string" && value.length > 0) {
1428
+ return clampUpstreamMessage(value);
1429
+ }
1430
+ if (value && typeof value === "object") {
1431
+ const nested = value.message;
1432
+ if (typeof nested === "string" && nested.length > 0) {
1433
+ return clampUpstreamMessage(nested);
1434
+ }
1435
+ }
1436
+ }
1437
+ return void 0;
1438
+ };
1439
+ var buildUpstreamError = (status, bodyRaw, bodyEncoding) => {
1440
+ if (status >= 200 && status < 300) return void 0;
1441
+ if (bodyRaw === null || bodyRaw.length === 0) return void 0;
1442
+ if (bodyEncoding === "base64") return void 0;
1443
+ const snippet = bodyRaw.slice(0, ERROR_SNIPPET_BYTES);
1444
+ return {
1445
+ status,
1446
+ message: extractUpstreamErrorMessage(bodyRaw),
1447
+ snippetHash: createHash("sha256").update(snippet).digest("hex"),
1448
+ snippetLength: snippet.length
1449
+ };
1450
+ };
1451
+ var computeOutcome = (status, payment, warnings) => {
1452
+ if (warnings.includes(FETCH_WARNINGS.mppSessionCloseFailed))
1453
+ return "payment_close_failed";
1454
+ if (status === 402 && payment !== null) return "payment_rejected";
1455
+ if (status === 402) return "payment_failed";
1456
+ if (status >= 400) return "server_error";
1457
+ return "success";
1458
+ };
1459
+ var errorFetchResult = (startedAt, error, skipReason, capabilityIdRequested, outcome, status = null) => {
1460
+ const result = {
1461
+ runId: null,
1462
+ ok: false,
1463
+ status,
1464
+ latencyMs: Date.now() - startedAt,
1465
+ payment: null,
1466
+ outcome,
1467
+ body: null,
1468
+ bodyRaw: null,
1469
+ error
1470
+ };
1471
+ if (capabilityIdRequested) result.runTrackingSkipped = [skipReason];
1472
+ return result;
1473
+ };
1474
+ var resolveRecordingClient = (client, opts) => {
1475
+ const credentials = client.getCredentials();
1476
+ const recordingClient = opts.account ? client.withAccount(opts.account) : client;
1477
+ const canRecord = opts.account !== void 0 || credentials.kind === "account" || credentials.kind === "session";
1478
+ return { recordingClient, canRecord };
1479
+ };
1480
+ var recordErrorFetchRun = async (client, opts, result, capabilityIdOverride) => {
1481
+ const effectiveCapId = opts.capabilityId;
1482
+ if (effectiveCapId === void 0) return;
1483
+ const { recordingClient, canRecord } = resolveRecordingClient(client, opts);
1484
+ if (!canRecord) return;
1485
+ const fetchOriginForRecord = opts.capabilityId === void 0 ? "direct_url" : isShortToken(effectiveCapId) ? "from_search" : opts.fetchOrigin;
1486
+ try {
1487
+ const created = await recordingClient.runs.create({
1488
+ capabilityId: effectiveCapId,
1489
+ ...opts.searchId && { searchId: opts.searchId },
1490
+ ...fetchOriginForRecord && { fetchOrigin: fetchOriginForRecord },
1491
+ ...result.status !== null && { status: result.status },
1492
+ latencyMs: result.latencyMs,
1493
+ ...opts.requestSchema && { requestSchema: opts.requestSchema }
1494
+ });
1495
+ result.runId = created.runId;
1496
+ delete result.runTrackingSkipped;
1497
+ } catch {
1498
+ }
1499
+ };
1500
+ var recordTimeoutRun = async (client, opts, err, startedAt, capabilityIdRequested, capabilityIdOverride) => {
1501
+ const result = errorFetchResult(
1502
+ startedAt,
1503
+ err.message,
1504
+ "timeout",
1505
+ capabilityIdRequested,
1506
+ "network_error"
1603
1507
  );
1604
- // The user's wallets only. Lightweight sibling of `me()` for callers that
1605
- // just need wallet addresses (e.g. managed-account resolution on the pay
1606
- // path) avoids the heavier internal-profile computation behind `me()`.
1607
- wallets = (opts = {}) => request(
1608
- this.client,
1609
- { method: "GET", path: "/v1/users/me/wallets", signal: opts.signal },
1610
- userWalletsSchema
1508
+ await recordErrorFetchRun(client, opts, result);
1509
+ };
1510
+ var sniffJsonShapeBytes = (buf) => {
1511
+ let i = 0;
1512
+ if (buf.length >= 3 && buf[0] === 239 && buf[1] === 187 && buf[2] === 191) {
1513
+ i = 3;
1514
+ }
1515
+ while (i < buf.length && (buf[i] === 32 || buf[i] === 9 || buf[i] === 10 || buf[i] === 13)) {
1516
+ i++;
1517
+ }
1518
+ if (i >= buf.length) return false;
1519
+ return buf[i] === 123 || buf[i] === 91;
1520
+ };
1521
+ var withDefaultContentType = (headers, body) => {
1522
+ if (!headers && body === void 0) return void 0;
1523
+ const next = headers ? { ...headers } : {};
1524
+ if (body === void 0) return next;
1525
+ const hasContentType = Object.keys(next).some(
1526
+ (k) => k.toLowerCase() === "content-type"
1611
1527
  );
1612
- // Exchange an ephemeral session code (e.g. runner / sandbox handoff) for
1613
- // a session access token. The returned token has no refresh path — pair
1614
- // with `refreshToken: ""` when constructing the client.
1615
- exchangeSessionCode = (code, opts = {}) => request(
1616
- this.client,
1617
- {
1618
- method: "POST",
1619
- path: "/v1/session/exchange",
1620
- body: { code },
1621
- signal: opts.signal
1622
- },
1623
- sessionExchangeResponseSchema
1528
+ if (!hasContentType) {
1529
+ next["content-type"] = typeof body === "string" || sniffJsonShapeBytes(body) ? "application/json" : "application/octet-stream";
1530
+ }
1531
+ return next;
1532
+ };
1533
+ var clientFetch = async (client, url, opts = {}) => {
1534
+ const startedAt = Date.now();
1535
+ const capabilityIdRequested = opts.capabilityId !== void 0;
1536
+ const normalized = normalizeTransportEnvelopeRequest(url, {
1537
+ method: opts.method,
1538
+ body: opts.body
1539
+ });
1540
+ const requestUrl = normalized.url;
1541
+ const requestBody = normalized.body;
1542
+ const method = normalized.method ?? (requestBody !== void 0 ? "POST" : "GET");
1543
+ const { canRecord: canRecordForResolve } = resolveRecordingClient(
1544
+ client,
1545
+ opts
1624
1546
  );
1625
- // Server-side revoke of the refresh token. Throws ZeroApiError on any
1626
- // non-2xx callers that have already cleared local state (e.g. CLI
1627
- // logout) typically want to swallow with `.catch(() => {})` so a flaky
1628
- // server doesn't surface as a user-visible failure for a sign-out that
1629
- // already succeeded locally. Response body is opaque; schema is
1630
- // `z.unknown()` and the resolved value is discarded.
1631
- logout = async (refreshToken, opts = {}) => {
1632
- await request(
1633
- this.client,
1634
- {
1635
- method: "POST",
1636
- path: "/v1/auth/logout",
1637
- body: { refreshToken },
1638
- signal: opts.signal
1639
- },
1640
- z.unknown()
1641
- );
1547
+ const resolvePromise = !capabilityIdRequested && canRecordForResolve ? client.capabilities.resolveByUrl(redactUrl(requestUrl), method).catch(() => null) : Promise.resolve(null);
1548
+ const probeHeaders = withDefaultContentType(opts.headers, requestBody);
1549
+ const requestInit = {
1550
+ method,
1551
+ headers: probeHeaders,
1552
+ body: requestBody,
1553
+ signal: opts.signal
1642
1554
  };
1643
- // Force a session refresh. Routes through refreshSessionOnce() so a
1644
- // proactive call doesn't race a 401-driven transport refresh —
1645
- // concurrent POSTs with the same refresh token kill the session
1646
- // family (RFC 6819 token-theft response).
1647
- refresh = async () => {
1648
- const credentials = this.client.getCredentials();
1649
- if (credentials.kind !== "session") {
1650
- throw new ZeroAuthError(
1651
- "auth_no_credentials",
1652
- "refresh() requires the client to be constructed with a session \u2014 got credentials.kind=" + credentials.kind
1653
- );
1654
- }
1655
- const ok = await this.client.refreshSessionOnce();
1656
- if (!ok) {
1657
- throw new ZeroAuthError(
1658
- "auth_session_expired",
1659
- "Session refresh failed. The refresh token may be revoked or expired."
1555
+ const configuredFetch = buildConfiguredFetch(client, {
1556
+ timeoutMs: opts.timeoutMs
1557
+ });
1558
+ let response;
1559
+ let payment = null;
1560
+ const skipReasons = [];
1561
+ const warnings = [];
1562
+ try {
1563
+ response = await configuredFetch(requestUrl, requestInit);
1564
+ } catch (err) {
1565
+ if (err instanceof ZeroTimeoutError) {
1566
+ await recordTimeoutRun(
1567
+ client,
1568
+ opts,
1569
+ err,
1570
+ startedAt,
1571
+ capabilityIdRequested
1660
1572
  );
1573
+ throw err;
1661
1574
  }
1662
- };
1663
- signMessage = async (input, opts = {}) => {
1664
- const credentials = this.client.getCredentials();
1665
- if (credentials.kind === "account") {
1666
- return await credentials.account.signMessage({ message: input.message });
1667
- }
1668
- if (credentials.kind === "session") {
1575
+ if (err instanceof ZeroError) throw err;
1576
+ const errMessage = err instanceof Error ? err.message : String(err);
1577
+ const result2 = errorFetchResult(
1578
+ startedAt,
1579
+ errMessage,
1580
+ "fetch_failed_before_response",
1581
+ capabilityIdRequested,
1582
+ "network_error"
1583
+ );
1584
+ await recordErrorFetchRun(client, opts, result2);
1585
+ return result2;
1586
+ }
1587
+ if (response.status === 402) {
1588
+ const requirement = await detectPaymentRequirement(response);
1589
+ if (requirement?.protocol === "x402" || requirement?.protocol === "mpp") {
1590
+ const payCall = requirement.protocol === "x402" ? client.payments.pay : client.payments.payMpp;
1669
1591
  try {
1670
- const parsed = await request(
1671
- this.client,
1672
- {
1673
- method: "POST",
1674
- path: "/v1/users/me/sign-message",
1675
- body: { message: normalizeMessage(input.message) }
1676
- },
1677
- signResponseSchema
1678
- );
1679
- this.assertSignerMatches(parsed.walletAddress, opts.expectedAddress);
1680
- return parsed.signature;
1592
+ const paid = await payCall({
1593
+ url: requestUrl,
1594
+ method,
1595
+ // Fresh clone for the paid retry — same rationale as
1596
+ // the probe-side clone above. Re-derives the default
1597
+ // Content-Type so the paid leg matches the probe.
1598
+ headers: withDefaultContentType(opts.headers, requestBody),
1599
+ body: requestBody,
1600
+ maxPay: opts.maxPay,
1601
+ account: opts.account,
1602
+ signal: opts.signal,
1603
+ timeoutMs: opts.timeoutMs,
1604
+ ...opts.displayCostAmount && {
1605
+ displayCostAmount: opts.displayCostAmount
1606
+ }
1607
+ });
1608
+ response = paid.response;
1609
+ payment = paid.payment;
1610
+ if (paid.warnings) warnings.push(...paid.warnings);
1681
1611
  } catch (err) {
1682
- repackageSignFailure(err, "signMessage");
1612
+ if (err instanceof ZeroSessionCloseFailedError) {
1613
+ response = err.response;
1614
+ payment = {
1615
+ protocol: "mpp",
1616
+ chain: tempoChainLabelFromId(err.session.chainId),
1617
+ txHash: null,
1618
+ amount: err.capturedAmount,
1619
+ asset: "USDC",
1620
+ session: err.session
1621
+ };
1622
+ warnings.push(FETCH_WARNINGS.mppSessionCloseFailed);
1623
+ } else if (err instanceof ZeroInsufficientFundsError) {
1624
+ return errorFetchResult(
1625
+ startedAt,
1626
+ err.message,
1627
+ FETCH_SKIP_REASONS.insufficientFunds,
1628
+ capabilityIdRequested,
1629
+ "insufficient_funds",
1630
+ null
1631
+ );
1632
+ } else if (err instanceof ZeroPaymentError || err instanceof ZeroAuthError) {
1633
+ const result2 = errorFetchResult(
1634
+ startedAt,
1635
+ err.message,
1636
+ "payment_failed",
1637
+ capabilityIdRequested,
1638
+ "payment_failed",
1639
+ 402
1640
+ );
1641
+ await recordErrorFetchRun(client, opts, result2);
1642
+ return result2;
1643
+ } else if (err instanceof ZeroTimeoutError) {
1644
+ await recordTimeoutRun(
1645
+ client,
1646
+ opts,
1647
+ err,
1648
+ startedAt,
1649
+ capabilityIdRequested
1650
+ );
1651
+ throw err;
1652
+ } else {
1653
+ throw err;
1654
+ }
1683
1655
  }
1656
+ } else if (requirement?.protocol === "unknown") {
1657
+ const result2 = errorFetchResult(
1658
+ startedAt,
1659
+ "Capability returned 402 with an unrecognized payment protocol. Neither x402 nor MPP markers were detected in the response headers or body.",
1660
+ "unrecognized_402_protocol",
1661
+ capabilityIdRequested,
1662
+ "payment_failed",
1663
+ 402
1664
+ );
1665
+ await recordErrorFetchRun(client, opts, result2);
1666
+ return result2;
1684
1667
  }
1685
- throw new ZeroAuthError(
1686
- "auth_no_credentials",
1687
- "signMessage() requires the client to be constructed with `account` or `session`."
1688
- );
1689
- };
1690
- // Managed-only: BYO accounts sign locally via viem's account.signTransaction.
1691
- signTransaction = async (input, opts = {}) => {
1692
- const credentials = this.client.getCredentials();
1693
- if (credentials.kind === "session") {
1694
- try {
1695
- const parsed = await request(
1696
- this.client,
1697
- {
1698
- method: "POST",
1699
- path: "/v1/users/me/sign-transaction",
1700
- body: { unsignedTransaction: input.unsignedTransaction }
1701
- },
1702
- signResponseSchema
1703
- );
1704
- this.assertSignerMatches(parsed.walletAddress, opts.expectedAddress);
1705
- return parsed.signature;
1706
- } catch (err) {
1707
- repackageSignFailure(err, "signTransaction");
1708
- }
1668
+ }
1669
+ const latencyMs = Date.now() - startedAt;
1670
+ const maxResponseBytes = opts.maxResponseBytes ?? MAX_RESPONSE_BYTES;
1671
+ let body;
1672
+ let bodyRaw;
1673
+ let bodyEncoding;
1674
+ try {
1675
+ const parsed = await parseResponseBody(response, maxResponseBytes);
1676
+ body = parsed.body;
1677
+ bodyRaw = parsed.bodyRaw;
1678
+ bodyEncoding = parsed.bodyEncoding;
1679
+ if (parsed.truncated) warnings.push(FETCH_WARNINGS.bodyTruncated);
1680
+ } catch (err) {
1681
+ if (err instanceof Error && err.name === "AbortError") {
1682
+ throw new ZeroApiError("Request aborted by caller", {
1683
+ status: 0,
1684
+ cause: err
1685
+ });
1709
1686
  }
1710
- if (credentials.kind === "account") {
1711
- throw new ZeroAuthError(
1712
- "auth_error",
1713
- "signTransaction() is a managed-signing operation and is not supported in account mode. Sign locally with viem's account.signTransaction instead."
1714
- );
1687
+ const message = err instanceof Error ? err.message : String(err);
1688
+ const skips = [];
1689
+ const settledLocally = paymentHasAnchor(payment);
1690
+ if (settledLocally && response.status === 402) {
1691
+ warnings.push(FETCH_WARNINGS.paidButStill402NotRecorded);
1692
+ skips.push(FETCH_WARNINGS.paidButStill402NotRecorded);
1715
1693
  }
1716
- throw new ZeroAuthError(
1717
- "auth_no_credentials",
1718
- "signTransaction() requires session credentials (managed signing)."
1719
- );
1720
- };
1721
- signTypedData = async (input, opts = {}) => {
1722
- const credentials = this.client.getCredentials();
1723
- if (credentials.kind === "account") {
1724
- if (!credentials.account.signTypedData) {
1725
- throw new ZeroAuthError(
1726
- "auth_error",
1727
- "The configured viem account does not implement signTypedData."
1728
- );
1729
- }
1730
- return await credentials.account.signTypedData(input);
1694
+ if (payment !== null && !settledLocally) {
1695
+ warnings.push(FETCH_WARNINGS.paymentSettlementUnconfirmed);
1696
+ skips.push(FETCH_WARNINGS.paymentSettlementUnconfirmed);
1731
1697
  }
1732
- if (credentials.kind === "session") {
1733
- try {
1734
- const parsed = await request(
1735
- this.client,
1736
- {
1737
- method: "POST",
1738
- path: "/v1/users/me/sign-typed-data",
1739
- body: { typedData: input }
1740
- },
1741
- signResponseSchema
1742
- );
1743
- this.assertSignerMatches(parsed.walletAddress, opts.expectedAddress);
1744
- return parsed.signature;
1745
- } catch (err) {
1746
- repackageSignFailure(err, "signTypedData");
1747
- }
1698
+ if (payment !== null && payment.amount === "unknown") {
1699
+ warnings.push(FETCH_WARNINGS.paymentAmountUnknown);
1748
1700
  }
1749
- throw new ZeroAuthError(
1750
- "auth_no_credentials",
1751
- "signTypedData() requires the client to be constructed with `account` or `session`."
1752
- );
1753
- };
1754
- // Compare against the caller's pinned address (not shared cache) so
1755
- // concurrent pay()s can't race past each other. Mismatch → clear + throw;
1756
- // the caller's retry rebuilds against the new wallet.
1757
- assertSignerMatches = (serverAddr, expectedAddr) => {
1758
- if (!expectedAddr) return;
1759
- const canonicalServer = getAddress(serverAddr);
1760
- const canonicalExpected = getAddress(expectedAddr);
1761
- if (canonicalExpected === canonicalServer) return;
1762
- this.client.clearManagedAccount();
1763
- throw new ZeroAuthError(
1764
- "auth_error",
1765
- `Managed signing wallet changed (caller expected ${canonicalExpected}, server signed as ${canonicalServer}). Cache cleared \u2014 retry to rebuild against the current wallet.`
1766
- );
1767
- };
1768
- };
1769
- var BUG_REPORT_CATEGORIES = [
1770
- "search_relevance",
1771
- "ranking_issue",
1772
- "missing_capability",
1773
- "wrong_schema",
1774
- "misleading_description",
1775
- "broken_execution",
1776
- "payment_failure",
1777
- "billing_anomaly",
1778
- "cli_bug",
1779
- "security",
1780
- "other"
1781
- ];
1782
- var createBugReportResponseSchema = z.object({
1783
- bugReportId: z.string(),
1784
- status: z.string(),
1785
- deduped: z.boolean(),
1786
- category: z.enum(BUG_REPORT_CATEGORIES).nullable(),
1787
- title: z.string().nullable(),
1788
- attached: z.object({
1789
- capabilityId: z.number().nullable(),
1790
- runId: z.number().nullable(),
1791
- searchId: z.number().nullable()
1792
- })
1793
- });
1794
-
1795
- // src/namespaces/bug-reports.ts
1796
- var BugReports = class {
1797
- constructor(client) {
1798
- this.client = client;
1701
+ skips.push(FETCH_SKIP_REASONS.bodyReadFailed);
1702
+ const result2 = {
1703
+ runId: null,
1704
+ ok: false,
1705
+ status: response.status,
1706
+ latencyMs: Date.now() - startedAt,
1707
+ payment,
1708
+ outcome: computeOutcome(response.status, payment, warnings),
1709
+ body: null,
1710
+ bodyRaw: null,
1711
+ error: message
1712
+ };
1713
+ if (capabilityIdRequested) result2.runTrackingSkipped = skips;
1714
+ if (warnings.length > 0) result2.warnings = warnings;
1715
+ return result2;
1799
1716
  }
1800
- // POST /v1/bug-reports. Wallet-attributed server-side; the SDK client's
1801
- // active credential drives attribution (account-mode EIP-191 from the
1802
- // wallet, session-mode Bearer + server resolves the user's wallet).
1803
- create = (input, opts = {}) => request(
1804
- this.client,
1805
- {
1806
- method: "POST",
1807
- path: "/v1/bug-reports",
1808
- body: input,
1809
- signal: opts.signal
1810
- },
1811
- createBugReportResponseSchema
1812
- );
1813
- };
1814
- var ratingSchema = z.object({
1815
- score: z.string(),
1816
- successRate: z.string(),
1817
- reviews: z.number(),
1818
- stars: z.string().nullable().optional(),
1819
- state: z.enum(["unrated", "rated"]).optional()
1820
- });
1821
-
1822
- // src/schemas/capabilities.ts
1823
- var capabilityResponseSchema = z.object({
1824
- uid: z.string(),
1825
- slug: z.string(),
1826
- name: z.string(),
1827
- description: z.string(),
1828
- url: z.string(),
1829
- urlTemplate: z.string().nullable().optional(),
1830
- method: z.string(),
1831
- headers: z.record(z.string(), z.string()).nullable(),
1832
- bodySchema: z.record(z.string(), z.unknown()).nullable(),
1833
- responseSchema: z.record(z.string(), z.unknown()).nullable(),
1834
- example: z.object({ request: z.unknown(), response: z.unknown() }).nullable(),
1835
- tags: z.array(z.string()).nullable(),
1836
- exampleAgentPrompt: z.string().nullable().optional(),
1837
- displayCostAmount: z.string(),
1838
- displayCostAsset: z.string(),
1839
- reviewCount: z.number(),
1840
- rating: ratingSchema,
1841
- priceObserved: z.object({
1842
- minCents: z.string().nullable(),
1843
- medianCents: z.string().nullable(),
1844
- maxCents: z.string().nullable(),
1845
- // Deprecated alias for maxCents; kept for back-compat with older
1846
- // API versions that still emit it.
1847
- p95Cents: z.string().nullable(),
1848
- sampleCount: z.number(),
1849
- varies: z.boolean(),
1850
- failureChargeRate: z.number().nullable()
1851
- }).nullable().optional(),
1852
- paymentMethods: z.array(
1853
- z.object({
1854
- uid: z.string(),
1855
- protocol: z.string(),
1856
- methodType: z.string(),
1857
- chain: z.string().nullable(),
1858
- mode: z.string(),
1859
- costAmount: z.string(),
1860
- costPer: z.string(),
1861
- priority: z.number()
1862
- })
1863
- ).nullable(),
1864
- availabilityStatus: z.enum(["healthy", "unknown", "down"]).nullable().optional(),
1865
- /**
1866
- * @deprecated The API no longer emits `displayStatus`; it has been replaced
1867
- * by the consolidated 3-value {@link capabilityResponseSchema.shape.availabilityStatus}.
1868
- * Kept as an optional field for one release for back-compat (ZERO-89 soft
1869
- * migration) and will be removed in the next major version.
1870
- */
1871
- displayStatus: z.enum(["healthy", "stable", "degraded", "unhealthy", "unknown"]).optional(),
1872
- activationCount: z.number().optional(),
1873
- lastUsedAt: z.string().nullable().optional(),
1874
- lastSuccessfullyRanAt: z.string().nullable().optional()
1875
- });
1876
- var resolveCapabilityResponseSchema = z.object({
1877
- capabilityId: z.string(),
1878
- capabilitySlug: z.string(),
1879
- matchedVia: z.enum(["exact", "template"])
1880
- });
1881
-
1882
- // src/namespaces/capabilities.ts
1883
- var Capabilities = class {
1884
- constructor(client) {
1885
- this.client = client;
1717
+ const settled = paymentHasAnchor(payment);
1718
+ const paidButStill402 = settled && response.status === 402;
1719
+ const settlementUnconfirmed = payment !== null && !settled;
1720
+ if (paidButStill402) warnings.push(FETCH_WARNINGS.paidButStill402NotRecorded);
1721
+ if (settlementUnconfirmed)
1722
+ warnings.push(FETCH_WARNINGS.paymentSettlementUnconfirmed);
1723
+ if (payment !== null && payment.amount === "unknown") {
1724
+ warnings.push(FETCH_WARNINGS.paymentAmountUnknown);
1886
1725
  }
1887
- get = (id, opts = {}) => request(
1888
- this.client,
1889
- {
1890
- method: "GET",
1891
- path: `/v1/capabilities/${encodeURIComponent(id)}`,
1892
- query: opts.searchId ? { searchId: opts.searchId } : void 0,
1893
- signal: opts.signal
1894
- },
1895
- capabilityResponseSchema
1726
+ let runId = null;
1727
+ const closeFailed = warnings.includes(FETCH_WARNINGS.mppSessionCloseFailed);
1728
+ const upstreamErrorForRecord = buildUpstreamError(
1729
+ response.status,
1730
+ bodyRaw,
1731
+ bodyEncoding
1896
1732
  );
1897
- // Resolve a raw endpoint URL + method back to the capability that owns it,
1898
- // for the `zero fetch <url>` case where the client has no local search
1899
- // context to match against (a memoized/cross-session URL). Returns null on
1900
- // 404 — i.e. no match or an ambiguous multi-match, both of which the server
1901
- // reports as 404 (resolveByUrl fails closed rather than mis-attribute).
1902
- // The URL travels in the POST body so it stays out of access logs and is
1903
- // never stored; the caller uses the returned capabilityId to record an
1904
- // attributed run without the URL ever touching the run row (ZERO-335).
1905
- resolveByUrl = async (url, method, opts = {}) => {
1906
- try {
1907
- return await request(
1908
- this.client,
1909
- {
1910
- method: "POST",
1911
- path: "/v1/capabilities/resolve",
1912
- body: { url, method },
1913
- signal: opts.signal
1914
- },
1915
- resolveCapabilityResponseSchema
1916
- );
1917
- } catch (err) {
1918
- if (err instanceof ZeroApiError && err.status === 404) return null;
1919
- throw err;
1733
+ if (capabilityIdRequested) {
1734
+ if (paidButStill402)
1735
+ skipReasons.push(FETCH_WARNINGS.paidButStill402NotRecorded);
1736
+ else if (closeFailed)
1737
+ skipReasons.push(FETCH_SKIP_REASONS.mppSessionCloseFailedNotRecorded);
1738
+ }
1739
+ const resolvedCap = await resolvePromise;
1740
+ const effectiveCapabilityId = opts.capabilityId ?? resolvedCap?.capabilityId;
1741
+ const capabilityResolution = !capabilityIdRequested && canRecordForResolve ? resolvedCap !== null ? {
1742
+ resolved: true,
1743
+ capabilityId: resolvedCap.capabilityId,
1744
+ capabilitySlug: resolvedCap.capabilitySlug,
1745
+ matchedVia: resolvedCap.matchedVia
1746
+ } : { resolved: false } : void 0;
1747
+ if (effectiveCapabilityId !== void 0 && !paidButStill402 && !closeFailed) {
1748
+ const { recordingClient, canRecord } = resolveRecordingClient(client, opts);
1749
+ if (!canRecord) {
1750
+ skipReasons.push("no_credentials");
1751
+ } else {
1752
+ const knownAmount = payment !== null && payment.amount !== "unknown";
1753
+ try {
1754
+ let derived;
1755
+ if (opts.deriveRunMetadata) {
1756
+ try {
1757
+ derived = opts.deriveRunMetadata({
1758
+ status: response.status,
1759
+ ok: response.ok,
1760
+ bodyRaw,
1761
+ ...bodyEncoding && { bodyEncoding }
1762
+ });
1763
+ } catch {
1764
+ }
1765
+ }
1766
+ const result2 = await recordingClient.runs.create({
1767
+ capabilityId: effectiveCapabilityId,
1768
+ searchId: opts.searchId,
1769
+ // Auto-resolved runs are always direct_url — they only fire when
1770
+ // no capabilityId was supplied (the direct_url path by definition).
1771
+ ...opts.capabilityId === void 0 ? { fetchOrigin: "direct_url" } : opts.fetchOrigin && { fetchOrigin: opts.fetchOrigin },
1772
+ status: response.status,
1773
+ latencyMs,
1774
+ ...opts.requestSchema && { requestSchema: opts.requestSchema },
1775
+ ...derived?.responseSchema && {
1776
+ responseSchema: derived.responseSchema
1777
+ },
1778
+ ...upstreamErrorForRecord && {
1779
+ errorSnippetHash: upstreamErrorForRecord.snippetHash,
1780
+ errorSnippetLength: upstreamErrorForRecord.snippetLength
1781
+ },
1782
+ ...payment && {
1783
+ ...knownAmount && {
1784
+ costAmount: payment.amount,
1785
+ costAsset: payment.asset
1786
+ },
1787
+ paymentProtocol: payment.protocol,
1788
+ paymentChain: payment.chain,
1789
+ paymentTxHash: payment.txHash ?? void 0,
1790
+ paymentMode: payment.session ? "session" : "charge"
1791
+ }
1792
+ });
1793
+ runId = result2.runId;
1794
+ } catch (err) {
1795
+ const errorMessage2 = err instanceof Error ? err.message : String(err);
1796
+ const code = err instanceof ZeroError ? err.code : void 0;
1797
+ const skipMsg = code ? `run_create_failed:${code}: ${errorMessage2}` : `run_create_failed: ${errorMessage2}`;
1798
+ skipReasons.push(skipMsg);
1799
+ client.logger?.({
1800
+ level: "warn",
1801
+ message: "Failed to record run for fetch",
1802
+ meta: {
1803
+ url: redactUrl(url),
1804
+ capabilityId: effectiveCapabilityId,
1805
+ error: errorMessage2,
1806
+ errorCode: code
1807
+ }
1808
+ });
1809
+ }
1920
1810
  }
1811
+ }
1812
+ const result = {
1813
+ runId,
1814
+ ok: response.ok,
1815
+ status: response.status,
1816
+ latencyMs,
1817
+ payment,
1818
+ outcome: computeOutcome(response.status, payment, warnings),
1819
+ body,
1820
+ bodyRaw
1921
1821
  };
1822
+ if (bodyEncoding) result.bodyEncoding = bodyEncoding;
1823
+ if (!response.ok) {
1824
+ const candidate = bodyRaw !== null && bodyRaw.length > 0 && bodyEncoding !== "base64" ? bodyRaw.slice(0, 240) : null;
1825
+ result.error = candidate !== null && !requestBodyEchoed(candidate, opts.body) ? candidate : `HTTP ${response.status}`;
1826
+ if (upstreamErrorForRecord) result.upstreamError = upstreamErrorForRecord;
1827
+ }
1828
+ if (capabilityIdRequested && skipReasons.length > 0) {
1829
+ result.runTrackingSkipped = skipReasons;
1830
+ }
1831
+ if (warnings.length > 0) result.warnings = warnings;
1832
+ if (capabilityResolution !== void 0)
1833
+ result.capabilityResolution = capabilityResolution;
1834
+ return result;
1922
1835
  };
1923
- var createRunResponseSchema = z.object({
1924
- runId: z.string()
1925
- });
1926
- var runListItemSchema = z.object({
1927
- uid: z.string(),
1928
- capabilityUid: z.string(),
1929
- capabilitySlug: z.string(),
1930
- capabilityName: z.string(),
1931
- status: z.number().nullable(),
1932
- latencyMs: z.number().nullable(),
1933
- cost: z.object({ amount: z.string(), asset: z.string().nullable() }).nullable(),
1934
- payment: z.object({
1935
- protocol: z.string(),
1936
- chain: z.string().nullable(),
1937
- txHash: z.string().nullable(),
1938
- mode: z.string().nullable()
1939
- }).nullable(),
1940
- createdAt: z.coerce.date(),
1941
- reviewed: z.boolean()
1836
+
1837
+ // package.json
1838
+ var package_default = {
1839
+ version: "0.8.0"};
1840
+
1841
+ // src/version.ts
1842
+ var SDK_VERSION = package_default.version;
1843
+ var userWalletDtoSchema = z.object({
1844
+ walletAddress: z.string(),
1845
+ source: z.enum(["self_custody", "privy_embedded"]),
1846
+ isPrimary: z.boolean(),
1847
+ linkedAt: z.union([z.string(), z.date()]).nullable().optional(),
1848
+ delegationGranted: z.boolean(),
1849
+ signerQuorumId: z.string().nullable(),
1850
+ // Per-transaction USDC cap in base units (6 dp). null = unlimited, 0 = free-only.
1851
+ maxTransactionUsdcBaseUnits: z.number().int().nullable()
1942
1852
  });
1943
- var listRunsResponseSchema = z.object({
1944
- runs: z.array(runListItemSchema),
1945
- nextCursor: z.string().nullable()
1853
+ var welcomeBonusSummarySchema = z.object({
1854
+ status: z.string(),
1855
+ amountUsd: z.number()
1946
1856
  });
1947
- var runDetailSchema = z.object({
1948
- runId: z.string(),
1949
- status: z.number().nullable(),
1950
- latencyMs: z.number().nullable(),
1951
- errorClass: z.string().nullable(),
1952
- createdAt: z.coerce.date(),
1953
- cost: z.object({ amount: z.string(), asset: z.string().nullable() }).nullable(),
1954
- payment: z.object({
1955
- protocol: z.string(),
1956
- chain: z.string().nullable(),
1957
- txHash: z.string().nullable(),
1958
- mode: z.string().nullable()
1959
- }).nullable(),
1960
- capability: z.object({
1961
- uid: z.string(),
1962
- slug: z.string(),
1963
- name: z.string(),
1964
- url: z.string(),
1965
- method: z.string(),
1966
- whatItDoes: z.string().nullable(),
1967
- tags: z.array(z.string())
1857
+ var internalUserDtoSchema = z.object({
1858
+ id: z.string(),
1859
+ email: z.string().nullable(),
1860
+ createdAt: z.union([z.string(), z.date()]).optional(),
1861
+ lastLoginAt: z.union([z.string(), z.date()]).nullable().optional(),
1862
+ onboardingCompleted: z.boolean().optional(),
1863
+ delegationGranted: z.boolean().optional(),
1864
+ wallets: z.array(userWalletDtoSchema).optional(),
1865
+ balance: z.object({ amount: z.string(), asset: z.string() }).nullable().optional(),
1866
+ welcomeBonus: welcomeBonusSummarySchema.nullable().optional()
1867
+ });
1868
+ var publicUserDtoSchema = z.object({
1869
+ user: z.object({
1870
+ id: z.string(),
1871
+ email: z.string().nullable()
1968
1872
  }),
1969
- reviewed: z.boolean()
1873
+ walletAddress: z.string().nullable(),
1874
+ balance: z.object({ amount: z.string(), asset: z.string() }).nullable(),
1875
+ welcomeBonus: welcomeBonusSummarySchema.nullable()
1970
1876
  });
1971
- var createReviewResponseSchema = z.object({
1972
- reviewId: z.string(),
1973
- recorded: z.boolean(),
1974
- updated: z.boolean().optional()
1877
+ var signResponseSchema = z.object({
1878
+ signature: z.string().regex(/^0x[0-9a-fA-F]+$/, "must be 0x-prefixed hex"),
1879
+ walletAddress: z.string().regex(/^0x[0-9a-fA-F]{40}$/, "must be 0x-prefixed 40-char hex address")
1975
1880
  });
1976
- var batchReviewResponseSchema = z.object({
1977
- results: z.array(
1978
- z.union([
1979
- z.object({
1980
- runId: z.string(),
1981
- ok: z.literal(true),
1982
- reviewId: z.string(),
1983
- updated: z.boolean()
1984
- }),
1985
- z.object({
1986
- runId: z.string(),
1987
- ok: z.literal(false),
1988
- status: z.number(),
1989
- error: z.string()
1990
- })
1991
- ])
1992
- ),
1993
- summary: z.object({
1994
- total: z.number(),
1995
- ok: z.number(),
1996
- failed: z.number()
1881
+
1882
+ // src/schemas/device.ts
1883
+ var deviceStartResponseSchema = z.object({
1884
+ deviceCode: z.string(),
1885
+ userCode: z.string(),
1886
+ verificationUri: z.string(),
1887
+ pollInterval: z.number(),
1888
+ expiresAt: z.number()
1889
+ });
1890
+ var devicePollResponseSchema = z.union([
1891
+ // userCode + verificationUri are optional: older API deployments don't
1892
+ // echo them on pending.
1893
+ z.object({
1894
+ error: z.literal("authorization_pending"),
1895
+ userCode: z.string().optional(),
1896
+ verificationUri: z.string().optional()
1897
+ }),
1898
+ z.object({ error: z.literal("expired_token") }),
1899
+ z.object({
1900
+ accessToken: z.string(),
1901
+ refreshToken: z.string(),
1902
+ expiresIn: z.number(),
1903
+ user: internalUserDtoSchema
1997
1904
  })
1905
+ ]);
1906
+ var sessionExchangeResponseSchema = z.object({
1907
+ token: z.string(),
1908
+ expiresAt: z.number()
1909
+ });
1910
+ var refreshResponseSchema = z.object({
1911
+ accessToken: z.string(),
1912
+ refreshToken: z.string()
1913
+ });
1914
+ var tryRefreshSession = async (client, callerSignal) => {
1915
+ const credentials = client.getCredentials();
1916
+ if (credentials.kind !== "session") return false;
1917
+ if (!credentials.refreshToken) return false;
1918
+ const url = `${client.baseUrl.replace(/\/$/, "")}/v1/auth/refresh`;
1919
+ const timeoutSignal = AbortSignal.timeout(client.timeout);
1920
+ const signal = callerSignal ? AbortSignal.any([callerSignal, timeoutSignal]) : timeoutSignal;
1921
+ let response;
1922
+ try {
1923
+ response = await client.fetchImpl(url, {
1924
+ method: "POST",
1925
+ headers: { "content-type": "application/json" },
1926
+ body: JSON.stringify({ refreshToken: credentials.refreshToken }),
1927
+ signal
1928
+ });
1929
+ } catch {
1930
+ return false;
1931
+ }
1932
+ if (!response.ok) return false;
1933
+ let body;
1934
+ try {
1935
+ body = await response.json();
1936
+ } catch {
1937
+ return false;
1938
+ }
1939
+ const parsed = refreshResponseSchema.safeParse(body);
1940
+ if (!parsed.success) return false;
1941
+ await client.applyRefreshedTokens({
1942
+ accessToken: parsed.data.accessToken,
1943
+ refreshToken: parsed.data.refreshToken
1944
+ });
1945
+ return true;
1946
+ };
1947
+ var buildSessionAuthHeaders = (accessToken) => ({
1948
+ authorization: `Bearer ${accessToken}`
1949
+ });
1950
+ var buildCanonicalMessage = (method, pathWithQuery, body, timestamp, nonce) => {
1951
+ const bodyHash = createHash("sha256").update(body ?? "").digest("hex");
1952
+ return `${method}:${pathWithQuery}:${bodyHash}:${timestamp}:${nonce}`;
1953
+ };
1954
+ var buildAccountAuthHeaders = async (account, method, pathWithQuery, body) => {
1955
+ const timestamp = Math.floor(Date.now() / 1e3).toString();
1956
+ const nonce = crypto.randomUUID();
1957
+ const message = buildCanonicalMessage(
1958
+ method,
1959
+ pathWithQuery,
1960
+ body,
1961
+ timestamp,
1962
+ nonce
1963
+ );
1964
+ const signature = await account.signMessage({ message });
1965
+ return {
1966
+ "x-zero-address": account.address,
1967
+ "x-zero-timestamp": timestamp,
1968
+ "x-zero-nonce": nonce,
1969
+ "x-zero-signature": signature
1970
+ };
1971
+ };
1972
+
1973
+ // src/transport/http.ts
1974
+ var RETRYABLE_STATUS = /* @__PURE__ */ new Set([408, 502, 503, 504]);
1975
+ var sleep = (ms, signal) => new Promise((resolve, reject) => {
1976
+ if (signal?.aborted) {
1977
+ reject(new DOMException("Aborted", "AbortError"));
1978
+ return;
1979
+ }
1980
+ const timer = setTimeout(() => {
1981
+ signal?.removeEventListener("abort", onAbort);
1982
+ resolve();
1983
+ }, ms);
1984
+ const onAbort = () => {
1985
+ clearTimeout(timer);
1986
+ reject(new DOMException("Aborted", "AbortError"));
1987
+ };
1988
+ signal?.addEventListener("abort", onAbort, { once: true });
1998
1989
  });
1990
+ var buildPathWithQuery = (path, query) => {
1991
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
1992
+ if (!query) return normalizedPath;
1993
+ const params = new URLSearchParams();
1994
+ for (const [key, value] of Object.entries(query)) {
1995
+ if (value !== void 0) params.set(key, String(value));
1996
+ }
1997
+ const qs = params.toString();
1998
+ return qs ? `${normalizedPath}?${qs}` : normalizedPath;
1999
+ };
2000
+ var joinUrl = (baseUrl, pathWithQuery) => {
2001
+ const base3 = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
2002
+ return `${base3}${pathWithQuery}`;
2003
+ };
2004
+ var buildHeaders = async (client, method, pathWithQuery, hasBody, bodyStr) => {
2005
+ const headers = { ...client.defaultHeaders };
2006
+ if (hasBody) headers["content-type"] = "application/json";
2007
+ headers["x-zero-sdk-version"] = SDK_VERSION;
2008
+ if (client.apiVersion) headers["x-zero-api-version"] = client.apiVersion;
2009
+ const credentials = client.getCredentials();
2010
+ if (credentials.kind === "session") {
2011
+ Object.assign(headers, buildSessionAuthHeaders(credentials.accessToken));
2012
+ } else if (credentials.kind === "account") {
2013
+ const signed = await buildAccountAuthHeaders(
2014
+ credentials.account,
2015
+ method,
2016
+ pathWithQuery,
2017
+ bodyStr
2018
+ );
2019
+ Object.assign(headers, signed);
2020
+ }
2021
+ return headers;
2022
+ };
2023
+ var parseErrorBody = async (response) => {
2024
+ try {
2025
+ return await response.clone().json();
2026
+ } catch {
2027
+ try {
2028
+ return await response.text();
2029
+ } catch {
2030
+ return void 0;
2031
+ }
2032
+ }
2033
+ };
2034
+ var errorMessageFromBody = (body, status) => {
2035
+ if (body !== null && typeof body === "object" && "error" in body && typeof body.error === "string") {
2036
+ return body.error;
2037
+ }
2038
+ return `HTTP ${status}`;
2039
+ };
2040
+ var request = async (client, opts, schema) => {
2041
+ const pathWithQuery = buildPathWithQuery(opts.path, opts.query);
2042
+ const url = joinUrl(client.baseUrl, pathWithQuery);
2043
+ const hasBody = opts.body !== void 0;
2044
+ const bodyStr = hasBody ? JSON.stringify(
2045
+ opts.body,
2046
+ (_k, v) => typeof v === "bigint" ? v.toString() : v
2047
+ ) : void 0;
2048
+ const configuredFetch = buildConfiguredFetch(client);
2049
+ const performOnce = async () => configuredFetch(url, {
2050
+ method: opts.method,
2051
+ headers: await buildHeaders(
2052
+ client,
2053
+ opts.method,
2054
+ pathWithQuery,
2055
+ hasBody,
2056
+ bodyStr
2057
+ ),
2058
+ body: bodyStr,
2059
+ signal: opts.signal
2060
+ });
2061
+ let refreshAttempted = false;
2062
+ const executeWithRefresh = async () => {
2063
+ const response = await performOnce();
2064
+ if (response.status !== 401 || refreshAttempted) return response;
2065
+ const credentials = client.getCredentials();
2066
+ if (credentials.kind !== "session") return response;
2067
+ refreshAttempted = true;
2068
+ const refreshed = await client.refreshSessionOnce(opts.signal);
2069
+ if (!refreshed) return response;
2070
+ return await performOnce();
2071
+ };
2072
+ let lastNetworkError;
2073
+ for (let attempt = 0; attempt <= client.maxRetries; attempt++) {
2074
+ let response;
2075
+ try {
2076
+ response = await executeWithRefresh();
2077
+ } catch (e) {
2078
+ if (e instanceof ZeroError) throw e;
2079
+ lastNetworkError = e;
2080
+ if (attempt < client.maxRetries) {
2081
+ try {
2082
+ await sleep(2 ** attempt * 200, opts.signal);
2083
+ } catch (sleepErr) {
2084
+ throw new ZeroApiError("Request aborted by caller", {
2085
+ status: 0,
2086
+ cause: sleepErr
2087
+ });
2088
+ }
2089
+ continue;
2090
+ }
2091
+ throw new ZeroApiError("Network request failed", {
2092
+ status: 0,
2093
+ cause: e
2094
+ });
2095
+ }
2096
+ if (response.ok) {
2097
+ let text;
2098
+ try {
2099
+ text = await response.text();
2100
+ } catch (e) {
2101
+ throw new ZeroApiError("Response body read failed", {
2102
+ status: response.status,
2103
+ cause: e
2104
+ });
2105
+ }
2106
+ let json;
2107
+ try {
2108
+ json = JSON.parse(text);
2109
+ } catch {
2110
+ }
2111
+ const parsed = schema.safeParse(json);
2112
+ if (!parsed.success) {
2113
+ throw new ZeroValidationError(
2114
+ `Response did not match expected schema for ${opts.method} ${opts.path}`,
2115
+ { cause: parsed.error, body: text }
2116
+ );
2117
+ }
2118
+ return parsed.data;
2119
+ }
2120
+ if (RETRYABLE_STATUS.has(response.status) && attempt < client.maxRetries) {
2121
+ try {
2122
+ await sleep(2 ** attempt * 200, opts.signal);
2123
+ } catch (sleepErr) {
2124
+ throw new ZeroApiError("Request aborted by caller", {
2125
+ status: 0,
2126
+ cause: sleepErr
2127
+ });
2128
+ }
2129
+ continue;
2130
+ }
2131
+ const errorBody = await parseErrorBody(response);
2132
+ const requestId = response.headers.get("x-request-id") ?? void 0;
2133
+ throw new ZeroApiError(errorMessageFromBody(errorBody, response.status), {
2134
+ status: response.status,
2135
+ requestId,
2136
+ body: errorBody
2137
+ });
2138
+ }
2139
+ throw new ZeroApiError("Exhausted retries", {
2140
+ status: 0,
2141
+ cause: lastNetworkError
2142
+ });
2143
+ };
1999
2144
 
2000
- // src/namespaces/runs.ts
2001
- var Runs = class {
2145
+ // src/namespaces/auth-device.ts
2146
+ var AuthDevice = class {
2002
2147
  constructor(client) {
2003
2148
  this.client = client;
2004
2149
  }
2005
- create = (input, opts = {}) => request(
2150
+ start = (opts = {}) => request(
2006
2151
  this.client,
2007
2152
  {
2008
2153
  method: "POST",
2009
- path: "/v1/runs",
2010
- body: input,
2154
+ path: "/v1/auth/device/start",
2011
2155
  signal: opts.signal
2012
2156
  },
2013
- createRunResponseSchema
2157
+ deviceStartResponseSchema
2014
2158
  );
2015
- list = (params = {}, opts = {}) => {
2016
- if (params.limit === 0) {
2017
- if (opts.signal?.aborted) {
2018
- return Promise.reject(
2019
- new ZeroApiError("Request aborted by caller", { status: 0 })
2020
- );
2021
- }
2022
- if (params.cursor === void 0) {
2023
- return Promise.reject(
2024
- new ZeroApiError(
2025
- "runs.list({ limit: 0 }) requires a cursor \u2014 without one the empty page is indistinguishable from end-of-stream",
2026
- { status: 0 }
2027
- )
2028
- );
2029
- }
2030
- return Promise.resolve({
2031
- runs: [],
2032
- nextCursor: params.cursor
2033
- });
2034
- }
2035
- const query = {};
2036
- if (params.capabilityId !== void 0)
2037
- query.capabilityId = params.capabilityId;
2038
- if (params.unreviewed !== void 0) query.unreviewed = params.unreviewed;
2039
- if (params.limit !== void 0) query.limit = params.limit;
2040
- if (params.cursor !== void 0) query.cursor = params.cursor;
2041
- return request(
2159
+ poll = async (deviceCode, opts = {}) => {
2160
+ const parsed = await request(
2042
2161
  this.client,
2043
2162
  {
2044
- method: "GET",
2045
- path: "/v1/runs",
2046
- query,
2163
+ method: "POST",
2164
+ path: "/v1/auth/device/poll",
2165
+ body: { deviceCode },
2047
2166
  signal: opts.signal
2048
2167
  },
2049
- listRunsResponseSchema
2168
+ devicePollResponseSchema
2050
2169
  );
2170
+ if ("error" in parsed) {
2171
+ return parsed.error === "authorization_pending" ? {
2172
+ status: "pending",
2173
+ userCode: parsed.userCode,
2174
+ verificationUri: parsed.verificationUri
2175
+ } : { status: "expired" };
2176
+ }
2177
+ return {
2178
+ status: "ok",
2179
+ accessToken: parsed.accessToken,
2180
+ refreshToken: parsed.refreshToken,
2181
+ expiresIn: parsed.expiresIn,
2182
+ user: parsed.user
2183
+ };
2051
2184
  };
2052
- get = (runId, opts = {}) => request(
2053
- this.client,
2054
- {
2055
- method: "GET",
2056
- path: `/v1/runs/${runId}`,
2057
- signal: opts.signal
2058
- },
2059
- runDetailSchema
2060
- );
2061
- review = (input, opts = {}) => request(
2062
- this.client,
2063
- {
2064
- method: "POST",
2065
- path: "/v1/reviews",
2066
- body: input,
2067
- signal: opts.signal
2068
- },
2069
- createReviewResponseSchema
2070
- );
2071
- // Bulk review submission. The CLI's bulk JSONL parser stays CLI-side —
2072
- // SDK consumers pass already-parsed structured input.
2073
- createReviewsBatch = (reviews, opts = {}) => request(
2074
- this.client,
2075
- {
2076
- method: "POST",
2077
- path: "/v1/reviews/batch",
2078
- body: { reviews },
2079
- signal: opts.signal
2080
- },
2081
- batchReviewResponseSchema
2082
- );
2083
2185
  };
2084
- var USDC_BASE2 = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
2085
- var USDC_TEMPO2 = "0x20c000000000000000000000b9537d11c60e8b50";
2086
- var TEMPO_CHAIN_ID2 = 4217;
2087
- var baseChain = base;
2088
- var tempoChain2 = {
2089
- id: TEMPO_CHAIN_ID2,
2090
- name: "Tempo",
2091
- nativeCurrency: { name: "USD", symbol: "USD", decimals: 18 },
2092
- rpcUrls: {
2093
- default: { http: ["https://rpc.tempo.xyz"] }
2094
- }
2186
+ var userWalletsSchema = z.array(userWalletDtoSchema);
2187
+ var normalizeMessage = (message) => {
2188
+ if (typeof message === "string") return message;
2189
+ if (typeof message.raw === "string") return message.raw;
2190
+ return bytesToHex(message.raw);
2095
2191
  };
2096
- var ERC20_BALANCE_ABI2 = [
2097
- {
2098
- inputs: [{ name: "account", type: "address" }],
2099
- name: "balanceOf",
2100
- outputs: [{ name: "", type: "uint256" }],
2101
- stateMutability: "view",
2102
- type: "function"
2192
+ var repackageSignFailure = (err, op) => {
2193
+ if (err instanceof ZeroApiError && err.status === 402) {
2194
+ const body = err.body;
2195
+ const detail = body && typeof body.error === "string" ? `: ${body.error}` : "";
2196
+ throw new ZeroPaymentError(
2197
+ `Sign endpoint refused ${op} (HTTP 402)${detail}`,
2198
+ { cause: err }
2199
+ );
2103
2200
  }
2104
- ];
2105
- var USDC_DECIMALS = 6;
2106
-
2107
- // src/namespaces/wallet.ts
2108
- var fundingUrlResponseSchema = z.object({ url: z.string() });
2109
- var userWalletListSchema = z.array(userWalletDtoSchema);
2110
- var migrateAuthorizationResponseSchema = z.object({
2111
- transactionHash: z.string()
2112
- });
2113
- var errorMessage = (reason) => reason instanceof Error ? reason.message : String(reason);
2114
- var Wallet = class {
2201
+ throw err;
2202
+ };
2203
+ var Auth = class {
2115
2204
  constructor(client) {
2116
2205
  this.client = client;
2206
+ this.device = new AuthDevice(client);
2117
2207
  }
2118
- clients = {};
2119
- // The configured wallet address, or null if the client wasn't constructed
2120
- // with an account. Stable read — does not hit the network.
2121
- get address() {
2122
- const credentials = this.client.getCredentials();
2123
- if (credentials.kind !== "account") return null;
2124
- return credentials.account.address;
2125
- }
2126
- balance = async (opts = {}) => {
2127
- const target = opts.address ?? this.address;
2128
- if (!target) {
2129
- throw new ZeroWalletError(
2130
- "balance() requires either an `address` option or a client constructed with an account"
2131
- );
2132
- }
2133
- const chain = opts.chain ?? "total";
2134
- if (chain !== "total") {
2135
- const raw = await this.readChainBalance(chain, target);
2136
- return { amount: formatUnits(raw, USDC_DECIMALS), asset: "USDC" };
2137
- }
2138
- const chains = ["base", "tempo"];
2139
- const settled = await Promise.allSettled(
2140
- chains.map((c) => this.readChainBalance(c, target))
2141
- );
2142
- let sum = 0n;
2143
- const errors = [];
2144
- for (let i = 0; i < settled.length; i++) {
2145
- const result = settled[i];
2146
- const chainName = chains[i];
2147
- if (!result || !chainName) continue;
2148
- if (result.status === "fulfilled") {
2149
- sum += result.value;
2150
- } else {
2151
- errors.push({ chain: chainName, message: errorMessage(result.reason) });
2152
- }
2153
- }
2154
- if (errors.length === settled.length) {
2155
- throw new ZeroWalletError(
2156
- `balance() failed for every chain: ${errors.map((e) => `${e.chain} (${e.message})`).join(", ")}`
2157
- );
2158
- }
2159
- const balance = {
2160
- amount: formatUnits(sum, USDC_DECIMALS),
2161
- asset: "USDC"
2162
- };
2163
- if (errors.length > 0) balance.errors = errors;
2164
- return balance;
2165
- };
2166
- // All wallets linked to the authenticated session user. Session-only.
2167
- list = (opts = {}) => request(
2208
+ device;
2209
+ // The internal (web-app facing) user profile. Richer than profile(): full
2210
+ // wallet list with per-wallet delegation/signer/limit metadata.
2211
+ me = (opts = {}) => request(
2168
2212
  this.client,
2169
- {
2170
- method: "GET",
2171
- path: "/v1/users/me/wallets",
2172
- signal: opts.signal
2173
- },
2174
- userWalletListSchema
2213
+ { method: "GET", path: "/v1/users/me", signal: opts.signal },
2214
+ internalUserDtoSchema
2175
2215
  );
2176
- // Provision a managed (Privy-embedded) wallet for the session user.
2177
- // Idempotent server-side repeat calls return the existing wallet.
2178
- provision = (opts = {}) => request(
2216
+ // The public (agent-facing) profile identity + primary wallet address +
2217
+ // balance + welcome-bonus, no per-wallet metadata. Same shape the MCP
2218
+ // get_profile tool returns; rendered by `zero auth whoami`.
2219
+ profile = (opts = {}) => request(
2220
+ this.client,
2221
+ { method: "GET", path: "/v1/users/me/profile", signal: opts.signal },
2222
+ publicUserDtoSchema
2223
+ );
2224
+ // The user's wallets only. Lightweight sibling of `me()` for callers that
2225
+ // just need wallet addresses (e.g. managed-account resolution on the pay
2226
+ // path) — avoids the heavier internal-profile computation behind `me()`.
2227
+ wallets = (opts = {}) => request(
2179
2228
  this.client,
2180
- {
2181
- method: "POST",
2182
- path: "/v1/users/me/wallets/provision",
2183
- signal: opts.signal
2184
- },
2185
- userWalletDtoSchema
2229
+ { method: "GET", path: "/v1/users/me/wallets", signal: opts.signal },
2230
+ userWalletsSchema
2186
2231
  );
2187
- // Relays a BYO-wallet-signed EIP-3009 ReceiveWithAuthorization, sweeping
2188
- // USDC from the BYO wallet to the user's Zero wallet on Base via the
2189
- // gas-paying relayer. The SDK does not author the authorization (chain
2190
- // nonce + EIP-712 domain coupling stays caller-side); it only forwards
2191
- // the signed payload. Session-only.
2192
- migrateAuthorization = (authorization, opts = {}) => request(
2232
+ // Exchange an ephemeral session code (e.g. runner / sandbox handoff) for
2233
+ // a session access token. The returned token has no refresh path pair
2234
+ // with `refreshToken: ""` when constructing the client.
2235
+ exchangeSessionCode = (code, opts = {}) => request(
2193
2236
  this.client,
2194
2237
  {
2195
2238
  method: "POST",
2196
- path: "/v1/users/me/wallets/migrate",
2197
- body: { authorization },
2239
+ path: "/v1/session/exchange",
2240
+ body: { code },
2198
2241
  signal: opts.signal
2199
2242
  },
2200
- migrateAuthorizationResponseSchema
2243
+ sessionExchangeResponseSchema
2201
2244
  );
2202
- // Routes by identity: account mode signs EIP-191 against /v1/wallet/fund-url,
2203
- // session mode hits /v1/users/me/fund-url (Bearer-authed, wallet resolved
2204
- // server-side). Both honor `provider` (coinbase | stripe).
2205
- fundingUrl = async (opts = {}) => {
2206
- const credentials = this.client.getCredentials();
2207
- const path = credentials.kind === "account" ? "/v1/wallet/fund-url" : credentials.kind === "session" ? "/v1/users/me/fund-url" : null;
2208
- if (!path) {
2209
- throw new ZeroWalletError(
2210
- "fundingUrl() requires a client constructed with an account or session credentials"
2211
- );
2212
- }
2213
- const query = {
2214
- provider: opts.provider ?? "coinbase"
2215
- };
2216
- if (opts.amount) query.amount = opts.amount;
2217
- const { url } = await request(
2245
+ // Server-side revoke of the refresh token. Throws ZeroApiError on any
2246
+ // non-2xx callers that have already cleared local state (e.g. CLI
2247
+ // logout) typically want to swallow with `.catch(() => {})` so a flaky
2248
+ // server doesn't surface as a user-visible failure for a sign-out that
2249
+ // already succeeded locally. Response body is opaque; schema is
2250
+ // `z.unknown()` and the resolved value is discarded.
2251
+ logout = async (refreshToken, opts = {}) => {
2252
+ await request(
2218
2253
  this.client,
2219
- { method: "GET", path, query, signal: opts.signal },
2220
- fundingUrlResponseSchema
2254
+ {
2255
+ method: "POST",
2256
+ path: "/v1/auth/logout",
2257
+ body: { refreshToken },
2258
+ signal: opts.signal
2259
+ },
2260
+ z.unknown()
2221
2261
  );
2222
- return url;
2223
2262
  };
2224
- getPublicClient = (chain) => {
2225
- if (chain === "base") {
2226
- if (!this.clients.base) {
2227
- this.clients.base = createPublicClient({
2228
- chain: baseChain,
2229
- transport: http()
2230
- });
2231
- }
2232
- return this.clients.base;
2263
+ // Force a session refresh. Routes through refreshSessionOnce() so a
2264
+ // proactive call doesn't race a 401-driven transport refresh —
2265
+ // concurrent POSTs with the same refresh token kill the session
2266
+ // family (RFC 6819 token-theft response).
2267
+ refresh = async () => {
2268
+ const credentials = this.client.getCredentials();
2269
+ if (credentials.kind !== "session") {
2270
+ throw new ZeroAuthError(
2271
+ "auth_no_credentials",
2272
+ "refresh() requires the client to be constructed with a session \u2014 got credentials.kind=" + credentials.kind
2273
+ );
2233
2274
  }
2234
- if (!this.clients.tempo) {
2235
- this.clients.tempo = createPublicClient({
2236
- // biome-ignore lint/suspicious/noExplicitAny: tempo is a custom chain object that viem's stricter Chain type rejects shape-wise but accepts at runtime
2237
- chain: tempoChain2,
2238
- transport: http()
2239
- });
2275
+ const ok = await this.client.refreshSessionOnce();
2276
+ if (!ok) {
2277
+ throw new ZeroAuthError(
2278
+ "auth_session_expired",
2279
+ "Session refresh failed. The refresh token may be revoked or expired."
2280
+ );
2240
2281
  }
2241
- return this.clients.tempo;
2242
2282
  };
2243
- /**
2244
- * @internal
2245
- *
2246
- * Per-chain USDC balance read. Exposed (rather than private) so unit
2247
- * tests can stub it without standing up a real viem public client —
2248
- * viem's `http()` transport doesn't go through `client.fetchImpl`, so
2249
- * the standard mock-fetch trick doesn't cover this path.
2250
- */
2251
- readChainBalance = async (chain, address) => {
2252
- const publicClient = this.getPublicClient(chain);
2253
- const tokenAddress = chain === "base" ? USDC_BASE2 : USDC_TEMPO2;
2254
- const balance = await publicClient.readContract({
2255
- address: tokenAddress,
2256
- abi: ERC20_BALANCE_ABI2,
2257
- functionName: "balanceOf",
2258
- args: [address]
2259
- });
2260
- return balance;
2261
- };
2262
- };
2263
-
2264
- // src/options.ts
2265
- var DEFAULT_BASE_URL = "https://api.zero.xyz";
2266
- var DEFAULT_TIMEOUT_MS = 18e4;
2267
- var DEFAULT_MAX_RETRIES = 2;
2268
-
2269
- // src/auth/credentials.ts
2270
- var noopRefresh = async () => {
2271
- };
2272
- var MAX_402_PROBE_BYTES = 1024 * 1024;
2273
- var MAX_RESPONSE_BYTES = 10 * 1024 * 1024;
2274
- var TEXT_CONTENT_TYPES = [
2275
- "application/json",
2276
- "application/xml",
2277
- "application/javascript",
2278
- "application/ecmascript",
2279
- "application/x-www-form-urlencoded",
2280
- "text/",
2281
- "+json",
2282
- "+xml"
2283
- ];
2284
- var MPP_SCHEME_TOKENS = /* @__PURE__ */ new Set(["payment", "mpp"]);
2285
- var splitWwwAuthenticateChallenges = (header) => {
2286
- const chunks = [];
2287
- let buf = "";
2288
- let inQuotes = false;
2289
- for (let i = 0; i < header.length; i++) {
2290
- const ch = header[i];
2291
- if (inQuotes) {
2292
- if (ch === "\\" && i + 1 < header.length) {
2293
- buf += ch + header[i + 1];
2294
- i++;
2295
- continue;
2296
- }
2297
- if (ch === '"') inQuotes = false;
2298
- buf += ch;
2299
- continue;
2300
- }
2301
- if (ch === '"') {
2302
- inQuotes = true;
2303
- buf += ch;
2304
- continue;
2283
+ signMessage = async (input, opts = {}) => {
2284
+ const credentials = this.client.getCredentials();
2285
+ if (credentials.kind === "account") {
2286
+ return await credentials.account.signMessage({ message: input.message });
2305
2287
  }
2306
- if (ch === ",") {
2307
- chunks.push(buf);
2308
- buf = "";
2309
- continue;
2288
+ if (credentials.kind === "session") {
2289
+ try {
2290
+ const parsed = await request(
2291
+ this.client,
2292
+ {
2293
+ method: "POST",
2294
+ path: "/v1/users/me/sign-message",
2295
+ body: { message: normalizeMessage(input.message) }
2296
+ },
2297
+ signResponseSchema
2298
+ );
2299
+ this.assertSignerMatches(parsed.walletAddress, opts.expectedAddress);
2300
+ return parsed.signature;
2301
+ } catch (err) {
2302
+ repackageSignFailure(err, "signMessage");
2303
+ }
2310
2304
  }
2311
- buf += ch;
2312
- }
2313
- if (buf.length > 0) chunks.push(buf);
2314
- return chunks;
2315
- };
2316
- var isTextContentType = (contentType) => {
2317
- if (!contentType) return true;
2318
- const lower = contentType.toLowerCase();
2319
- return TEXT_CONTENT_TYPES.some((marker) => lower.includes(marker));
2320
- };
2321
- var redactUrl = (url) => {
2322
- try {
2323
- const u = new URL(url);
2324
- return `${u.origin}${u.pathname}`;
2325
- } catch {
2326
- return "<invalid url>";
2327
- }
2328
- };
2329
- var ECHO_DETECT_MIN_LEN = 16;
2330
- var ECHO_DETECT_MAX_SCAN = 64 * 1024;
2331
- var requestBodyEchoed = (candidate, requestBody) => {
2332
- if (requestBody === void 0) return false;
2333
- const asString = typeof requestBody === "string" ? requestBody : new TextDecoder("utf-8", { fatal: false }).decode(
2334
- requestBody.byteLength > ECHO_DETECT_MAX_SCAN ? requestBody.subarray(0, ECHO_DETECT_MAX_SCAN) : requestBody
2335
- );
2336
- if (asString.length < ECHO_DETECT_MIN_LEN) return false;
2337
- const scanLen = Math.min(asString.length, ECHO_DETECT_MAX_SCAN);
2338
- for (let i = 0; i + ECHO_DETECT_MIN_LEN <= scanLen; i++) {
2339
- if (candidate.includes(asString.slice(i, i + ECHO_DETECT_MIN_LEN))) {
2340
- return true;
2305
+ throw new ZeroAuthError(
2306
+ "auth_no_credentials",
2307
+ "signMessage() requires the client to be constructed with `account` or `session`."
2308
+ );
2309
+ };
2310
+ // Managed-only: BYO accounts sign locally via viem's account.signTransaction.
2311
+ signTransaction = async (input, opts = {}) => {
2312
+ const credentials = this.client.getCredentials();
2313
+ if (credentials.kind === "session") {
2314
+ try {
2315
+ const parsed = await request(
2316
+ this.client,
2317
+ {
2318
+ method: "POST",
2319
+ path: "/v1/users/me/sign-transaction",
2320
+ body: { unsignedTransaction: input.unsignedTransaction }
2321
+ },
2322
+ signResponseSchema
2323
+ );
2324
+ this.assertSignerMatches(parsed.walletAddress, opts.expectedAddress);
2325
+ return parsed.signature;
2326
+ } catch (err) {
2327
+ repackageSignFailure(err, "signTransaction");
2328
+ }
2341
2329
  }
2342
- }
2343
- return false;
2344
- };
2345
- var cancelBody2 = (response) => {
2346
- try {
2347
- response.body?.cancel().catch(() => {
2348
- });
2349
- } catch {
2350
- }
2351
- };
2352
- var detectPaymentRequirement = async (response) => {
2353
- if (response.status !== 402) return null;
2354
- const x402Header = response.headers.get("payment-required") ?? response.headers.get("x-payment-required");
2355
- if (x402Header) {
2356
- cancelBody2(response);
2357
- try {
2358
- const decoded = JSON.parse(
2359
- Buffer.from(x402Header, "base64").toString("utf8")
2330
+ if (credentials.kind === "account") {
2331
+ throw new ZeroAuthError(
2332
+ "auth_error",
2333
+ "signTransaction() is a managed-signing operation and is not supported in account mode. Sign locally with viem's account.signTransaction instead."
2360
2334
  );
2361
- return { protocol: "x402", raw: decoded };
2362
- } catch {
2363
- return { protocol: "x402", raw: { encoded: x402Header } };
2364
2335
  }
2365
- }
2366
- const declaredLen = Number.parseInt(
2367
- response.headers.get("content-length") ?? "",
2368
- 10
2369
- );
2370
- const bodyFits = !Number.isFinite(declaredLen) || declaredLen <= MAX_402_PROBE_BYTES;
2371
- if (bodyFits) {
2372
- try {
2373
- const text = await readClippedText(response, MAX_402_PROBE_BYTES);
2374
- if (text) {
2375
- const parsed = JSON.parse(text);
2376
- if (looksLikeX402Body(parsed)) {
2377
- return { protocol: "x402", raw: parsed };
2378
- }
2336
+ throw new ZeroAuthError(
2337
+ "auth_no_credentials",
2338
+ "signTransaction() requires session credentials (managed signing)."
2339
+ );
2340
+ };
2341
+ signTypedData = async (input, opts = {}) => {
2342
+ const credentials = this.client.getCredentials();
2343
+ if (credentials.kind === "account") {
2344
+ if (!credentials.account.signTypedData) {
2345
+ throw new ZeroAuthError(
2346
+ "auth_error",
2347
+ "The configured viem account does not implement signTypedData."
2348
+ );
2379
2349
  }
2380
- } catch {
2350
+ return await credentials.account.signTypedData(input);
2381
2351
  }
2382
- }
2383
- const wwwAuth = response.headers.get("www-authenticate");
2384
- if (wwwAuth) {
2385
- const schemes = splitWwwAuthenticateChallenges(wwwAuth).map(
2386
- (challenge) => challenge.trim().split(/\s+/)[0]?.toLowerCase() ?? ""
2387
- );
2388
- if (schemes.some((scheme) => MPP_SCHEME_TOKENS.has(scheme))) {
2389
- cancelBody2(response);
2390
- return { protocol: "mpp", raw: { "www-authenticate": wwwAuth } };
2352
+ if (credentials.kind === "session") {
2353
+ try {
2354
+ const parsed = await request(
2355
+ this.client,
2356
+ {
2357
+ method: "POST",
2358
+ path: "/v1/users/me/sign-typed-data",
2359
+ body: { typedData: input }
2360
+ },
2361
+ signResponseSchema
2362
+ );
2363
+ this.assertSignerMatches(parsed.walletAddress, opts.expectedAddress);
2364
+ return parsed.signature;
2365
+ } catch (err) {
2366
+ repackageSignFailure(err, "signTypedData");
2367
+ }
2391
2368
  }
2392
- }
2393
- cancelBody2(response);
2394
- return { protocol: "unknown", raw: {} };
2369
+ throw new ZeroAuthError(
2370
+ "auth_no_credentials",
2371
+ "signTypedData() requires the client to be constructed with `account` or `session`."
2372
+ );
2373
+ };
2374
+ // Compare against the caller's pinned address (not shared cache) so
2375
+ // concurrent pay()s can't race past each other. Mismatch → clear + throw;
2376
+ // the caller's retry rebuilds against the new wallet.
2377
+ assertSignerMatches = (serverAddr, expectedAddr) => {
2378
+ if (!expectedAddr) return;
2379
+ const canonicalServer = getAddress(serverAddr);
2380
+ const canonicalExpected = getAddress(expectedAddr);
2381
+ if (canonicalExpected === canonicalServer) return;
2382
+ this.client.clearManagedAccount();
2383
+ throw new ZeroAuthError(
2384
+ "auth_error",
2385
+ `Managed signing wallet changed (caller expected ${canonicalExpected}, server signed as ${canonicalServer}). Cache cleared \u2014 retry to rebuild against the current wallet.`
2386
+ );
2387
+ };
2395
2388
  };
2396
- var readClippedBytes = async (response, max) => {
2397
- if (response.body === null) return new Uint8Array(0);
2398
- const reader = response.body.getReader();
2399
- const chunks = [];
2400
- let total = 0;
2401
- try {
2402
- while (total < max) {
2403
- const { done, value } = await reader.read();
2404
- if (done) break;
2405
- if (!value) continue;
2406
- chunks.push(value);
2407
- total += value.byteLength;
2408
- }
2409
- } finally {
2410
- try {
2411
- await reader.cancel();
2412
- } catch {
2413
- }
2414
- }
2415
- const merged = new Uint8Array(Math.min(total, max));
2416
- let offset = 0;
2417
- for (const c of chunks) {
2418
- const take = Math.min(c.byteLength, max - offset);
2419
- merged.set(c.subarray(0, take), offset);
2420
- offset += take;
2421
- if (offset >= max) break;
2389
+ var BUG_REPORT_CATEGORIES = [
2390
+ "search_relevance",
2391
+ "ranking_issue",
2392
+ "missing_capability",
2393
+ "wrong_schema",
2394
+ "misleading_description",
2395
+ "broken_execution",
2396
+ "payment_failure",
2397
+ "billing_anomaly",
2398
+ "cli_bug",
2399
+ "security",
2400
+ "other"
2401
+ ];
2402
+ var createBugReportResponseSchema = z.object({
2403
+ bugReportId: z.string(),
2404
+ status: z.string(),
2405
+ deduped: z.boolean(),
2406
+ category: z.enum(BUG_REPORT_CATEGORIES).nullable(),
2407
+ title: z.string().nullable(),
2408
+ attached: z.object({
2409
+ capabilityId: z.number().nullable(),
2410
+ runId: z.number().nullable(),
2411
+ searchId: z.number().nullable()
2412
+ })
2413
+ });
2414
+
2415
+ // src/namespaces/bug-reports.ts
2416
+ var BugReports = class {
2417
+ constructor(client) {
2418
+ this.client = client;
2422
2419
  }
2423
- return merged;
2424
- };
2425
- var readClippedText = async (response, max) => {
2426
- const bytes = await readClippedBytes(response, max);
2427
- return new TextDecoder().decode(bytes);
2420
+ // POST /v1/bug-reports. Wallet-attributed server-side; the SDK client's
2421
+ // active credential drives attribution (account-mode → EIP-191 from the
2422
+ // wallet, session-mode Bearer + server resolves the user's wallet).
2423
+ create = (input, opts = {}) => request(
2424
+ this.client,
2425
+ {
2426
+ method: "POST",
2427
+ path: "/v1/bug-reports",
2428
+ body: input,
2429
+ signal: opts.signal
2430
+ },
2431
+ createBugReportResponseSchema
2432
+ );
2428
2433
  };
2429
- var parseResponseBody = async (response, max) => {
2430
- const contentType = response.headers.get("content-type");
2431
- const bytes = await readClippedBytes(response, max + 1);
2432
- const truncated = bytes.byteLength > max;
2433
- const view = truncated ? bytes.subarray(0, max) : bytes;
2434
- const buf = Buffer.from(view.buffer, view.byteOffset, view.byteLength);
2435
- if (!isTextContentType(contentType)) {
2436
- const base64 = buf.toString("base64");
2437
- return {
2438
- body: base64,
2439
- bodyRaw: base64,
2440
- bodyEncoding: "base64",
2441
- truncated
2442
- };
2434
+ var ratingSchema = z.object({
2435
+ score: z.string(),
2436
+ successRate: z.string(),
2437
+ reviews: z.number(),
2438
+ stars: z.string().nullable().optional(),
2439
+ state: z.enum(["unrated", "rated"]).optional()
2440
+ });
2441
+
2442
+ // src/schemas/capabilities.ts
2443
+ var capabilityResponseSchema = z.object({
2444
+ uid: z.string(),
2445
+ slug: z.string(),
2446
+ name: z.string(),
2447
+ description: z.string(),
2448
+ url: z.string(),
2449
+ urlTemplate: z.string().nullable().optional(),
2450
+ method: z.string(),
2451
+ headers: z.record(z.string(), z.string()).nullable(),
2452
+ bodySchema: z.record(z.string(), z.unknown()).nullable(),
2453
+ responseSchema: z.record(z.string(), z.unknown()).nullable(),
2454
+ example: z.object({ request: z.unknown(), response: z.unknown() }).nullable(),
2455
+ tags: z.array(z.string()).nullable(),
2456
+ exampleAgentPrompt: z.string().nullable().optional(),
2457
+ whatItDoes: z.string().nullable().optional(),
2458
+ resultDescription: z.string().nullable().optional(),
2459
+ failureModes: z.array(z.string()).nullable().optional(),
2460
+ whenToPreferThis: z.string().nullable().optional(),
2461
+ // Agent-facing execution guide (markdown). Covers usage patterns, input
2462
+ // field guidance, cross-cap composition, and known failure modes.
2463
+ // Named after MCP tool convention: description = what it is, instructions = how to use it.
2464
+ instructions: z.string().nullable().optional(),
2465
+ displayCostAmount: z.string(),
2466
+ displayCostAsset: z.string(),
2467
+ reviewCount: z.number(),
2468
+ rating: ratingSchema,
2469
+ priceObserved: z.object({
2470
+ minCents: z.string().nullable(),
2471
+ medianCents: z.string().nullable(),
2472
+ maxCents: z.string().nullable(),
2473
+ // Deprecated alias for maxCents; kept for back-compat with older
2474
+ // API versions that still emit it.
2475
+ p95Cents: z.string().nullable(),
2476
+ sampleCount: z.number(),
2477
+ varies: z.boolean(),
2478
+ failureChargeRate: z.number().nullable()
2479
+ }).nullable().optional(),
2480
+ paymentMethods: z.array(
2481
+ z.object({
2482
+ uid: z.string(),
2483
+ protocol: z.string(),
2484
+ methodType: z.string(),
2485
+ chain: z.string().nullable(),
2486
+ mode: z.string(),
2487
+ costAmount: z.string(),
2488
+ costPer: z.string(),
2489
+ priority: z.number()
2490
+ })
2491
+ ).nullable(),
2492
+ availabilityStatus: z.enum(["healthy", "unknown", "down"]).nullable().optional(),
2493
+ /**
2494
+ * @deprecated The API no longer emits `displayStatus`; it has been replaced
2495
+ * by the consolidated 3-value {@link capabilityResponseSchema.shape.availabilityStatus}.
2496
+ * Kept as an optional field for one release for back-compat (ZERO-89 soft
2497
+ * migration) and will be removed in the next major version.
2498
+ */
2499
+ displayStatus: z.enum(["healthy", "stable", "degraded", "unhealthy", "unknown"]).optional(),
2500
+ activationCount: z.number().optional(),
2501
+ lastUsedAt: z.string().nullable().optional(),
2502
+ lastSuccessfullyRanAt: z.string().nullable().optional()
2503
+ });
2504
+ var resolveCapabilityResponseSchema = z.object({
2505
+ capabilityId: z.string(),
2506
+ capabilitySlug: z.string(),
2507
+ matchedVia: z.enum(["exact", "template"])
2508
+ });
2509
+
2510
+ // src/namespaces/capabilities.ts
2511
+ var Capabilities = class {
2512
+ constructor(client) {
2513
+ this.client = client;
2443
2514
  }
2444
- const text = buf.toString("utf8");
2445
- if (contentType?.toLowerCase().includes("json")) {
2515
+ get = (id, opts = {}) => request(
2516
+ this.client,
2517
+ {
2518
+ method: "GET",
2519
+ path: `/v1/capabilities/${encodeURIComponent(id)}`,
2520
+ query: opts.searchId ? { searchId: opts.searchId } : void 0,
2521
+ signal: opts.signal
2522
+ },
2523
+ capabilityResponseSchema
2524
+ );
2525
+ // Resolve a raw endpoint URL + method back to the capability that owns it,
2526
+ // for the `zero fetch <url>` case where the client has no local search
2527
+ // context to match against (a memoized/cross-session URL). Returns null on
2528
+ // 404 — i.e. no match or an ambiguous multi-match, both of which the server
2529
+ // reports as 404 (resolveByUrl fails closed rather than mis-attribute).
2530
+ // The URL travels in the POST body so it stays out of access logs and is
2531
+ // never stored; the caller uses the returned capabilityId to record an
2532
+ // attributed run without the URL ever touching the run row (ZERO-335).
2533
+ resolveByUrl = async (url, method, opts = {}) => {
2446
2534
  try {
2447
- return { body: JSON.parse(text), bodyRaw: text, truncated };
2448
- } catch {
2535
+ return await request(
2536
+ this.client,
2537
+ {
2538
+ method: "POST",
2539
+ path: "/v1/capabilities/resolve",
2540
+ body: { url, method },
2541
+ signal: opts.signal
2542
+ },
2543
+ resolveCapabilityResponseSchema
2544
+ );
2545
+ } catch (err) {
2546
+ if (err instanceof ZeroApiError && err.status === 404) return null;
2547
+ throw err;
2449
2548
  }
2450
- }
2451
- return { body: text, bodyRaw: text, truncated };
2452
- };
2453
- var UPSTREAM_ERROR_FIELDS = [
2454
- "error",
2455
- "message",
2456
- "detail",
2457
- "reason",
2458
- "error_description"
2459
- ];
2460
- var UPSTREAM_ERROR_MAX_LEN = 200;
2461
- var ERROR_SNIPPET_BYTES = 500;
2462
- var clampUpstreamMessage = (value) => {
2463
- const trimmed = value.trim();
2464
- if (!trimmed) return void 0;
2465
- return trimmed.length > UPSTREAM_ERROR_MAX_LEN ? `${trimmed.slice(0, UPSTREAM_ERROR_MAX_LEN)}\u2026` : trimmed;
2466
- };
2467
- var tryParseJson = (text) => {
2468
- try {
2469
- return JSON.parse(text);
2470
- } catch {
2471
- return null;
2472
- }
2549
+ };
2473
2550
  };
2474
- var extractUpstreamErrorMessage = (body) => {
2475
- const parsed = tryParseJson(body);
2476
- if (parsed === null || typeof parsed !== "object") {
2477
- return clampUpstreamMessage(body);
2551
+ var createRunResponseSchema = z.object({
2552
+ runId: z.string()
2553
+ });
2554
+ var runListItemSchema = z.object({
2555
+ uid: z.string(),
2556
+ capabilityUid: z.string(),
2557
+ capabilitySlug: z.string(),
2558
+ capabilityName: z.string(),
2559
+ status: z.number().nullable(),
2560
+ latencyMs: z.number().nullable(),
2561
+ cost: z.object({ amount: z.string(), asset: z.string().nullable() }).nullable(),
2562
+ payment: z.object({
2563
+ protocol: z.string(),
2564
+ chain: z.string().nullable(),
2565
+ txHash: z.string().nullable(),
2566
+ mode: z.string().nullable()
2567
+ }).nullable(),
2568
+ createdAt: z.coerce.date(),
2569
+ reviewed: z.boolean()
2570
+ });
2571
+ var listRunsResponseSchema = z.object({
2572
+ runs: z.array(runListItemSchema),
2573
+ nextCursor: z.string().nullable()
2574
+ });
2575
+ var runDetailSchema = z.object({
2576
+ runId: z.string(),
2577
+ status: z.number().nullable(),
2578
+ latencyMs: z.number().nullable(),
2579
+ errorClass: z.string().nullable(),
2580
+ createdAt: z.coerce.date(),
2581
+ cost: z.object({ amount: z.string(), asset: z.string().nullable() }).nullable(),
2582
+ payment: z.object({
2583
+ protocol: z.string(),
2584
+ chain: z.string().nullable(),
2585
+ txHash: z.string().nullable(),
2586
+ mode: z.string().nullable()
2587
+ }).nullable(),
2588
+ capability: z.object({
2589
+ uid: z.string(),
2590
+ slug: z.string(),
2591
+ name: z.string(),
2592
+ url: z.string(),
2593
+ method: z.string(),
2594
+ whatItDoes: z.string().nullable(),
2595
+ tags: z.array(z.string())
2596
+ }),
2597
+ reviewed: z.boolean()
2598
+ });
2599
+ var createReviewResponseSchema = z.object({
2600
+ reviewId: z.string(),
2601
+ recorded: z.boolean(),
2602
+ updated: z.boolean().optional()
2603
+ });
2604
+ var batchReviewResponseSchema = z.object({
2605
+ results: z.array(
2606
+ z.union([
2607
+ z.object({
2608
+ runId: z.string(),
2609
+ ok: z.literal(true),
2610
+ reviewId: z.string(),
2611
+ updated: z.boolean()
2612
+ }),
2613
+ z.object({
2614
+ runId: z.string(),
2615
+ ok: z.literal(false),
2616
+ status: z.number(),
2617
+ error: z.string()
2618
+ })
2619
+ ])
2620
+ ),
2621
+ summary: z.object({
2622
+ total: z.number(),
2623
+ ok: z.number(),
2624
+ failed: z.number()
2625
+ })
2626
+ });
2627
+
2628
+ // src/namespaces/runs.ts
2629
+ var Runs = class {
2630
+ constructor(client) {
2631
+ this.client = client;
2478
2632
  }
2479
- const record = parsed;
2480
- for (const field of UPSTREAM_ERROR_FIELDS) {
2481
- const value = record[field];
2482
- if (typeof value === "string" && value.length > 0) {
2483
- return clampUpstreamMessage(value);
2484
- }
2485
- if (value && typeof value === "object") {
2486
- const nested = value.message;
2487
- if (typeof nested === "string" && nested.length > 0) {
2488
- return clampUpstreamMessage(nested);
2633
+ create = (input, opts = {}) => request(
2634
+ this.client,
2635
+ {
2636
+ method: "POST",
2637
+ path: "/v1/runs",
2638
+ body: input,
2639
+ signal: opts.signal
2640
+ },
2641
+ createRunResponseSchema
2642
+ );
2643
+ list = (params = {}, opts = {}) => {
2644
+ if (params.limit === 0) {
2645
+ if (opts.signal?.aborted) {
2646
+ return Promise.reject(
2647
+ new ZeroApiError("Request aborted by caller", { status: 0 })
2648
+ );
2649
+ }
2650
+ if (params.cursor === void 0) {
2651
+ return Promise.reject(
2652
+ new ZeroApiError(
2653
+ "runs.list({ limit: 0 }) requires a cursor \u2014 without one the empty page is indistinguishable from end-of-stream",
2654
+ { status: 0 }
2655
+ )
2656
+ );
2489
2657
  }
2658
+ return Promise.resolve({
2659
+ runs: [],
2660
+ nextCursor: params.cursor
2661
+ });
2490
2662
  }
2491
- }
2492
- return void 0;
2493
- };
2494
- var buildUpstreamError = (status, bodyRaw, bodyEncoding) => {
2495
- if (status >= 200 && status < 300) return void 0;
2496
- if (bodyRaw === null || bodyRaw.length === 0) return void 0;
2497
- if (bodyEncoding === "base64") return void 0;
2498
- const snippet = bodyRaw.slice(0, ERROR_SNIPPET_BYTES);
2499
- return {
2500
- status,
2501
- message: extractUpstreamErrorMessage(bodyRaw),
2502
- snippetHash: createHash("sha256").update(snippet).digest("hex"),
2503
- snippetLength: snippet.length
2504
- };
2505
- };
2506
- var computeOutcome = (status, payment, warnings) => {
2507
- if (warnings.includes(FETCH_WARNINGS.mppSessionCloseFailed))
2508
- return "payment_close_failed";
2509
- if (status === 402 && payment !== null) return "payment_rejected";
2510
- if (status === 402) return "payment_failed";
2511
- if (status >= 400) return "server_error";
2512
- return "success";
2513
- };
2514
- var errorFetchResult = (startedAt, error, skipReason, capabilityIdRequested, outcome, status = null) => {
2515
- const result = {
2516
- runId: null,
2517
- ok: false,
2518
- status,
2519
- latencyMs: Date.now() - startedAt,
2520
- payment: null,
2521
- outcome,
2522
- body: null,
2523
- bodyRaw: null,
2524
- error
2663
+ const query = {};
2664
+ if (params.capabilityId !== void 0)
2665
+ query.capabilityId = params.capabilityId;
2666
+ if (params.unreviewed !== void 0) query.unreviewed = params.unreviewed;
2667
+ if (params.limit !== void 0) query.limit = params.limit;
2668
+ if (params.cursor !== void 0) query.cursor = params.cursor;
2669
+ return request(
2670
+ this.client,
2671
+ {
2672
+ method: "GET",
2673
+ path: "/v1/runs",
2674
+ query,
2675
+ signal: opts.signal
2676
+ },
2677
+ listRunsResponseSchema
2678
+ );
2525
2679
  };
2526
- if (capabilityIdRequested) result.runTrackingSkipped = [skipReason];
2527
- return result;
2528
- };
2529
- var resolveRecordingClient = (client, opts) => {
2530
- const credentials = client.getCredentials();
2531
- const recordingClient = opts.account ? client.withAccount(opts.account) : client;
2532
- const canRecord = opts.account !== void 0 || credentials.kind === "account" || credentials.kind === "session";
2533
- return { recordingClient, canRecord };
2534
- };
2535
- var recordErrorFetchRun = async (client, opts, result, capabilityIdOverride) => {
2536
- const effectiveCapId = opts.capabilityId;
2537
- if (effectiveCapId === void 0) return;
2538
- const { recordingClient, canRecord } = resolveRecordingClient(client, opts);
2539
- if (!canRecord) return;
2540
- const fetchOriginForRecord = opts.capabilityId === void 0 ? "direct_url" : opts.fetchOrigin;
2541
- try {
2542
- const created = await recordingClient.runs.create({
2543
- capabilityId: effectiveCapId,
2544
- ...opts.searchId && { searchId: opts.searchId },
2545
- ...fetchOriginForRecord && { fetchOrigin: fetchOriginForRecord },
2546
- ...result.status !== null && { status: result.status },
2547
- latencyMs: result.latencyMs,
2548
- ...opts.requestSchema && { requestSchema: opts.requestSchema }
2549
- });
2550
- result.runId = created.runId;
2551
- delete result.runTrackingSkipped;
2552
- } catch {
2553
- }
2554
- };
2555
- var recordTimeoutRun = async (client, opts, err, startedAt, capabilityIdRequested, capabilityIdOverride) => {
2556
- const result = errorFetchResult(
2557
- startedAt,
2558
- err.message,
2559
- "timeout",
2560
- capabilityIdRequested,
2561
- "network_error"
2680
+ get = (runId, opts = {}) => request(
2681
+ this.client,
2682
+ {
2683
+ method: "GET",
2684
+ path: `/v1/runs/${runId}`,
2685
+ signal: opts.signal
2686
+ },
2687
+ runDetailSchema
2688
+ );
2689
+ review = (input, opts = {}) => request(
2690
+ this.client,
2691
+ {
2692
+ method: "POST",
2693
+ path: "/v1/reviews",
2694
+ body: input,
2695
+ signal: opts.signal
2696
+ },
2697
+ createReviewResponseSchema
2698
+ );
2699
+ // Bulk review submission. The CLI's bulk JSONL parser stays CLI-side —
2700
+ // SDK consumers pass already-parsed structured input.
2701
+ createReviewsBatch = (reviews, opts = {}) => request(
2702
+ this.client,
2703
+ {
2704
+ method: "POST",
2705
+ path: "/v1/reviews/batch",
2706
+ body: { reviews },
2707
+ signal: opts.signal
2708
+ },
2709
+ batchReviewResponseSchema
2562
2710
  );
2563
- await recordErrorFetchRun(client, opts, result);
2564
2711
  };
2565
- var sniffJsonShapeBytes = (buf) => {
2566
- let i = 0;
2567
- if (buf.length >= 3 && buf[0] === 239 && buf[1] === 187 && buf[2] === 191) {
2568
- i = 3;
2569
- }
2570
- while (i < buf.length && (buf[i] === 32 || buf[i] === 9 || buf[i] === 10 || buf[i] === 13)) {
2571
- i++;
2712
+ var USDC_BASE2 = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
2713
+ var USDC_TEMPO2 = "0x20c000000000000000000000b9537d11c60e8b50";
2714
+ var TEMPO_CHAIN_ID2 = 4217;
2715
+ var baseChain = base;
2716
+ var tempoChain2 = {
2717
+ id: TEMPO_CHAIN_ID2,
2718
+ name: "Tempo",
2719
+ nativeCurrency: { name: "USD", symbol: "USD", decimals: 18 },
2720
+ rpcUrls: {
2721
+ default: { http: ["https://rpc.tempo.xyz"] }
2572
2722
  }
2573
- if (i >= buf.length) return false;
2574
- return buf[i] === 123 || buf[i] === 91;
2575
2723
  };
2576
- var withDefaultContentType = (headers, body) => {
2577
- if (!headers && body === void 0) return void 0;
2578
- const next = headers ? { ...headers } : {};
2579
- if (body === void 0) return next;
2580
- const hasContentType = Object.keys(next).some(
2581
- (k) => k.toLowerCase() === "content-type"
2582
- );
2583
- if (!hasContentType) {
2584
- next["content-type"] = typeof body === "string" || sniffJsonShapeBytes(body) ? "application/json" : "application/octet-stream";
2724
+ var ERC20_BALANCE_ABI2 = [
2725
+ {
2726
+ inputs: [{ name: "account", type: "address" }],
2727
+ name: "balanceOf",
2728
+ outputs: [{ name: "", type: "uint256" }],
2729
+ stateMutability: "view",
2730
+ type: "function"
2585
2731
  }
2586
- return next;
2587
- };
2588
- var clientFetch = async (client, url, opts = {}) => {
2589
- const startedAt = Date.now();
2590
- const capabilityIdRequested = opts.capabilityId !== void 0;
2591
- const normalized = normalizeTransportEnvelopeRequest(url, {
2592
- method: opts.method,
2593
- body: opts.body
2594
- });
2595
- const requestUrl = normalized.url;
2596
- const requestBody = normalized.body;
2597
- const method = normalized.method ?? (requestBody !== void 0 ? "POST" : "GET");
2598
- const { canRecord: canRecordForResolve } = resolveRecordingClient(
2599
- client,
2600
- opts
2601
- );
2602
- const resolvePromise = !capabilityIdRequested && canRecordForResolve ? client.capabilities.resolveByUrl(redactUrl(requestUrl), method).catch(() => null) : Promise.resolve(null);
2603
- const probeHeaders = withDefaultContentType(opts.headers, requestBody);
2604
- const requestInit = {
2605
- method,
2606
- headers: probeHeaders,
2607
- body: requestBody,
2608
- signal: opts.signal
2609
- };
2610
- const configuredFetch = buildConfiguredFetch(client, {
2611
- timeoutMs: opts.timeoutMs
2612
- });
2613
- let response;
2614
- let payment = null;
2615
- const skipReasons = [];
2616
- const warnings = [];
2617
- try {
2618
- response = await configuredFetch(requestUrl, requestInit);
2619
- } catch (err) {
2620
- if (err instanceof ZeroTimeoutError) {
2621
- await recordTimeoutRun(
2622
- client,
2623
- opts,
2624
- err,
2625
- startedAt,
2626
- capabilityIdRequested
2627
- );
2628
- throw err;
2629
- }
2630
- if (err instanceof ZeroError) throw err;
2631
- const errMessage = err instanceof Error ? err.message : String(err);
2632
- const result2 = errorFetchResult(
2633
- startedAt,
2634
- errMessage,
2635
- "fetch_failed_before_response",
2636
- capabilityIdRequested,
2637
- "network_error"
2638
- );
2639
- await recordErrorFetchRun(client, opts, result2);
2640
- return result2;
2732
+ ];
2733
+ var USDC_DECIMALS = 6;
2734
+
2735
+ // src/namespaces/wallet.ts
2736
+ var fundingUrlResponseSchema = z.object({ url: z.string() });
2737
+ var userWalletListSchema = z.array(userWalletDtoSchema);
2738
+ var migrateAuthorizationResponseSchema = z.object({
2739
+ transactionHash: z.string()
2740
+ });
2741
+ var errorMessage = (reason) => reason instanceof Error ? reason.message : String(reason);
2742
+ var Wallet = class {
2743
+ constructor(client) {
2744
+ this.client = client;
2641
2745
  }
2642
- if (response.status === 402) {
2643
- const requirement = await detectPaymentRequirement(response);
2644
- if (requirement?.protocol === "x402" || requirement?.protocol === "mpp") {
2645
- const payCall = requirement.protocol === "x402" ? client.payments.pay : client.payments.payMpp;
2646
- try {
2647
- const paid = await payCall({
2648
- url: requestUrl,
2649
- method,
2650
- // Fresh clone for the paid retry — same rationale as
2651
- // the probe-side clone above. Re-derives the default
2652
- // Content-Type so the paid leg matches the probe.
2653
- headers: withDefaultContentType(opts.headers, requestBody),
2654
- body: requestBody,
2655
- maxPay: opts.maxPay,
2656
- account: opts.account,
2657
- signal: opts.signal,
2658
- timeoutMs: opts.timeoutMs,
2659
- ...opts.displayCostAmount && {
2660
- displayCostAmount: opts.displayCostAmount
2661
- }
2662
- });
2663
- response = paid.response;
2664
- payment = paid.payment;
2665
- if (paid.warnings) warnings.push(...paid.warnings);
2666
- } catch (err) {
2667
- if (err instanceof ZeroSessionCloseFailedError) {
2668
- response = err.response;
2669
- payment = {
2670
- protocol: "mpp",
2671
- chain: tempoChainLabelFromId(err.session.chainId),
2672
- txHash: null,
2673
- amount: err.capturedAmount,
2674
- asset: "USDC",
2675
- session: err.session
2676
- };
2677
- warnings.push(FETCH_WARNINGS.mppSessionCloseFailed);
2678
- } else if (err instanceof ZeroInsufficientFundsError) {
2679
- return errorFetchResult(
2680
- startedAt,
2681
- err.message,
2682
- FETCH_SKIP_REASONS.insufficientFunds,
2683
- capabilityIdRequested,
2684
- "insufficient_funds",
2685
- null
2686
- );
2687
- } else if (err instanceof ZeroPaymentError || err instanceof ZeroAuthError) {
2688
- const result2 = errorFetchResult(
2689
- startedAt,
2690
- err.message,
2691
- "payment_failed",
2692
- capabilityIdRequested,
2693
- "payment_failed",
2694
- 402
2695
- );
2696
- await recordErrorFetchRun(client, opts, result2);
2697
- return result2;
2698
- } else if (err instanceof ZeroTimeoutError) {
2699
- await recordTimeoutRun(
2700
- client,
2701
- opts,
2702
- err,
2703
- startedAt,
2704
- capabilityIdRequested
2705
- );
2706
- throw err;
2707
- } else {
2708
- throw err;
2709
- }
2710
- }
2711
- } else if (requirement?.protocol === "unknown") {
2712
- const result2 = errorFetchResult(
2713
- startedAt,
2714
- "Capability returned 402 with an unrecognized payment protocol. Neither x402 nor MPP markers were detected in the response headers or body.",
2715
- "unrecognized_402_protocol",
2716
- capabilityIdRequested,
2717
- "payment_failed",
2718
- 402
2719
- );
2720
- await recordErrorFetchRun(client, opts, result2);
2721
- return result2;
2722
- }
2746
+ clients = {};
2747
+ // The configured wallet address, or null if the client wasn't constructed
2748
+ // with an account. Stable read does not hit the network.
2749
+ get address() {
2750
+ const credentials = this.client.getCredentials();
2751
+ if (credentials.kind !== "account") return null;
2752
+ return credentials.account.address;
2723
2753
  }
2724
- const latencyMs = Date.now() - startedAt;
2725
- const maxResponseBytes = opts.maxResponseBytes ?? MAX_RESPONSE_BYTES;
2726
- let body;
2727
- let bodyRaw;
2728
- let bodyEncoding;
2729
- try {
2730
- const parsed = await parseResponseBody(response, maxResponseBytes);
2731
- body = parsed.body;
2732
- bodyRaw = parsed.bodyRaw;
2733
- bodyEncoding = parsed.bodyEncoding;
2734
- if (parsed.truncated) warnings.push(FETCH_WARNINGS.bodyTruncated);
2735
- } catch (err) {
2736
- if (err instanceof Error && err.name === "AbortError") {
2737
- throw new ZeroApiError("Request aborted by caller", {
2738
- status: 0,
2739
- cause: err
2740
- });
2754
+ balance = async (opts = {}) => {
2755
+ const target = opts.address ?? this.address;
2756
+ if (!target) {
2757
+ throw new ZeroWalletError(
2758
+ "balance() requires either an `address` option or a client constructed with an account"
2759
+ );
2741
2760
  }
2742
- const message = err instanceof Error ? err.message : String(err);
2743
- const skips = [];
2744
- const settledLocally = paymentHasAnchor(payment);
2745
- if (settledLocally && response.status === 402) {
2746
- warnings.push(FETCH_WARNINGS.paidButStill402NotRecorded);
2747
- skips.push(FETCH_WARNINGS.paidButStill402NotRecorded);
2761
+ const chain = opts.chain ?? "total";
2762
+ if (chain !== "total") {
2763
+ const raw = await this.readChainBalance(chain, target);
2764
+ return { amount: formatUnits(raw, USDC_DECIMALS), asset: "USDC" };
2748
2765
  }
2749
- if (payment !== null && !settledLocally) {
2750
- warnings.push(FETCH_WARNINGS.paymentSettlementUnconfirmed);
2751
- skips.push(FETCH_WARNINGS.paymentSettlementUnconfirmed);
2766
+ const chains = ["base", "tempo"];
2767
+ const settled = await Promise.allSettled(
2768
+ chains.map((c) => this.readChainBalance(c, target))
2769
+ );
2770
+ let sum = 0n;
2771
+ const errors = [];
2772
+ for (let i = 0; i < settled.length; i++) {
2773
+ const result = settled[i];
2774
+ const chainName = chains[i];
2775
+ if (!result || !chainName) continue;
2776
+ if (result.status === "fulfilled") {
2777
+ sum += result.value;
2778
+ } else {
2779
+ errors.push({ chain: chainName, message: errorMessage(result.reason) });
2780
+ }
2752
2781
  }
2753
- if (payment !== null && payment.amount === "unknown") {
2754
- warnings.push(FETCH_WARNINGS.paymentAmountUnknown);
2782
+ if (errors.length === settled.length) {
2783
+ throw new ZeroWalletError(
2784
+ `balance() failed for every chain: ${errors.map((e) => `${e.chain} (${e.message})`).join(", ")}`
2785
+ );
2755
2786
  }
2756
- skips.push(FETCH_SKIP_REASONS.bodyReadFailed);
2757
- const result2 = {
2758
- runId: null,
2759
- ok: false,
2760
- status: response.status,
2761
- latencyMs: Date.now() - startedAt,
2762
- payment,
2763
- outcome: computeOutcome(response.status, payment, warnings),
2764
- body: null,
2765
- bodyRaw: null,
2766
- error: message
2787
+ const balance = {
2788
+ amount: formatUnits(sum, USDC_DECIMALS),
2789
+ asset: "USDC"
2767
2790
  };
2768
- if (capabilityIdRequested) result2.runTrackingSkipped = skips;
2769
- if (warnings.length > 0) result2.warnings = warnings;
2770
- return result2;
2771
- }
2772
- const settled = paymentHasAnchor(payment);
2773
- const paidButStill402 = settled && response.status === 402;
2774
- const settlementUnconfirmed = payment !== null && !settled;
2775
- if (paidButStill402) warnings.push(FETCH_WARNINGS.paidButStill402NotRecorded);
2776
- if (settlementUnconfirmed)
2777
- warnings.push(FETCH_WARNINGS.paymentSettlementUnconfirmed);
2778
- if (payment !== null && payment.amount === "unknown") {
2779
- warnings.push(FETCH_WARNINGS.paymentAmountUnknown);
2780
- }
2781
- let runId = null;
2782
- const closeFailed = warnings.includes(FETCH_WARNINGS.mppSessionCloseFailed);
2783
- const upstreamErrorForRecord = buildUpstreamError(
2784
- response.status,
2785
- bodyRaw,
2786
- bodyEncoding
2791
+ if (errors.length > 0) balance.errors = errors;
2792
+ return balance;
2793
+ };
2794
+ // All wallets linked to the authenticated session user. Session-only.
2795
+ list = (opts = {}) => request(
2796
+ this.client,
2797
+ {
2798
+ method: "GET",
2799
+ path: "/v1/users/me/wallets",
2800
+ signal: opts.signal
2801
+ },
2802
+ userWalletListSchema
2787
2803
  );
2788
- if (capabilityIdRequested) {
2789
- if (paidButStill402)
2790
- skipReasons.push(FETCH_WARNINGS.paidButStill402NotRecorded);
2791
- else if (closeFailed)
2792
- skipReasons.push(FETCH_SKIP_REASONS.mppSessionCloseFailedNotRecorded);
2793
- }
2794
- const resolvedCap = await resolvePromise;
2795
- const effectiveCapabilityId = opts.capabilityId ?? resolvedCap?.capabilityId;
2796
- const capabilityResolution = !capabilityIdRequested && canRecordForResolve ? resolvedCap !== null ? {
2797
- resolved: true,
2798
- capabilityId: resolvedCap.capabilityId,
2799
- capabilitySlug: resolvedCap.capabilitySlug,
2800
- matchedVia: resolvedCap.matchedVia
2801
- } : { resolved: false } : void 0;
2802
- if (effectiveCapabilityId !== void 0 && !paidButStill402 && !closeFailed) {
2803
- const { recordingClient, canRecord } = resolveRecordingClient(client, opts);
2804
- if (!canRecord) {
2805
- skipReasons.push("no_credentials");
2806
- } else {
2807
- const knownAmount = payment !== null && payment.amount !== "unknown";
2808
- try {
2809
- let derived;
2810
- if (opts.deriveRunMetadata) {
2811
- try {
2812
- derived = opts.deriveRunMetadata({
2813
- status: response.status,
2814
- ok: response.ok,
2815
- bodyRaw,
2816
- ...bodyEncoding && { bodyEncoding }
2817
- });
2818
- } catch {
2819
- }
2820
- }
2821
- const result2 = await recordingClient.runs.create({
2822
- capabilityId: effectiveCapabilityId,
2823
- searchId: opts.searchId,
2824
- // Auto-resolved runs are always direct_url — they only fire when
2825
- // no capabilityId was supplied (the direct_url path by definition).
2826
- ...opts.capabilityId === void 0 ? { fetchOrigin: "direct_url" } : opts.fetchOrigin && { fetchOrigin: opts.fetchOrigin },
2827
- status: response.status,
2828
- latencyMs,
2829
- ...opts.requestSchema && { requestSchema: opts.requestSchema },
2830
- ...derived?.responseSchema && {
2831
- responseSchema: derived.responseSchema
2832
- },
2833
- ...upstreamErrorForRecord && {
2834
- errorSnippetHash: upstreamErrorForRecord.snippetHash,
2835
- errorSnippetLength: upstreamErrorForRecord.snippetLength
2836
- },
2837
- ...payment && {
2838
- ...knownAmount && {
2839
- costAmount: payment.amount,
2840
- costAsset: payment.asset
2841
- },
2842
- paymentProtocol: payment.protocol,
2843
- paymentChain: payment.chain,
2844
- paymentTxHash: payment.txHash ?? void 0,
2845
- paymentMode: payment.session ? "session" : "charge"
2846
- }
2847
- });
2848
- runId = result2.runId;
2849
- } catch (err) {
2850
- const errorMessage2 = err instanceof Error ? err.message : String(err);
2851
- const code = err instanceof ZeroError ? err.code : void 0;
2852
- const skipMsg = code ? `run_create_failed:${code}: ${errorMessage2}` : `run_create_failed: ${errorMessage2}`;
2853
- skipReasons.push(skipMsg);
2854
- client.logger?.({
2855
- level: "warn",
2856
- message: "Failed to record run for fetch",
2857
- meta: {
2858
- url: redactUrl(url),
2859
- capabilityId: effectiveCapabilityId,
2860
- error: errorMessage2,
2861
- errorCode: code
2862
- }
2804
+ // Provision a managed (Privy-embedded) wallet for the session user.
2805
+ // Idempotent server-side — repeat calls return the existing wallet.
2806
+ provision = (opts = {}) => request(
2807
+ this.client,
2808
+ {
2809
+ method: "POST",
2810
+ path: "/v1/users/me/wallets/provision",
2811
+ signal: opts.signal
2812
+ },
2813
+ userWalletDtoSchema
2814
+ );
2815
+ // Relays a BYO-wallet-signed EIP-3009 ReceiveWithAuthorization, sweeping
2816
+ // USDC from the BYO wallet to the user's Zero wallet on Base via the
2817
+ // gas-paying relayer. The SDK does not author the authorization (chain
2818
+ // nonce + EIP-712 domain coupling stays caller-side); it only forwards
2819
+ // the signed payload. Session-only.
2820
+ migrateAuthorization = (authorization, opts = {}) => request(
2821
+ this.client,
2822
+ {
2823
+ method: "POST",
2824
+ path: "/v1/users/me/wallets/migrate",
2825
+ body: { authorization },
2826
+ signal: opts.signal
2827
+ },
2828
+ migrateAuthorizationResponseSchema
2829
+ );
2830
+ // Routes by identity: account mode signs EIP-191 against /v1/wallet/fund-url,
2831
+ // session mode hits /v1/users/me/fund-url (Bearer-authed, wallet resolved
2832
+ // server-side). Both honor `provider` (coinbase | stripe).
2833
+ fundingUrl = async (opts = {}) => {
2834
+ const credentials = this.client.getCredentials();
2835
+ const path = credentials.kind === "account" ? "/v1/wallet/fund-url" : credentials.kind === "session" ? "/v1/users/me/fund-url" : null;
2836
+ if (!path) {
2837
+ throw new ZeroWalletError(
2838
+ "fundingUrl() requires a client constructed with an account or session credentials"
2839
+ );
2840
+ }
2841
+ const query = {
2842
+ provider: opts.provider ?? "coinbase"
2843
+ };
2844
+ if (opts.amount) query.amount = opts.amount;
2845
+ const { url } = await request(
2846
+ this.client,
2847
+ { method: "GET", path, query, signal: opts.signal },
2848
+ fundingUrlResponseSchema
2849
+ );
2850
+ return url;
2851
+ };
2852
+ getPublicClient = (chain) => {
2853
+ if (chain === "base") {
2854
+ if (!this.clients.base) {
2855
+ this.clients.base = createPublicClient({
2856
+ chain: baseChain,
2857
+ transport: http()
2863
2858
  });
2864
2859
  }
2860
+ return this.clients.base;
2865
2861
  }
2866
- }
2867
- const result = {
2868
- runId,
2869
- ok: response.ok,
2870
- status: response.status,
2871
- latencyMs,
2872
- payment,
2873
- outcome: computeOutcome(response.status, payment, warnings),
2874
- body,
2875
- bodyRaw
2862
+ if (!this.clients.tempo) {
2863
+ this.clients.tempo = createPublicClient({
2864
+ // biome-ignore lint/suspicious/noExplicitAny: tempo is a custom chain object that viem's stricter Chain type rejects shape-wise but accepts at runtime
2865
+ chain: tempoChain2,
2866
+ transport: http()
2867
+ });
2868
+ }
2869
+ return this.clients.tempo;
2876
2870
  };
2877
- if (bodyEncoding) result.bodyEncoding = bodyEncoding;
2878
- if (!response.ok) {
2879
- const candidate = bodyRaw !== null && bodyRaw.length > 0 && bodyEncoding !== "base64" ? bodyRaw.slice(0, 240) : null;
2880
- result.error = candidate !== null && !requestBodyEchoed(candidate, opts.body) ? candidate : `HTTP ${response.status}`;
2881
- if (upstreamErrorForRecord) result.upstreamError = upstreamErrorForRecord;
2882
- }
2883
- if (capabilityIdRequested && skipReasons.length > 0) {
2884
- result.runTrackingSkipped = skipReasons;
2885
- }
2886
- if (warnings.length > 0) result.warnings = warnings;
2887
- if (capabilityResolution !== void 0)
2888
- result.capabilityResolution = capabilityResolution;
2889
- return result;
2871
+ /**
2872
+ * @internal
2873
+ *
2874
+ * Per-chain USDC balance read. Exposed (rather than private) so unit
2875
+ * tests can stub it without standing up a real viem public client —
2876
+ * viem's `http()` transport doesn't go through `client.fetchImpl`, so
2877
+ * the standard mock-fetch trick doesn't cover this path.
2878
+ */
2879
+ readChainBalance = async (chain, address) => {
2880
+ const publicClient = this.getPublicClient(chain);
2881
+ const tokenAddress = chain === "base" ? USDC_BASE2 : USDC_TEMPO2;
2882
+ const balance = await publicClient.readContract({
2883
+ address: tokenAddress,
2884
+ abi: ERC20_BALANCE_ABI2,
2885
+ functionName: "balanceOf",
2886
+ args: [address]
2887
+ });
2888
+ return balance;
2889
+ };
2890
+ };
2891
+
2892
+ // src/options.ts
2893
+ var DEFAULT_BASE_URL = "https://api.zero.xyz";
2894
+ var DEFAULT_TIMEOUT_MS = 18e4;
2895
+ var DEFAULT_MAX_RETRIES = 2;
2896
+
2897
+ // src/auth/credentials.ts
2898
+ var noopRefresh = async () => {
2890
2899
  };
2891
2900
  var searchResultSchema = z.object({
2892
2901
  id: z.string(),
2902
+ // Opaque attribution token (format: `z_{shortId}.{position}`). Optional
2903
+ // so old API deploys that don't emit it still parse cleanly. Pass as
2904
+ // capabilityId to client.fetch() for exact search attribution.
2905
+ token: z.string().nullable().optional(),
2893
2906
  position: z.number(),
2894
2907
  slug: z.string(),
2895
2908
  name: z.string(),
@@ -2908,6 +2921,9 @@ var searchResultSchema = z.object({
2908
2921
  cost: z.object({ amount: z.string(), asset: z.string() }),
2909
2922
  reviewCount: z.number().optional(),
2910
2923
  rating: ratingSchema,
2924
+ // All-time activation count (non-deleted runs), mirroring capabilities.ts.
2925
+ // Optional so a response from an older API deploy still parses (ZERO-385).
2926
+ activationCount: z.number().optional(),
2911
2927
  availabilityStatus: z.enum(["healthy", "unknown", "down"]).nullable().optional(),
2912
2928
  /**
2913
2929
  * @deprecated The API no longer emits `displayStatus`; it has been replaced
@@ -3299,6 +3315,6 @@ var ZeroClient = class _ZeroClient {
3299
3315
  };
3300
3316
  };
3301
3317
 
3302
- export { Auth, AuthDevice, BUG_REPORT_CATEGORIES, BugReports, Capabilities, DEFAULT_BASE_URL, DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT_MS, FETCH_SKIP_REASONS, FETCH_WARNINGS, Payments, Runs, SDK_VERSION, TEMPO_CHAIN_ID, TEMPO_TESTNET_CHAIN_ID, Wallet, ZeroApiError, ZeroAuthError, ZeroClient, ZeroConfigurationError, ZeroError, ZeroPaymentError, ZeroSessionCloseFailedError, ZeroTimeoutError, ZeroValidationError, ZeroWalletError, asSchemaNode, coerceTempoChainId, createManagedAccount, extractInputEnvelope, normalizeTransportEnvelopeRequest, paymentHasAnchor, tempoChainLabelFromId, unwrapTransportEnvelope };
3303
- //# sourceMappingURL=chunk-3YKWO4B2.js.map
3304
- //# sourceMappingURL=chunk-3YKWO4B2.js.map
3318
+ export { Auth, AuthDevice, BUG_REPORT_CATEGORIES, BugReports, Capabilities, DEFAULT_BASE_URL, DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT_MS, FETCH_SKIP_REASONS, FETCH_WARNINGS, Payments, Runs, SDK_VERSION, TEMPO_CHAIN_ID, TEMPO_TESTNET_CHAIN_ID, Wallet, ZeroApiError, ZeroAuthError, ZeroClient, ZeroConfigurationError, ZeroError, ZeroPaymentError, ZeroSessionCloseFailedError, ZeroTimeoutError, ZeroValidationError, ZeroWalletError, asSchemaNode, coerceTempoChainId, createManagedAccount, extractInputEnvelope, isShortToken, normalizeTransportEnvelopeRequest, paymentHasAnchor, tempoChainLabelFromId, unwrapTransportEnvelope };
3319
+ //# sourceMappingURL=chunk-NUBD543C.js.map
3320
+ //# sourceMappingURL=chunk-NUBD543C.js.map