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