@zeroxyz/sdk 0.5.0 → 0.7.0

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