@neus/sdk 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -36,9 +36,6 @@ const client = new NeusClient({
36
36
  apiUrl: 'https://api.neus.network',
37
37
  // Optional: request timeout (ms)
38
38
  timeout: 30000,
39
- // Optional: server-side enterprise API key for higher limits on eligible endpoints
40
- // (Do not embed API keys in browser apps.)
41
- apiKey: process.env.NEUS_API_KEY
42
39
  });
43
40
  ```
44
41
 
@@ -90,36 +87,11 @@ if (!res.data?.eligible) {
90
87
 
91
88
  Note: `gateCheck` evaluates **existing public/discoverable proofs**. For strict real-time decisions, create a new proof via `client.verify(...)` (or `POST /api/v1/verification`) and use the final status.
92
89
 
93
- ## Lookup mode (Premium, server-side only; non-persistent)
90
+ ## Resilience & Polling
94
91
 
95
- If you need a **real-time assessment** without minting/storing proofs (no `qHash`), use lookup mode:
96
-
97
- - **Endpoint:** `POST /api/v1/verification/lookup`
98
- - **Auth:** `Authorization: Bearer sk_live_...` (or `sk_test_...`)
99
- - **Semantics:** runs `external_lookup` verifiers only; **does not** create a proof record
100
-
101
- Note: lookup mode is deployment-dependent and is not part of the stable public integrator OpenAPI (`docs/api/public-api.json`).
102
-
103
- ```javascript
104
- const res = await client.lookup({
105
- // Premium API key (keep server-side only)
106
- apiKey: process.env.NEUS_API_KEY,
107
- verifierIds: ['wallet-risk'],
108
- targetWalletAddress: '0x...',
109
- data: { chainId: 1 }
110
- });
111
-
112
- if (!res.data?.verified) {
113
- throw new Error('Rejected');
114
- }
115
- ```
116
-
117
- Note: `gateCheck()` and `lookup()` are different tools.
118
- - Use **`gateCheck()`** when you want to gate based on **existing public/discoverable proofs** (fast, minimal response).
119
- - Use **`lookup()`** when you want a **fresh, non-persistent** decision (typically billable in hosted deployments).
120
- - Only combine them if you are intentionally doing a cache-first strategy (gate-check first to avoid unnecessary lookups).
121
-
122
- ## Private proof reads (owner)
92
+ The SDK is designed for production stability:
93
+ - **Automatic Backoff**: `pollProofStatus()` automatically detects rate limiting (`429`) and applies jittered exponential backoff.
94
+ - **Wallet Identification**: Automatically attaches headers to preferred wallet-based limiting for higher reliability behind shared IPs (NATs).
123
95
 
124
96
  - Private proof by Proof ID (qHash): `client.getPrivateStatus(qHash, wallet)`
125
97
  - Private proofs by wallet/DID: `client.getPrivateProofsByWallet(walletOrDid, { limit, offset }, wallet)`
package/cjs/client.cjs CHANGED
@@ -159,7 +159,7 @@ function constructVerificationMessage({ walletAddress, signedTimestamp, data, ve
159
159
  }
160
160
  const chainContext = typeof chain === "string" && chain.length > 0 ? chain : chainId;
161
161
  if (!chainContext) {
162
- throw new SDKError("chainId is required (or provide chain for preview mode)", "INVALID_CHAIN_CONTEXT");
162
+ throw new SDKError("chainId is required (or provide chain for universal mode)", "INVALID_CHAIN_CONTEXT");
163
163
  }
164
164
  if (chainContext === chainId && typeof chainId !== "number") {
165
165
  throw new SDKError("chainId must be a number when provided", "INVALID_CHAIN_ID");
@@ -230,6 +230,67 @@ var validateVerifierData = (verifierId, data) => {
230
230
  if (!data.owner || !validateWalletAddress(data.owner)) {
231
231
  return { valid: false, error: "owner (wallet address) is required" };
232
232
  }
233
+ if (data.content !== void 0 && data.content !== null) {
234
+ if (typeof data.content !== "string") {
235
+ return { valid: false, error: "content must be a string when provided" };
236
+ }
237
+ if (data.content.length > 5e4) {
238
+ return { valid: false, error: "content exceeds 50KB inline limit" };
239
+ }
240
+ }
241
+ if (data.contentHash !== void 0 && data.contentHash !== null) {
242
+ if (typeof data.contentHash !== "string" || !/^0x[a-fA-F0-9]{64}$/.test(data.contentHash)) {
243
+ return { valid: false, error: "contentHash must be a 32-byte hex string (0x + 64 hex chars) when provided" };
244
+ }
245
+ }
246
+ if (data.contentType !== void 0 && data.contentType !== null) {
247
+ if (typeof data.contentType !== "string" || data.contentType.length > 100) {
248
+ return { valid: false, error: "contentType must be a string (max 100 chars) when provided" };
249
+ }
250
+ const base = String(data.contentType).split(";")[0].trim().toLowerCase();
251
+ if (!base || base.includes(" ") || !base.includes("/")) {
252
+ return { valid: false, error: "contentType must be a valid MIME type when provided" };
253
+ }
254
+ }
255
+ if (data.provenance !== void 0 && data.provenance !== null) {
256
+ if (!data.provenance || typeof data.provenance !== "object" || Array.isArray(data.provenance)) {
257
+ return { valid: false, error: "provenance must be an object when provided" };
258
+ }
259
+ const dk = data.provenance.declaredKind;
260
+ if (dk !== void 0 && dk !== null) {
261
+ const allowed = ["human", "ai", "mixed", "unknown"];
262
+ if (typeof dk !== "string" || !allowed.includes(dk)) {
263
+ return { valid: false, error: `provenance.declaredKind must be one of: ${allowed.join(", ")}` };
264
+ }
265
+ }
266
+ const ai = data.provenance.aiContext;
267
+ if (ai !== void 0 && ai !== null) {
268
+ if (typeof ai !== "object" || Array.isArray(ai)) {
269
+ return { valid: false, error: "provenance.aiContext must be an object when provided" };
270
+ }
271
+ if (ai.generatorType !== void 0 && ai.generatorType !== null) {
272
+ const allowed = ["local", "saas", "agent"];
273
+ if (typeof ai.generatorType !== "string" || !allowed.includes(ai.generatorType)) {
274
+ return { valid: false, error: `provenance.aiContext.generatorType must be one of: ${allowed.join(", ")}` };
275
+ }
276
+ }
277
+ if (ai.provider !== void 0 && ai.provider !== null) {
278
+ if (typeof ai.provider !== "string" || ai.provider.length > 64) {
279
+ return { valid: false, error: "provenance.aiContext.provider must be a string (max 64 chars) when provided" };
280
+ }
281
+ }
282
+ if (ai.model !== void 0 && ai.model !== null) {
283
+ if (typeof ai.model !== "string" || ai.model.length > 128) {
284
+ return { valid: false, error: "provenance.aiContext.model must be a string (max 128 chars) when provided" };
285
+ }
286
+ }
287
+ if (ai.runId !== void 0 && ai.runId !== null) {
288
+ if (typeof ai.runId !== "string" || ai.runId.length > 128) {
289
+ return { valid: false, error: "provenance.aiContext.runId must be a string (max 128 chars) when provided" };
290
+ }
291
+ }
292
+ }
293
+ }
233
294
  if (data.reference !== void 0) {
234
295
  if (!data.reference || typeof data.reference !== "object") {
235
296
  return { valid: false, error: "reference must be an object when provided" };
@@ -368,6 +429,17 @@ var validateVerifierData = (verifierId, data) => {
368
429
  if (!validTypes.includes(contentType)) {
369
430
  return { valid: false, error: `contentType must be one of: ${validTypes.join(", ")}` };
370
431
  }
432
+ const isTextual = contentType.startsWith("text/") || contentType.includes("markdown");
433
+ if (isTextual) {
434
+ try {
435
+ const maxBytes = 50 * 1024;
436
+ const bytes = typeof TextEncoder !== "undefined" ? new TextEncoder().encode(data.content).length : String(data.content).length;
437
+ if (bytes > maxBytes) {
438
+ return { valid: false, error: `content exceeds ${maxBytes} bytes limit for ai-content-moderation verifier (text)` };
439
+ }
440
+ } catch {
441
+ }
442
+ }
371
443
  }
372
444
  if (data.content.length > 13653334) {
373
445
  return { valid: false, error: "content exceeds 10MB limit" };
@@ -455,7 +527,7 @@ var NeusClient = class {
455
527
  * data: {
456
528
  * content: "My content",
457
529
  * owner: walletAddress, // or ownerAddress for nft-ownership/token-holding
458
- * reference: { type: 'content', id: 'my-unique-identifier' }
530
+ * reference: { type: 'other', id: 'my-unique-identifier' }
459
531
  * },
460
532
  * walletAddress: '0x...',
461
533
  * signature: '0x...',
@@ -543,6 +615,7 @@ var NeusClient = class {
543
615
  reference: data2.reference,
544
616
  ...data2.content && { content: data2.content },
545
617
  ...data2.contentHash && { contentHash: data2.contentHash },
618
+ ...data2.contentType && { contentType: data2.contentType },
546
619
  ...data2.provenance && { provenance: data2.provenance }
547
620
  };
548
621
  } else {
@@ -1037,9 +1110,11 @@ ${bytes.length}`;
1037
1110
  throw new ValidationError("qHash is required");
