@p2pdotme/sdk 1.0.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.
Files changed (50) hide show
  1. package/README.md +155 -0
  2. package/dist/fraud-engine.cjs +598 -0
  3. package/dist/fraud-engine.cjs.map +1 -0
  4. package/dist/fraud-engine.d.cts +194 -0
  5. package/dist/fraud-engine.d.ts +194 -0
  6. package/dist/fraud-engine.mjs +549 -0
  7. package/dist/fraud-engine.mjs.map +1 -0
  8. package/dist/index.cjs +75 -0
  9. package/dist/index.cjs.map +1 -0
  10. package/dist/index.d.cts +49 -0
  11. package/dist/index.d.ts +49 -0
  12. package/dist/index.mjs +46 -0
  13. package/dist/index.mjs.map +1 -0
  14. package/dist/order-routing.cjs +882 -0
  15. package/dist/order-routing.cjs.map +1 -0
  16. package/dist/order-routing.d.cts +68 -0
  17. package/dist/order-routing.d.ts +68 -0
  18. package/dist/order-routing.mjs +854 -0
  19. package/dist/order-routing.mjs.map +1 -0
  20. package/dist/payload.cjs +3164 -0
  21. package/dist/payload.cjs.map +1 -0
  22. package/dist/payload.d.cts +162 -0
  23. package/dist/payload.d.ts +162 -0
  24. package/dist/payload.mjs +3120 -0
  25. package/dist/payload.mjs.map +1 -0
  26. package/dist/profile.cjs +695 -0
  27. package/dist/profile.cjs.map +1 -0
  28. package/dist/profile.d.cts +133 -0
  29. package/dist/profile.d.ts +133 -0
  30. package/dist/profile.mjs +667 -0
  31. package/dist/profile.mjs.map +1 -0
  32. package/dist/qr-parsers.cjs +366 -0
  33. package/dist/qr-parsers.cjs.map +1 -0
  34. package/dist/qr-parsers.d.cts +41 -0
  35. package/dist/qr-parsers.d.ts +41 -0
  36. package/dist/qr-parsers.mjs +338 -0
  37. package/dist/qr-parsers.mjs.map +1 -0
  38. package/dist/react.cjs +4803 -0
  39. package/dist/react.cjs.map +1 -0
  40. package/dist/react.d.cts +511 -0
  41. package/dist/react.d.ts +511 -0
  42. package/dist/react.mjs +4759 -0
  43. package/dist/react.mjs.map +1 -0
  44. package/dist/zkkyc.cjs +868 -0
  45. package/dist/zkkyc.cjs.map +1 -0
  46. package/dist/zkkyc.d.cts +230 -0
  47. package/dist/zkkyc.d.ts +230 -0
  48. package/dist/zkkyc.mjs +824 -0
  49. package/dist/zkkyc.mjs.map +1 -0
  50. package/package.json +130 -0
