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