1038
1111
  }
1039
1112
  const startTime = Date.now();
1113
+ let consecutiveRateLimits = 0;
1040
1114
  while (Date.now() - startTime < timeout) {
1041
1115
  try {
1042
1116
  const status = await this.getStatus(qHash);
1117
+ consecutiveRateLimits = 0;
1043
1118
  if (onProgress && typeof onProgress === "function") {
1044
1119
  onProgress(status.data || status);
1045
1120
  }
@@ -1054,7 +1129,17 @@ ${bytes.length}`;
1054
1129
  if (error instanceof ValidationError) {
1055
1130
  throw error;
1056
1131
  }
1057
- await new Promise((resolve) => setTimeout(resolve, interval));
1132
+ let nextDelay = interval;
1133
+ if (error instanceof ApiError && Number(error.statusCode) === 429) {
1134
+ consecutiveRateLimits += 1;
1135
+ const exp = Math.min(6, consecutiveRateLimits);
1136
+ const base = Math.max(500, Number(interval) || 0);
1137
+ const max = 3e4;
1138
+ const backoff = Math.min(max, base * Math.pow(2, exp));
1139
+ const jitter = Math.floor(backoff * (0.5 + Math.random() * 0.5));
1140
+ nextDelay = jitter;
1141
+ }
1142
+ await new Promise((resolve) => setTimeout(resolve, nextDelay));
1058
1143
  }
1059
1144
  }
1060
1145
  throw new NetworkError(`Polling timeout after ${timeout}ms`, "POLLING_TIMEOUT");
@@ -1148,7 +1233,7 @@ ${bytes.length}`;
1148
1233
  return true;
1149
1234
  }
1150
1235
  // ============================================================================
1151
- // GATE & LOOKUP METHODS
1236
+ // PROOFS & GATING METHODS
1152
1237
  // ============================================================================
1153
1238
  /**
1154
1239
  * GET PROOFS BY WALLET - Fetch proofs for a wallet address
@@ -1281,45 +1366,6 @@ ${bytes.length}`;
1281
1366
  nextOffset: response.data?.nextOffset ?? null
1282
1367
  };
1283
1368
  }
1284
- /**
1285
- * LOOKUP MODE (API) - Non-persistent server-to-server checks
1286
- *
1287
- * Runs `external_lookup` verifiers without minting/storing a proof.
1288
- * Requires an enterprise API key (server-side only).
1289
- *
1290
- * @param {Object} params
1291
- * @param {string} params.apiKey - Enterprise API key (sk_live_... or sk_test_...)
1292
- * @param {Array<string>} params.verifierIds - Verifiers to run (external_lookup only)
1293
- * @param {string} params.targetWalletAddress - Wallet to evaluate
1294
- * @param {Object} [params.data] - Verifier input data (e.g., contractAddress/tokenId/chainId)
1295
- * @returns {Promise<Object>} API response ({ success, data })
1296
- */
1297
- async lookup(params = {}) {
1298
- const apiKey = (params.apiKey || "").toString().trim();
1299
- if (!apiKey || !(apiKey.startsWith("sk_live_") || apiKey.startsWith("sk_test_"))) {
1300
- throw new ValidationError("lookup requires apiKey (sk_live_* or sk_test_*)");
1301
- }
1302
- const verifierIds = Array.isArray(params.verifierIds) ? params.verifierIds.map((v) => String(v).trim()).filter(Boolean) : [];
1303
- if (verifierIds.length === 0) {
1304
- throw new ValidationError("lookup requires verifierIds (non-empty array)");
1305
- }
1306
- const targetWalletAddress = (params.targetWalletAddress || "").toString().trim();
1307
- if (!targetWalletAddress || !/^0x[a-fA-F0-9]{40}$/i.test(targetWalletAddress)) {
1308
- throw new ValidationError("lookup requires a valid targetWalletAddress (0x...)");
1309
- }
1310
- const body = {
1311
- verifierIds,
1312
- targetWalletAddress,
1313
- data: params.data && typeof params.data === "object" ? params.data : {}
1314
- };
1315
- const response = await this._makeRequest("POST", "/api/v1/verification/lookup", body, {
1316
- Authorization: `Bearer ${apiKey}`
1317
- });
1318
- if (!response.success) {
1319
- throw new ApiError(`Lookup failed: ${response.error?.message || "Unknown error"}`, response.error);
1320
- }
1321
- return response;
1322
- }
1323
1369
  /**
1324
1370
  * GATE CHECK (API) - Minimal eligibility check
1325
1371
  *
@@ -1337,7 +1383,6 @@ ${bytes.length}`;
1337
1383
  * @param {number} [params.sinceDays] - Optional time window in days
1338
1384
  * @param {number} [params.since] - Optional unix timestamp in ms (lower bound)
1339
1385
  * @param {number} [params.limit] - Max rows to scan (server may clamp)
1340
- * @param {string} [params.select] - Comma-separated projections (handle,provider,profileUrl,traits.<key>)
1341
1386
  * @returns {Promise<Object>} API response ({ success, data })
1342
1387
  */
1343
1388
  async gateCheck(params = {}) {
@@ -1377,7 +1422,6 @@ ${bytes.length}`;
1377
1422
  setIfPresent("sinceDays", params.sinceDays);
1378
1423
  setIfPresent("since", params.since);
1379
1424
  setIfPresent("limit", params.limit);
1380
- setCsvIfPresent("select", params.select);
1381
1425
  setIfPresent("referenceType", params.referenceType);
1382
1426
  setIfPresent("referenceId", params.referenceId);
1383
1427
  setIfPresent("tag", params.tag);
@@ -1391,7 +1435,6 @@ ${bytes.length}`;
1391
1435
  setIfPresent("domain", params.domain);
1392
1436
  setIfPresent("minBalance", params.minBalance);
1393
1437
  setIfPresent("provider", params.provider);
1394
- setIfPresent("handle", params.handle);
1395
1438
  setIfPresent("ownerAddress", params.ownerAddress);
1396
1439
  setIfPresent("riskLevel", params.riskLevel);
1397
1440
  setBoolIfPresent("sanctioned", params.sanctioned);
@@ -1399,8 +1442,6 @@ ${bytes.length}`;
1399
1442
  setIfPresent("primaryWalletAddress", params.primaryWalletAddress);
1400
1443
  setIfPresent("secondaryWalletAddress", params.secondaryWalletAddress);
1401
1444
  setIfPresent("verificationMethod", params.verificationMethod);
1402
- setIfPresent("traitPath", params.traitPath);
1403
- setIfPresent("traitGte", params.traitGte);
1404
1445
  const response = await this._makeRequest("GET", `/api/v1/proofs/gate/check?${qs.toString()}`);
1405
1446
  if (!response.success) {
1406
1447
  throw new ApiError(`Gate check failed: ${response.error?.message || "Unknown error"}`, response.error);
package/cjs/index.cjs CHANGED
@@ -197,7 +197,7 @@ function constructVerificationMessage({ walletAddress, signedTimestamp, data, ve
197
197
  }
198
198
  const chainContext = typeof chain === "string" && chain.length > 0 ? chain : chainId;
199
199
  if (!chainContext) {
200
- throw new SDKError("chainId is required (or provide chain for preview mode)", "INVALID_CHAIN_CONTEXT");
200
+ throw new SDKError("chainId is required (or provide chain for universal mode)", "INVALID_CHAIN_CONTEXT");
201
201
  }
202
202
  if (chainContext === chainId && typeof chainId !== "number") {
203
203
  throw new SDKError("chainId must be a number when provided", "INVALID_CHAIN_ID");
@@ -472,10 +472,14 @@ function validateVerifierPayload(verifierId, data) {
472
472
  result.warnings.push("ownerAddress omitted (most deployments default to the signed walletAddress)");
473
473
  }
474
474
  } else if (id === "ownership-basic") {
475
- ["content"].forEach((key) => {
476
- if (!(key in data))
477
- result.missing.push(key);
478
- });
475
+ if (!("owner" in data))
476
+ result.missing.push("owner");
477
+ const hasContent = typeof data.content === "string" && data.content.length > 0;
478
+ const hasContentHash = typeof data.contentHash === "string" && data.contentHash.length > 0;
479
+ const hasRefId = typeof data.reference?.id === "string" && data.reference.id.length > 0;
480
+ if (!hasContent && !hasContentHash && !hasRefId) {
481
+ result.missing.push("content (or contentHash or reference.id)");
482
+ }
479
483
  }
480
484
  if (result.missing.length > 0) {
481
485
  result.valid = false;
@@ -648,10 +652,24 @@ var init_utils = __esm({
648
652
  }
649
653
  setTimeout(pollAttempt, this.currentInterval);
650
654
  } catch (error) {
651
- reject(new SDKError(
652
- `Polling failed: ${error.message}`,
653
- "POLLING_ERROR"
654
- ));
655
+ if (error instanceof ValidationError) {
656
+ reject(error);
657
+ return;
658
+ }
659
+ if (error instanceof ApiError && error.statusCode === 429 || error?.isRetryable === true) {
660
+ if (this.options.exponentialBackoff) {
661
+ const next = Math.min(this.currentInterval * 2, this.options.maxInterval);
662
+ const jitter = next * (0.5 + Math.random() * 0.5);
663
+ this.currentInterval = Math.max(250, Math.floor(jitter));
664
+ }
665
+ if (this.attempt >= this.options.maxAttempts) {
666
+ reject(new SDKError("Verification polling timeout", "POLLING_TIMEOUT"));
667
+ return;
668
+ }
669
+ setTimeout(pollAttempt, this.currentInterval);
670
+ return;
671
+ }
672
+ reject(new SDKError(`Polling failed: ${error.message}`, "POLLING_ERROR"));
655
673
  }
656
674
  };
657
675
  pollAttempt();
@@ -714,6 +732,67 @@ var init_client = __esm({
714
732
  if (!data.owner || !validateWalletAddress(data.owner)) {
715
733
  return { valid: false, error: "owner (wallet address) is required" };
716
734
  }
735
+ if (data.content !== void 0 && data.content !== null) {
736
+ if (typeof data.content !== "string") {
737
+ return { valid: false, error: "content must be a string when provided" };
738
+ }
739
+ if (data.content.length > 5e4) {
740
+ return { valid: false, error: "content exceeds 50KB inline limit" };
741
+ }
742
+ }
743
+ if (data.contentHash !== void 0 && data.contentHash !== null) {
744
+ if (typeof data.contentHash !== "string" || !/^0x[a-fA-F0-9]{64}$/.test(data.contentHash)) {
745
+ return { valid: false, error: "contentHash must be a 32-byte hex string (0x + 64 hex chars) when provided" };
746
+ }
747
+ }
748
+ if (data.contentType !== void 0 && data.contentType !== null) {
749
+ if (typeof data.contentType !== "string" || data.contentType.length > 100) {
750
+ return { valid: false, error: "contentType must be a string (max 100 chars) when provided" };
751
+ }
752
+ const base = String(data.contentType).split(";")[0].trim().toLowerCase();
753
+ if (!base || base.includes(" ") || !base.includes("/")) {
754
+ return { valid: false, error: "contentType must be a valid MIME type when provided" };
755
+ }
756
+ }
757
+ if (data.provenance !== void 0 && data.provenance !== null) {
758
+ if (!data.provenance || typeof data.provenance !== "object" || Array.isArray(data.provenance)) {
759
+ return { valid: false, error: "provenance must be an object when provided" };
760
+ }
761
+ const dk = data.provenance.declaredKind;
762
+ if (dk !== void 0 && dk !== null) {
763
+ const allowed = ["human", "ai", "mixed", "unknown"];
764
+ if (typeof dk !== "string" || !allowed.includes(dk)) {
765
+ return { valid: false, error: `provenance.declaredKind must be one of: ${allowed.join(", ")}` };
766
+ }
767
+ }
768
+ const ai = data.provenance.aiContext;
769
+ if (ai !== void 0 && ai !== null) {
770
+ if (typeof ai !== "object" || Array.isArray(ai)) {
771
+ return { valid: false, error: "provenance.aiContext must be an object when provided" };
772
+ }
773
+ if (ai.generatorType !== void 0 && ai.generatorType !== null) {
774
+ const allowed = ["local", "saas", "agent"];
775
+ if (typeof ai.generatorType !== "string" || !allowed.includes(ai.generatorType)) {
776
+ return { valid: false, error: `provenance.aiContext.generatorType must be one of: ${allowed.join(", ")}` };
777
+ }
778
+ }
779
+ if (ai.provider !== void 0 && ai.provider !== null) {
780
+ if (typeof ai.provider !== "string" || ai.provider.length > 64) {
781
+ return { valid: false, error: "provenance.aiContext.provider must be a string (max 64 chars) when provided" };
782
+ }
783
+ }
784
+ if (ai.model !== void 0 && ai.model !== null) {
785
+ if (typeof ai.model !== "string" || ai.model.length > 128) {
786
+ return { valid: false, error: "provenance.aiContext.model must be a string (max 128 chars) when provided" };
787
+ }
788
+ }
789
+ if (ai.runId !== void 0 && ai.runId !== null) {
790
+ if (typeof ai.runId !== "string" || ai.runId.length > 128) {
791
+ return { valid: false, error: "provenance.aiContext.runId must be a string (max 128 chars) when provided" };
792
+ }
793
+ }
794
+ }
795
+ }
717
796
  if (data.reference !== void 0) {
718
797
  if (!data.reference || typeof data.reference !== "object") {
719
798
  return { valid: false, error: "reference must be an object when provided" };
@@ -852,6 +931,17 @@ var init_client = __esm({
852
931
  if (!validTypes.includes(contentType)) {
853
932
  return { valid: false, error: `contentType must be one of: ${validTypes.join(", ")}` };
854
933
  }
934
+ const isTextual = contentType.startsWith("text/") || contentType.includes("markdown");
935
+ if (isTextual) {
936
+ try {
937
+ const maxBytes = 50 * 1024;
938
+ const bytes = typeof TextEncoder !== "undefined" ? new TextEncoder().encode(data.content).length : String(data.content).length;
939
+ if (bytes > maxBytes) {
940
+ return { valid: false, error: `content exceeds ${maxBytes} bytes limit for ai-content-moderation verifier (text)` };
941
+ }
942
+ } catch {
943
+ }
944
+ }
855
945
  }
856
946
  if (data.content.length > 13653334) {
857
947
  return { valid: false, error: "content exceeds 10MB limit" };
@@ -939,7 +1029,7 @@ var init_client = __esm({
939
1029
  * data: {
940
1030
  * content: "My content",
941
1031
  * owner: walletAddress, // or ownerAddress for nft-ownership/token-holding
942
- * reference: { type: 'content', id: 'my-unique-identifier' }
1032
+ * reference: { type: 'other', id: 'my-unique-identifier' }
943
1033
  * },
944
1034
  * walletAddress: '0x...',
945
1035
  * signature: '0x...',
@@ -1027,6 +1117,7 @@ var init_client = __esm({
1027
1117
  reference: data2.reference,
1028
1118
  ...data2.content && { content: data2.content },
1029
1119
  ...data2.contentHash && { contentHash: data2.contentHash },
1120
+ ...data2.contentType && { contentType: data2.contentType },
1030
1121
  ...data2.provenance && { provenance: data2.provenance }
1031
1122
  };
1032
1123
  } else {
@@ -1521,9 +1612,11 @@ ${bytes.length}`;
1521
1612
  throw new ValidationError("qHash is required");
1522
1613
  }
1523
1614
  const startTime = Date.now();
1615
+ let consecutiveRateLimits = 0;
1524
1616
  while (Date.now() - startTime < timeout) {
1525
1617
  try {
1526
1618
  const status = await this.getStatus(qHash);
1619
+ consecutiveRateLimits = 0;
1527
1620
  if (onProgress && typeof onProgress === "function") {
1528
1621
  onProgress(status.data || status);
1529
1622
  }
@@ -1538,7 +1631,17 @@ ${bytes.length}`;
1538
1631
  if (error instanceof ValidationError) {
1539
1632
  throw error;
1540
1633
  }
1541
- await new Promise((resolve) => setTimeout(resolve, interval));
1634
+ let nextDelay = interval;
1635
+ if (error instanceof ApiError && Number(error.statusCode) === 429) {
1636
+ consecutiveRateLimits += 1;
1637
+ const exp = Math.min(6, consecutiveRateLimits);
1638
+ const base = Math.max(500, Number(interval) || 0);
1639
+ const max = 3e4;
1640
+ const backoff = Math.min(max, base * Math.pow(2, exp));
1641
+ const jitter = Math.floor(backoff * (0.5 + Math.random() * 0.5));
1642
+ nextDelay = jitter;
1643
+ }
1644
+ await new Promise((resolve) => setTimeout(resolve, nextDelay));
1542
1645
  }
1543
1646
  }
1544
1647
  throw new NetworkError(`Polling timeout after ${timeout}ms`, "POLLING_TIMEOUT");
@@ -1632,7 +1735,7 @@ ${bytes.length}`;
1632
1735
  return true;
1633
1736
  }
1634
1737
  // ============================================================================
1635
- // GATE & LOOKUP METHODS
1738
+ // PROOFS & GATING METHODS
1636
1739
  // ============================================================================
1637
1740
  /**
1638
1741
  * GET PROOFS BY WALLET - Fetch proofs for a wallet address
@@ -1765,45 +1868,6 @@ ${bytes.length}`;
1765
1868
  nextOffset: response.data?.nextOffset ?? null
1766
1869
  };
1767
1870
  }
1768
- /**
1769
- * LOOKUP MODE (API) - Non-persistent server-to-server checks
1770
- *
1771
- * Runs `external_lookup` verifiers without minting/storing a proof.
1772
- * Requires an enterprise API key (server-side only).
1773
- *
1774
- * @param {Object} params
1775
- * @param {string} params.apiKey - Enterprise API key (sk_live_... or sk_test_...)
1776
- * @param {Array<string>} params.verifierIds - Verifiers to run (external_lookup only)
1777
- * @param {string} params.targetWalletAddress - Wallet to evaluate
1778
- * @param {Object} [params.data] - Verifier input data (e.g., contractAddress/tokenId/chainId)
1779
- * @returns {Promise<Object>} API response ({ success, data })
1780
- */
1781
- async lookup(params = {}) {
1782
- const apiKey = (params.apiKey || "").toString().trim();
1783
- if (!apiKey || !(apiKey.startsWith("sk_live_") || apiKey.startsWith("sk_test_"))) {
1784
- throw new ValidationError("lookup requires apiKey (sk_live_* or sk_test_*)");
1785
- }
1786
- const verifierIds = Array.isArray(params.verifierIds) ? params.verifierIds.map((v) => String(v).trim()).filter(Boolean) : [];
1787
- if (verifierIds.length === 0) {
1788
- throw new ValidationError("lookup requires verifierIds (non-empty array)");
1789
- }
1790
- const targetWalletAddress = (params.targetWalletAddress || "").toString().trim();
1791
- if (!targetWalletAddress || !/^0x[a-fA-F0-9]{40}$/i.test(targetWalletAddress)) {
1792
- throw new ValidationError("lookup requires a valid targetWalletAddress (0x...)");
1793
- }
1794
- const body = {
1795
- verifierIds,
1796
- targetWalletAddress,
1797
- data: params.data && typeof params.data === "object" ? params.data : {}
1798
- };
1799
- const response = await this._makeRequest("POST", "/api/v1/verification/lookup", body, {
1800
- Authorization: `Bearer ${apiKey}`
1801
- });
1802
- if (!response.success) {
1803
- throw new ApiError(`Lookup failed: ${response.error?.message || "Unknown error"}`, response.error);
1804
- }
1805
- return response;
1806
- }
1807
1871
  /**
1808
1872
  * GATE CHECK (API) - Minimal eligibility check
1809
1873
  *
@@ -1821,7 +1885,6 @@ ${bytes.length}`;
1821
1885
  * @param {number} [params.sinceDays] - Optional time window in days
1822
1886
  * @param {number} [params.since] - Optional unix timestamp in ms (lower bound)
1823
1887
  * @param {number} [params.limit] - Max rows to scan (server may clamp)
1824
- * @param {string} [params.select] - Comma-separated projections (handle,provider,profileUrl,traits.<key>)
1825
1888
  * @returns {Promise<Object>} API response ({ success, data })
1826
1889
  */
1827
1890
  async gateCheck(params = {}) {
@@ -1861,7 +1924,6 @@ ${bytes.length}`;
1861
1924
  setIfPresent("sinceDays", params.sinceDays);
1862
1925
  setIfPresent("since", params.since);
1863
1926
  setIfPresent("limit", params.limit);
1864
- setCsvIfPresent("select", params.select);
1865
1927
  setIfPresent("referenceType", params.referenceType);
1866
1928
  setIfPresent("referenceId", params.referenceId);
1867
1929
  setIfPresent("tag", params.tag);
@@ -1875,7 +1937,6 @@ ${bytes.length}`;
1875
1937
  setIfPresent("domain", params.domain);
1876
1938
  setIfPresent("minBalance", params.minBalance);
1877
1939
  setIfPresent("provider", params.provider);
1878
- setIfPresent("handle", params.handle);
1879
1940
  setIfPresent("ownerAddress", params.ownerAddress);
1880
1941
  setIfPresent("riskLevel", params.riskLevel);
1881
1942
  setBoolIfPresent("sanctioned", params.sanctioned);
@@ -1883,8 +1944,6 @@ ${bytes.length}`;
1883
1944
  setIfPresent("primaryWalletAddress", params.primaryWalletAddress);
1884
1945
  setIfPresent("secondaryWalletAddress", params.secondaryWalletAddress);
1885
1946
  setIfPresent("verificationMethod", params.verificationMethod);
1886
- setIfPresent("traitPath", params.traitPath);
1887
- setIfPresent("traitGte", params.traitGte);
1888
1947
  const response = await this._makeRequest("GET", `/api/v1/proofs/gate/check?${qs.toString()}`);
1889
1948
  if (!response.success) {
1890
1949
  throw new ApiError(`Gate check failed: ${response.error?.message || "Unknown error"}`, response.error);
package/cjs/utils.cjs CHANGED
@@ -75,6 +75,49 @@ var SDKError = class _SDKError extends Error {
75
75
  };
76
76
  }
77
77
  };
78
+ var ApiError = class _ApiError extends SDKError {
79
+ constructor(message, statusCode = 500, code = "API_ERROR", response = null) {
80
+ super(message, code);
81
+ this.name = "ApiError";
82
+ this.statusCode = statusCode;
83
+ this.response = response;
84
+ this.isClientError = statusCode >= 400 && statusCode < 500;
85
+ this.isServerError = statusCode >= 500;
86
+ this.isRetryable = this.isServerError || statusCode === 429;
87
+ }
88
+ static fromResponse(response, responseData) {
89
+ const statusCode = response.status;
90
+ const message = responseData?.error?.message || responseData?.message || `API request failed with status ${statusCode}`;
91
+ const code = responseData?.error?.code || "API_ERROR";
92
+ return new _ApiError(message, statusCode, code, responseData);
93
+ }
94
+ toJSON() {
95
+ return {
96
+ ...super.toJSON(),
97
+ statusCode: this.statusCode,
98
+ isClientError: this.isClientError,
99
+ isServerError: this.isServerError,
100
+ isRetryable: this.isRetryable
101
+ };
102
+ }
103
+ };
104
+ var ValidationError = class extends SDKError {
105
+ constructor(message, field = null, value = null) {
106
+ super(message, "VALIDATION_ERROR");
107
+ this.name = "ValidationError";
108
+ this.field = field;
109
+ this.value = value;
110
+ this.isRetryable = false;
111
+ }
112
+ toJSON() {
113
+ return {
114
+ ...super.toJSON(),
115
+ field: this.field,
116
+ value: this.value,
117
+ isRetryable: this.isRetryable
118
+ };
119
+ }
120
+ };
78
121
 
79
122
  // utils.js
80
123
  function deterministicStringify(obj) {
@@ -108,7 +151,7 @@ function constructVerificationMessage({ walletAddress, signedTimestamp, data, ve
108
151
  }
109
152
  const chainContext = typeof chain === "string" && chain.length > 0 ? chain : chainId;
110
153
  if (!chainContext) {
111
- throw new SDKError("chainId is required (or provide chain for preview mode)", "INVALID_CHAIN_CONTEXT");
154
+ throw new SDKError("chainId is required (or provide chain for universal mode)", "INVALID_CHAIN_CONTEXT");
112
155
  }
113
156
  if (chainContext === chainId && typeof chainId !== "number") {
114
157
  throw new SDKError("chainId must be a number when provided", "INVALID_CHAIN_ID");
@@ -384,10 +427,24 @@ var StatusPoller = class {
384
427
  }
385
428
  setTimeout(pollAttempt, this.currentInterval);
386
429
  } catch (error) {
387
- reject(new SDKError(
388
- `Polling failed: ${error.message}`,
389
- "POLLING_ERROR"
390
- ));
430
+ if (error instanceof ValidationError) {
431
+ reject(error);
432
+ return;
433
+ }
434
+ if (error instanceof ApiError && error.statusCode === 429 || error?.isRetryable === true) {
435
+ if (this.options.exponentialBackoff) {
436
+ const next = Math.min(this.currentInterval * 2, this.options.maxInterval);
437
+ const jitter = next * (0.5 + Math.random() * 0.5);
438
+ this.currentInterval = Math.max(250, Math.floor(jitter));
439
+ }
440
+ if (this.attempt >= this.options.maxAttempts) {
441
+ reject(new SDKError("Verification polling timeout", "POLLING_TIMEOUT"));
442
+ return;
443
+ }
444
+ setTimeout(pollAttempt, this.currentInterval);
445
+ return;
446
+ }
447
+ reject(new SDKError(`Polling failed: ${error.message}`, "POLLING_ERROR"));
391
448
  }
392
449
  };
393
450
  pollAttempt();
@@ -464,10 +521,14 @@ function validateVerifierPayload(verifierId, data) {
464
521
  result.warnings.push("ownerAddress omitted (most deployments default to the signed walletAddress)");
465
522
  }
466
523
  } else if (id === "ownership-basic") {
467
- ["content"].forEach((key) => {
468
- if (!(key in data))
469
- result.missing.push(key);
470
- });
524
+ if (!("owner" in data))
525
+ result.missing.push("owner");
526
+ const hasContent = typeof data.content === "string" && data.content.length > 0;
527
+ const hasContentHash = typeof data.contentHash === "string" && data.contentHash.length > 0;
528
+ const hasRefId = typeof data.reference?.id === "string" && data.reference.id.length > 0;
529
+ if (!hasContent && !hasContentHash && !hasRefId) {
530
+ result.missing.push("content (or contentHash or reference.id)");
531
+ }
471
532
  }
472
533
  if (result.missing.length > 0) {
473
534
  result.valid = false;