@@ -0,0 +1,854 @@
1
+ // src/order-routing/client.ts
2
+ import { stringToHex as stringToHex3 } from "viem";
3
+
4
+ // src/contracts/abis/index.ts
5
+ import { erc20Abi } from "viem";
6
+
7
+ // src/contracts/abis/order-flow-facet.ts
8
+ var orderFlowFacetAbi = [
9
+ {
10
+ inputs: [
11
+ { internalType: "uint256", name: "circleId", type: "uint256" },
12
+ { internalType: "uint256", name: "assignUpto", type: "uint256" },
13
+ { internalType: "bytes32", name: "currency", type: "bytes32" },
14
+ { internalType: "address", name: "user", type: "address" },
15
+ { internalType: "uint256", name: "usdtAmount", type: "uint256" },
16
+ { internalType: "uint256", name: "fiatAmount", type: "uint256" },
17
+ { internalType: "int256", name: "orderType", type: "int256" },
18
+ { internalType: "uint256", name: "preferredPCConfigId", type: "uint256" }
19
+ ],
20
+ name: "getAssignableMerchantsFromCircle",
21
+ outputs: [{ internalType: "address[]", name: "", type: "address[]" }],
22
+ stateMutability: "view",
23
+ type: "function"
24
+ },
25
+ {
26
+ inputs: [
27
+ { internalType: "address", name: "_user", type: "address" },
28
+ { internalType: "bytes32", name: "_nativeCurrency", type: "bytes32" }
29
+ ],
30
+ name: "userTxLimit",
31
+ outputs: [
32
+ { internalType: "uint256", name: "", type: "uint256" },
33
+ { internalType: "uint256", name: "", type: "uint256" }
34
+ ],
35
+ stateMutability: "view",
36
+ type: "function"
37
+ }
38
+ ];
39
+
40
+ // src/contracts/abis/p2p-config-facet.ts
41
+ var p2pConfigFacetAbi = [
42
+ {
43
+ inputs: [
44
+ {
45
+ internalType: "bytes32",
46
+ name: "_currency",
47
+ type: "bytes32"
48
+ }
49
+ ],
50
+ name: "getPriceConfig",
51
+ outputs: [
52
+ {
53
+ components: [
54
+ {
55
+ internalType: "uint256",
56
+ name: "buyPrice",
57
+ type: "uint256"
58
+ },
59
+ {
60
+ internalType: "uint256",
61
+ name: "sellPrice",
62
+ type: "uint256"
63
+ },
64
+ {
65
+ internalType: "int256",
66
+ name: "buyPriceOffset",
67
+ type: "int256"
68
+ },
69
+ {
70
+ internalType: "uint256",
71
+ name: "baseSpread",
72
+ type: "uint256"
73
+ }
74
+ ],
75
+ internalType: "struct P2pConfigStorage.PriceConfig",
76
+ name: "",
77
+ type: "tuple"
78
+ }
79
+ ],
80
+ stateMutability: "view",
81
+ type: "function"
82
+ },
83
+ {
84
+ inputs: [
85
+ {
86
+ internalType: "bytes32",
87
+ name: "_nativeCurrency",
88
+ type: "bytes32"
89
+ }
90
+ ],
91
+ name: "getRpPerUsdtLimitRational",
92
+ outputs: [
93
+ {
94
+ internalType: "uint256",
95
+ name: "numerator",
96
+ type: "uint256"
97
+ },
98
+ {
99
+ internalType: "uint256",
100
+ name: "denominator",
101
+ type: "uint256"
102
+ }
103
+ ],
104
+ stateMutability: "view",
105
+ type: "function"
106
+ }
107
+ ];
108
+
109
+ // src/contracts/abis/reputation-manager.ts
110
+ var reputationManagerAbi = [
111
+ {
112
+ inputs: [
113
+ {
114
+ internalType: "string",
115
+ name: "_socialName",
116
+ type: "string"
117
+ },
118
+ {
119
+ components: [
120
+ {
121
+ components: [
122
+ {
123
+ internalType: "string",
124
+ name: "provider",
125
+ type: "string"
126
+ },
127
+ {
128
+ internalType: "string",
129
+ name: "parameters",
130
+ type: "string"
131
+ },
132
+ {
133
+ internalType: "string",
134
+ name: "context",
135
+ type: "string"
136
+ }
137
+ ],
138
+ internalType: "struct IReclaimSDK.ClaimInfo",
139
+ name: "claimInfo",
140
+ type: "tuple"
141
+ },
142
+ {
143
+ components: [
144
+ {
145
+ components: [
146
+ {
147
+ internalType: "bytes32",
148
+ name: "identifier",
149
+ type: "bytes32"
150
+ },
151
+ {
152
+ internalType: "address",
153
+ name: "owner",
154
+ type: "address"
155
+ },
156
+ {
157
+ internalType: "uint32",
158
+ name: "timestampS",
159
+ type: "uint32"
160
+ },
161
+ {
162
+ internalType: "uint32",
163
+ name: "epoch",
164
+ type: "uint32"
165
+ }
166
+ ],
167
+ internalType: "struct IReclaimSDK.CompleteClaimData",
168
+ name: "claim",
169
+ type: "tuple"
170
+ },
171
+ {
172
+ internalType: "bytes[]",
173
+ name: "signatures",
174
+ type: "bytes[]"
175
+ }
176
+ ],
177
+ internalType: "struct IReclaimSDK.SignedClaim",
178
+ name: "signedClaim",
179
+ type: "tuple"
180
+ }
181
+ ],
182
+ internalType: "struct IReclaimSDK.Proof[]",
183
+ name: "proofs",
184
+ type: "tuple[]"
185
+ }
186
+ ],
187
+ name: "socialVerify",
188
+ outputs: [],
189
+ stateMutability: "nonpayable",
190
+ type: "function"
191
+ },
192
+ {
193
+ inputs: [
194
+ {
195
+ internalType: "uint256",
196
+ name: "nullifierSeed",
197
+ type: "uint256"
198
+ },
199
+ {
200
+ internalType: "uint256",
201
+ name: "nullifier",
202
+ type: "uint256"
203
+ },
204
+ {
205
+ internalType: "uint256",
206
+ name: "timestamp",
207
+ type: "uint256"
208
+ },
209
+ {
210
+ internalType: "uint256",
211
+ name: "signal",
212
+ type: "uint256"
213
+ },
214
+ {
215
+ internalType: "uint256[4]",
216
+ name: "revealArray",
217
+ type: "uint256[4]"
218
+ },
219
+ {
220
+ internalType: "uint256[8]",
221
+ name: "groth16Proof",
222
+ type: "uint256[8]"
223
+ }
224
+ ],
225
+ name: "submitAnonAadharProof",
226
+ outputs: [],
227
+ stateMutability: "nonpayable",
228
+ type: "function"
229
+ },
230
+ {
231
+ inputs: [
232
+ {
233
+ components: [
234
+ {
235
+ internalType: "bytes32",
236
+ name: "version",
237
+ type: "bytes32"
238
+ },
239
+ {
240
+ components: [
241
+ {
242
+ internalType: "bytes32",
243
+ name: "vkeyHash",
244
+ type: "bytes32"
245
+ },
246
+ {
247
+ internalType: "bytes",
248
+ name: "proof",
249
+ type: "bytes"
250
+ },
251
+ {
252
+ internalType: "bytes32[]",
253
+ name: "publicInputs",
254
+ type: "bytes32[]"
255
+ }
256
+ ],
257
+ internalType: "struct ProofVerificationData",
258
+ name: "proofVerificationData",
259
+ type: "tuple"
260
+ },
261
+ {
262
+ internalType: "bytes",
263
+ name: "committedInputs",
264
+ type: "bytes"
265
+ },
266
+ {
267
+ components: [
268
+ {
269
+ internalType: "uint256",
270
+ name: "validityPeriodInSeconds",
271
+ type: "uint256"
272
+ },
273
+ {
274
+ internalType: "string",
275
+ name: "domain",
276
+ type: "string"
277
+ },
278
+ {
279
+ internalType: "string",
280
+ name: "scope",
281
+ type: "string"
282
+ },
283
+ {
284
+ internalType: "bool",
285
+ name: "devMode",
286
+ type: "bool"
287
+ }
288
+ ],
289
+ internalType: "struct ServiceConfig",
290
+ name: "serviceConfig",
291
+ type: "tuple"
292
+ }
293
+ ],
294
+ internalType: "struct ProofVerificationParams",
295
+ name: "params",
296
+ type: "tuple"
297
+ },
298
+ {
299
+ internalType: "bool",
300
+ name: "isIDCard",
301
+ type: "bool"
302
+ }
303
+ ],
304
+ name: "zkPassportRegister",
305
+ outputs: [],
306
+ stateMutability: "nonpayable",
307
+ type: "function"
308
+ }
309
+ ];
310
+
311
+ // src/contracts/abis/index.ts
312
+ var DIAMOND_ABI = [...orderFlowFacetAbi, ...p2pConfigFacetAbi];
313
+ var ABIS = {
314
+ DIAMOND: DIAMOND_ABI,
315
+ FACETS: {
316
+ ORDER_FLOW: orderFlowFacetAbi,
317
+ CONFIG: p2pConfigFacetAbi
318
+ },
319
+ EXTERNAL: {
320
+ USDC: erc20Abi,
321
+ REPUTATION_MANAGER: reputationManagerAbi
322
+ }
323
+ };
324
+
325
+ // src/contracts/order-flow/index.ts
326
+ import { ResultAsync } from "neverthrow";
327
+
328
+ // src/lib/logger.ts
329
+ var noop = () => {
330
+ };
331
+ var noopLogger = {
332
+ debug: noop,
333
+ info: noop,
334
+ warn: noop,
335
+ error: noop
336
+ };
337
+
338
+ // src/lib/sleep.ts
339
+ function sleep(ms) {
340
+ return new Promise((resolve) => setTimeout(resolve, ms));
341
+ }
342
+
343
+ // src/validation/errors.ts
344
+ var SdkError = class extends Error {
345
+ code;
346
+ cause;
347
+ context;
348
+ constructor(message, options) {
349
+ super(message);
350
+ this.name = "SdkError";
351
+ this.code = options.code;
352
+ this.cause = options.cause;
353
+ this.context = options.context;
354
+ }
355
+ };
356
+
357
+ // src/validation/schemas.ts
358
+ import { err, ok } from "neverthrow";
359
+ import { isAddress } from "viem";
360
+ import { z } from "zod";
361
+ var ZodAddressSchema = z.string().refine((s) => isAddress(s), { message: "Invalid Ethereum address" });
362
+ var ZodCurrencySchema = z.enum([
363
+ "IDR",
364
+ "INR",
365
+ "BRL",
366
+ "ARS",
367
+ "MEX",
368
+ "VEN",
369
+ "EUR",
370
+ "NGN",
371
+ "USD"
372
+ ]);
373
+ function validate(schema, data, toError) {
374
+ const result = schema.safeParse(data);
375
+ if (result.success) {
376
+ return ok(result.data);
377
+ }
378
+ return err(toError(z.prettifyError(result.error), result.error, data));
379
+ }
380
+
381
+ // src/order-routing/errors.ts
382
+ var OrderRoutingError = class extends SdkError {
383
+ constructor(message, options) {
384
+ super(message, options);
385
+ this.name = "OrderRoutingError";
386
+ }
387
+ };
388
+
389
+ // src/order-routing/validation.ts
390
+ import { z as z2 } from "zod";
391
+ var ZodCircleScoreStateSchema = z2.object({
392
+ activeMerchantsCount: z2.coerce.number()
393
+ });
394
+ var ZodCircleMetricsForRoutingSchema = z2.object({
395
+ circleScore: z2.coerce.number(),
396
+ circleStatus: z2.string(),
397
+ scoreState: ZodCircleScoreStateSchema
398
+ });
399
+ var ZodCircleForRoutingSchema = z2.object({
400
+ circleId: z2.string(),
401
+ currency: z2.string(),
402
+ metrics: ZodCircleMetricsForRoutingSchema
403
+ });
404
+ var ZodCirclesForRoutingResponseSchema = z2.object({
405
+ circles: z2.array(ZodCircleForRoutingSchema)
406
+ });
407
+ var ZodCheckCircleEligibilityParamsSchema = z2.object({
408
+ circleId: z2.bigint(),
409
+ currency: z2.string(),
410
+ user: ZodAddressSchema,
411
+ usdtAmount: z2.bigint(),
412
+ fiatAmount: z2.bigint(),
413
+ orderType: z2.bigint(),
414
+ preferredPCConfigId: z2.bigint()
415
+ });
416
+ var ZodSelectCircleParamsSchema = z2.object({
417
+ currency: z2.string().min(1),
418
+ user: ZodAddressSchema,
419
+ usdtAmount: z2.bigint(),
420
+ fiatAmount: z2.bigint(),
421
+ orderType: z2.bigint(),
422
+ preferredPCConfigId: z2.bigint()
423
+ });
424
+
425
+ // src/contracts/order-flow/index.ts
426
+ function checkCircleEligibility(publicClient, contractAddress, params, logger = noopLogger) {
427
+ return validate(
428
+ ZodCheckCircleEligibilityParamsSchema,
429
+ params,
430
+ (message, cause, d) => new OrderRoutingError(message, { code: "VALIDATION_ERROR", cause, context: { data: d } })
431
+ ).asyncAndThen((validated) => {
432
+ logger.debug("checking on-chain eligibility", {
433
+ circleId: String(validated.circleId),
434
+ contractAddress
435
+ });
436
+ return ResultAsync.fromPromise(
437
+ publicClient.readContract({
438
+ address: contractAddress,
439
+ abi: ABIS.FACETS.ORDER_FLOW,
440
+ functionName: "getAssignableMerchantsFromCircle",
441
+ args: [
442
+ validated.circleId,
443
+ 1n,
444
+ validated.currency,
445
+ validated.user,
446
+ validated.usdtAmount,
447
+ validated.fiatAmount,
448
+ validated.orderType,
449
+ validated.preferredPCConfigId
450
+ ]
451
+ }),
452
+ (error) => new OrderRoutingError("Eligibility check failed", {
453
+ code: "CONTRACT_READ_ERROR",
454
+ cause: error,
455
+ context: { circleId: String(params.circleId) }
456
+ })
457
+ );
458
+ }).map((merchants) => {
459
+ const arr = merchants;
460
+ const eligible = arr.length >= 1;
461
+ logger.debug("eligibility check result", {
462
+ circleId: String(params.circleId),
463
+ assignableMerchants: arr.length,
464
+ eligible
465
+ });
466
+ return eligible;
467
+ });
468
+ }
469
+
470
+ // src/contracts/p2p-config/index.ts
471
+ import { ResultAsync as ResultAsync2 } from "neverthrow";
472
+ import { stringToHex } from "viem";
473
+
474
+ // src/profile/validation.ts
475
+ import { z as z3 } from "zod";
476
+ var ZodUsdcBalanceParamsSchema = z3.object({
477
+ address: ZodAddressSchema
478
+ });
479
+ var ZodGetBalancesParamsSchema = z3.object({
480
+ address: ZodAddressSchema,
481
+ currency: ZodCurrencySchema
482
+ });
483
+ var ZodTxLimitsParamsSchema = z3.object({
484
+ address: ZodAddressSchema,
485
+ currency: ZodCurrencySchema
486
+ });
487
+ var ZodPriceConfigParamsSchema = z3.object({
488
+ currency: ZodCurrencySchema
489
+ });
490
+
491
+ // src/contracts/reputation-manager/writes.ts
492
+ import { Result } from "neverthrow";
493
+ import { encodeFunctionData } from "viem";
494
+
495
+ // src/zkkyc/validation.ts
496
+ import { z as z4 } from "zod";
497
+ var ZodAnonAadharProofParamsSchema = z4.object({
498
+ nullifierSeed: z4.bigint(),
499
+ nullifier: z4.bigint(),
500
+ timestamp: z4.bigint(),
501
+ signal: z4.bigint(),
502
+ revealArray: z4.tuple([z4.bigint(), z4.bigint(), z4.bigint(), z4.bigint()]),
503
+ packedGroth16Proof: z4.tuple([
504
+ z4.bigint(),
505
+ z4.bigint(),
506
+ z4.bigint(),
507
+ z4.bigint(),
508
+ z4.bigint(),
509
+ z4.bigint(),
510
+ z4.bigint(),
511
+ z4.bigint()
512
+ ])
513
+ });
514
+ var ZodSocialVerifyParamsSchema = z4.object({
515
+ _socialName: z4.string(),
516
+ proofs: z4.array(
517
+ z4.object({
518
+ claimInfo: z4.object({
519
+ provider: z4.string(),
520
+ parameters: z4.string(),
521
+ context: z4.string()
522
+ }),
523
+ signedClaim: z4.object({
524
+ claim: z4.object({
525
+ identifier: z4.string(),
526
+ owner: ZodAddressSchema,
527
+ timestampS: z4.number(),
528
+ epoch: z4.number()
529
+ }),
530
+ signatures: z4.array(z4.string())
531
+ })
532
+ })
533
+ )
534
+ });
535
+ var ZodSolidityVerifierParametersSchema = z4.object({
536
+ version: z4.string().refine((val) => val.startsWith("0x"), {
537
+ message: "Version must be a hex string"
538
+ }),
539
+ proofVerificationData: z4.object({
540
+ vkeyHash: z4.string().refine((val) => /^0x[a-fA-F0-9]{64}$/.test(val), {
541
+ message: "Invalid bytes32 hex string"
542
+ }),
543
+ proof: z4.string().refine((val) => val.startsWith("0x"), {
544
+ message: "Proof must be a hex string"
545
+ }),
546
+ publicInputs: z4.array(
547
+ z4.string().refine((val) => /^0x[a-fA-F0-9]{64}$/.test(val), {
548
+ message: "Each public input must be a valid bytes32 hex string"
549
+ })
550
+ )
551
+ }),
552
+ committedInputs: z4.string().refine((val) => val.startsWith("0x"), {
553
+ message: "Committed inputs must be a hex string"
554
+ }),
555
+ serviceConfig: z4.object({
556
+ validityPeriodInSeconds: z4.number().int().nonnegative(),
557
+ domain: z4.string(),
558
+ scope: z4.string(),
559
+ devMode: z4.boolean()
560
+ })
561
+ });
562
+ var ZodZkPassportRegisterParamsSchema = z4.object({
563
+ params: ZodSolidityVerifierParametersSchema,
564
+ isIDCard: z4.boolean()
565
+ });
566
+
567
+ // src/contracts/tx-limits/index.ts
568
+ import { ResultAsync as ResultAsync3 } from "neverthrow";
569
+ import { formatUnits, stringToHex as stringToHex2 } from "viem";
570
+
571
+ // src/contracts/usdc/index.ts
572
+ import { ResultAsync as ResultAsync4 } from "neverthrow";
573
+
574
+ // src/order-routing/routing.ts
575
+ import { errAsync, okAsync } from "neverthrow";
576
+ var EPSILON = 0.25;
577
+ var RECOVERY_SCALE = 0.3;
578
+ var BOOTSTRAP_MAX_WEIGHT = 25;
579
+ var MAX_VALIDATION_ATTEMPTS = 3;
580
+ function circleWeight(c) {
581
+ const score = c.metrics.circleScore;
582
+ if (c.metrics.circleStatus === "paused") {
583
+ return score * RECOVERY_SCALE;
584
+ }
585
+ if (c.metrics.circleStatus === "bootstrap") {
586
+ return Math.min(score, BOOTSTRAP_MAX_WEIGHT);
587
+ }
588
+ return score;
589
+ }
590
+ function filterEligibleCircles(circles, orderCurrency) {
591
+ return circles.filter((c) => c.currency.toLowerCase() === orderCurrency.toLowerCase());
592
+ }
593
+ function weightedRandomChoice(arr, weights) {
594
+ const totalWeight = weights.reduce((sum, w) => sum + w, 0);
595
+ if (totalWeight === 0) {
596
+ return arr[Math.floor(Math.random() * arr.length)];
597
+ }
598
+ let rand = Math.random() * totalWeight;
599
+ for (let i = 0; i < arr.length; i++) {
600
+ rand -= weights[i];
601
+ if (rand <= 0) {
602
+ return arr[i];
603
+ }
604
+ }
605
+ return arr[arr.length - 1];
606
+ }
607
+ function selectCircle(eligible) {
608
+ if (eligible.length === 0) {
609
+ return null;
610
+ }
611
+ const activeCircles = eligible.filter((c) => c.metrics.circleStatus === "active");
612
+ const isExplore = Math.random() < EPSILON;
613
+ if (isExplore) {
614
+ const weights2 = eligible.map(circleWeight);
615
+ return weightedRandomChoice(eligible, weights2);
616
+ }
617
+ if (activeCircles.length === 0) {
618
+ const weights2 = eligible.map(circleWeight);
619
+ return weightedRandomChoice(eligible, weights2);
620
+ }
621
+ const weights = activeCircles.map((c) => c.metrics.circleScore);
622
+ return weightedRandomChoice(activeCircles, weights);
623
+ }
624
+ function selectCircleForOrderAsync(circles, orderCurrency, validateCircle, logger = noopLogger) {
625
+ const eligible = filterEligibleCircles(circles, orderCurrency);
626
+ let remaining = [...eligible];
627
+ logger.debug("filtering eligible circles", {
628
+ total: circles.length,
629
+ eligible: eligible.length,
630
+ currency: orderCurrency,
631
+ circles: eligible
632
+ });
633
+ if (eligible.length === 0) {
634
+ logger.warn("no eligible circles found for currency", { currency: orderCurrency });
635
+ }
636
+ function attempt(attemptsLeft) {
637
+ if (attemptsLeft <= 0 || remaining.length === 0) {
638
+ logger.warn("exhausted all attempts or circles", {
639
+ attemptsLeft,
640
+ remainingCircles: remaining.length
641
+ });
642
+ return errAsync(
643
+ new OrderRoutingError("No eligible circles found", {
644
+ code: "NO_ELIGIBLE_CIRCLES"
645
+ })
646
+ );
647
+ }
648
+ const selected = selectCircle(remaining);
649
+ if (!selected) {
650
+ return errAsync(
651
+ new OrderRoutingError("No eligible circles found", {
652
+ code: "NO_ELIGIBLE_CIRCLES"
653
+ })
654
+ );
655
+ }
656
+ const circleId = BigInt(selected.circleId);
657
+ logger.debug("selected circle, validating on-chain", {
658
+ circleId: String(circleId),
659
+ status: selected.metrics.circleStatus,
660
+ score: selected.metrics.circleScore,
661
+ attemptsLeft
662
+ });
663
+ return validateCircle(circleId).orElse((error) => {
664
+ logger.warn("validation errored, treating as ineligible", {
665
+ circleId: String(circleId),
666
+ error: String(error)
667
+ });
668
+ return okAsync(false);
669
+ }).andThen((isValid) => {
670
+ if (isValid) {
671
+ logger.info("circle validated successfully", { circleId: String(circleId) });
672
+ return okAsync(circleId);
673
+ }
674
+ logger.debug("circle failed validation, retrying", {
675
+ circleId: String(circleId),
676
+ remainingCircles: remaining.length - 1
677
+ });
678
+ remaining = remaining.filter((c) => c.circleId !== selected.circleId);
679
+ return attempt(attemptsLeft - 1);
680
+ });
681
+ }
682
+ return attempt(MAX_VALIDATION_ATTEMPTS);
683
+ }
684
+
685
+ // src/order-routing/subgraph/client.ts
686
+ import { ResultAsync as ResultAsync5 } from "neverthrow";
687
+ var DEFAULT_TIMEOUT_MS = 1e4;
688
+ var MAX_RETRIES = 3;
689
+ var BACKOFF_MS = 500;
690
+ function isTransient(error) {
691
+ if (error instanceof OrderRoutingError) return false;
692
+ if (error instanceof DOMException && error.name === "AbortError") return true;
693
+ if (error instanceof TypeError) return true;
694
+ return false;
695
+ }
696
+ function querySubgraph(url, params) {
697
+ const timeoutMs = params.timeoutMs ?? DEFAULT_TIMEOUT_MS;
698
+ const fetchOnce = async () => {
699
+ const controller = new AbortController();
700
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
701
+ try {
702
+ const response = await fetch(url, {
703
+ method: "POST",
704
+ headers: { "Content-Type": "application/json" },
705
+ body: JSON.stringify({
706
+ query: params.query,
707
+ variables: params.variables
708
+ }),
709
+ signal: controller.signal
710
+ });
711
+ if (!response.ok) {
712
+ throw new OrderRoutingError(`Subgraph request failed (status: ${response.status})`, {
713
+ code: "SUBGRAPH_ERROR",
714
+ cause: response,
715
+ context: { status: response.status }
716
+ });
717
+ }
718
+ const json = await response.json();
719
+ if (json.errors?.length > 0) {
720
+ throw new OrderRoutingError("Subgraph returned GraphQL errors", {
721
+ code: "SUBGRAPH_ERROR",
722
+ cause: json.errors,
723
+ context: { errors: json.errors }
724
+ });
725
+ }
726
+ if (!json.data) {
727
+ throw new OrderRoutingError("Subgraph returned no data", {
728
+ code: "SUBGRAPH_ERROR",
729
+ cause: "Missing data field in GraphQL response",
730
+ context: { response: json }
731
+ });
732
+ }
733
+ return json.data;
734
+ } finally {
735
+ clearTimeout(timer);
736
+ }
737
+ };
738
+ const fetchWithRetry = async () => {
739
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
740
+ try {
741
+ return await fetchOnce();
742
+ } catch (error) {
743
+ const lastAttempt = attempt === MAX_RETRIES;
744
+ if (lastAttempt || !isTransient(error)) throw error;
745
+ await sleep(BACKOFF_MS * (attempt + 1));
746
+ }
747
+ }
748
+ throw new OrderRoutingError("Subgraph query exhausted retries", {
749
+ code: "SUBGRAPH_ERROR"
750
+ });
751
+ };
752
+ return ResultAsync5.fromPromise(
753
+ fetchWithRetry(),
754
+ (error) => error instanceof OrderRoutingError ? error : new OrderRoutingError("Subgraph query failed", {
755
+ code: "SUBGRAPH_ERROR",
756
+ cause: error
757
+ })
758
+ );
759
+ }
760
+
761
+ // src/order-routing/subgraph/queries.ts
762
+ var CIRCLES_FOR_ROUTING_QUERY = (
763
+ /* GraphQL */
764
+ `
765
+ query CirclesForRouting($currency: Bytes!) {
766
+ circles(
767
+ first: 1000
768
+ where: {
769
+ currency: $currency
770
+ metrics_: {
771
+ circleStatus_in: ["active", "bootstrap", "paused"]
772
+ }
773
+ }
774
+ ) {
775
+ circleId
776
+ currency
777
+ metrics {
778
+ circleScore
779
+ circleStatus
780
+ scoreState {
781
+ activeMerchantsCount
782
+ }
783
+ }
784
+ }
785
+ }
786
+ `
787
+ );
788
+
789
+ // src/order-routing/subgraph/index.ts
790
+ function getCirclesForRouting(subgraphUrl, currency, logger = noopLogger) {
791
+ logger.debug("fetching circles from subgraph", { subgraphUrl, currency });
792
+ return querySubgraph(subgraphUrl, {
793
+ query: CIRCLES_FOR_ROUTING_QUERY,
794
+ variables: { currency }
795
+ }).andThen(
796
+ (data) => validate(
797
+ ZodCirclesForRoutingResponseSchema,
798
+ data,
799
+ (message, cause, d) => new OrderRoutingError(message, { code: "VALIDATION_ERROR", cause, context: { data: d } })
800
+ ).map((validated) => {
801
+ const circles = validated.circles.filter(
802
+ (item) => Number(item.metrics.scoreState.activeMerchantsCount) > 0
803
+ );
804
+ logger.info("fetched circles from subgraph", {
805
+ total: validated.circles.length,
806
+ withActiveMerchants: circles.length,
807
+ circles
808
+ });
809
+ return circles;
810
+ })
811
+ );
812
+ }
813
+
814
+ // src/order-routing/client.ts
815
+ function createOrderRouter(config) {
816
+ const { subgraphUrl, publicClient, contractAddress } = config;
817
+ const logger = config.logger ?? noopLogger;
818
+ return {
819
+ selectCircle(params) {
820
+ const currencyHex = stringToHex3(params.currency, { size: 32 });
821
+ logger.info("selectCircle started", {
822
+ currency: params.currency,
823
+ user: params.user,
824
+ orderType: String(params.orderType)
825
+ });
826
+ return getCirclesForRouting(subgraphUrl, currencyHex, logger).andThen(
827
+ (circles) => selectCircleForOrderAsync(
828
+ circles,
829
+ currencyHex,
830
+ (circleId) => checkCircleEligibility(
831
+ publicClient,
832
+ contractAddress,
833
+ {
834
+ circleId,
835
+ currency: currencyHex,
836
+ user: params.user,
837
+ usdtAmount: params.usdtAmount,
838
+ fiatAmount: params.fiatAmount,
839
+ orderType: params.orderType,
840
+ preferredPCConfigId: params.preferredPCConfigId
841
+ },
842
+ logger
843
+ ),
844
+ logger
845
+ )
846
+ );
847
+ }
848
+ };
849
+ }
850
+ export {
851
+ OrderRoutingError,
852
+ createOrderRouter
853
+ };
854
+ //# sourceMappingURL=order-routing.mjs.map