@usdctofiat/offramp 3.0.2 → 3.1.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.
@@ -1,10 +1,14 @@
1
1
  // src/config.ts
2
2
  import { getContracts, getGatingServiceAddress } from "@zkp2p/sdk";
3
- var SDK_VERSION = "3.0.2";
3
+ var SDK_VERSION = "3.1.0";
4
4
  var BASE_CHAIN_ID = 8453;
5
5
  var RUNTIME_ENV = "production";
6
6
  var API_BASE_URL = "https://api.zkp2p.xyz";
7
7
  var BASE_RPC_URL = "https://mainnet.base.org";
8
+ function resolveApiBaseUrl(override) {
9
+ const trimmed = override?.trim();
10
+ return (trimmed && trimmed.length > 0 ? trimmed : API_BASE_URL).replace(/\/$/, "");
11
+ }
8
12
  var contracts = getContracts(BASE_CHAIN_ID, RUNTIME_ENV);
9
13
  var addresses = contracts.addresses;
10
14
  var addrs = addresses;
@@ -21,11 +25,45 @@ var DEFAULT_INTENT_GUARDIAN_ADDRESS = "0xe29a5BD4D0CEbA6C125A0361E7D20ab4eA275C2
21
25
  var REFERRER = "galleonlabs";
22
26
  var MIN_DEPOSIT_USDC = 1;
23
27
  var MIN_ORDER_USDC = 1;
28
+ var MAX_ORDER_USDC = 2500;
24
29
  var INDEXER_MAX_ATTEMPTS = 12;
25
30
  var INDEXER_INITIAL_DELAY_MS = 1e3;
26
31
  var INDEXER_MAX_DELAY_MS = 1e4;
27
32
  var PAYEE_REGISTRATION_TIMEOUT_MS = 8e3;
28
33
 
34
+ // src/errors.ts
35
+ var MakersCreateError = class extends Error {
36
+ status;
37
+ body;
38
+ constructor(message, status, body) {
39
+ super(message);
40
+ this.name = "MakersCreateError";
41
+ this.status = status;
42
+ this.body = body;
43
+ }
44
+ };
45
+ var OfframpError = class extends Error {
46
+ code;
47
+ step;
48
+ cause;
49
+ txHash;
50
+ depositId;
51
+ constructor(message, code, step, cause, details) {
52
+ super(message);
53
+ this.name = "OfframpError";
54
+ this.code = code;
55
+ this.step = step;
56
+ this.cause = cause;
57
+ this.txHash = details?.txHash;
58
+ this.depositId = details?.depositId;
59
+ }
60
+ };
61
+ function isUserCancellation(error) {
62
+ if (!(error instanceof Error)) return false;
63
+ const msg = error.message.toLowerCase();
64
+ return msg.includes("user rejected") || msg.includes("user denied") || msg.includes("user cancelled") || msg.includes("rejected the request") || msg.includes("action_rejected");
65
+ }
66
+
29
67
  // src/platforms.ts
30
68
  import {
31
69
  currencyInfo,
@@ -102,7 +140,8 @@ var FALLBACK_CURRENCIES = {
102
140
  zelle: ["USD"],
103
141
  paypal: ["USD", "EUR", "GBP", "SGD", "NZD", "AUD", "CAD"],
104
142
  monzo: ["GBP"],
105
- n26: ["EUR"]
143
+ n26: ["EUR"],
144
+ luxon: ["USD", "EUR", "GBP"]
106
145
  };
107
146
  function gatherCatalogHashes(platform) {
108
147
  if (platform === "zelle") {
@@ -126,13 +165,39 @@ function resolveSupportedCurrencies(platform) {
126
165
  function normalizePaypalMeUsername(value) {
127
166
  const trimmed = value.trim();
128
167
  if (!trimmed) return "";
129
- const withoutProtocol = trimmed.replace(/^https?:\/\//i, "").replace(/^www\./i, "");
130
- if (/^paypal\.me(?:[?#].*)?$/i.test(withoutProtocol)) return "";
131
- const withoutDomain = withoutProtocol.replace(/^paypal\.me\//i, "");
132
- const [pathWithoutQuery] = withoutDomain.split(/[?#]/, 1);
168
+ const withoutProtocol = trimmed.replace(/^https?:\/\//i, "");
169
+ const [pathWithoutQuery] = withoutProtocol.split(/[?#]/, 1);
133
170
  const sanitizedPath = pathWithoutQuery.replace(/^\/+/, "");
134
- const [username] = sanitizedPath.split("/", 1);
135
- return username.replace(/^@+/, "").trim().toLowerCase();
171
+ const [host = "", ...pathSegments] = sanitizedPath.split("/");
172
+ const normalizedHost = host.toLowerCase().replace(/^www\./i, "");
173
+ if (normalizedHost === "paypal.me") {
174
+ const [username = ""] = pathSegments;
175
+ return username.replace(/^@+/, "").trim().toLowerCase();
176
+ }
177
+ if (normalizedHost === "paypal.com") {
178
+ const [paypalMePath = "", username = ""] = pathSegments;
179
+ if (paypalMePath.toLowerCase() !== "paypalme") return "";
180
+ return username.replace(/^@+/, "").trim().toLowerCase();
181
+ }
182
+ if (/^https?:\/\//i.test(trimmed) || trimmed.includes("/")) {
183
+ return trimmed;
184
+ }
185
+ return trimmed.replace(/^@+/, "").toLowerCase();
186
+ }
187
+ function isValidIBAN(iban) {
188
+ const upper = iban.toUpperCase();
189
+ if (!/^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$/.test(upper)) return false;
190
+ const rearranged = upper.slice(4) + upper.slice(0, 4);
191
+ const numeric = rearranged.split("").map((ch) => {
192
+ const code = ch.charCodeAt(0);
193
+ return code >= 65 && code <= 90 ? (code - 55).toString() : ch;
194
+ }).join("");
195
+ let remainder = numeric;
196
+ while (remainder.length > 2) {
197
+ const block = remainder.slice(0, 9);
198
+ remainder = (parseInt(block, 10) % 97).toString() + remainder.slice(block.length);
199
+ }
200
+ return parseInt(remainder, 10) % 97 === 1;
136
201
  }
137
202
  var BLUEPRINTS = {
138
203
  venmo: {
@@ -184,9 +249,9 @@ var BLUEPRINTS = {
184
249
  transform: (v) => v.replace(/^@+/, "").trim(),
185
250
  extensionRegistration: {
186
251
  providerId: "wise",
187
- requiredPrompt: "This Wisetag is not registered yet. Verify it in the Peer extension, then refresh and retry with the same Wisetag.",
188
- ctaLabel: "Install Peer Extension",
189
- minExtensionVersion: "0.4.12"
252
+ requiredPrompt: "This Wisetag is not registered yet. Register it with PeerAuth, then retry with the same Wisetag.",
253
+ ctaLabel: "Register Wisetag",
254
+ minExtensionVersion: "0.5.0"
190
255
  }
191
256
  },
192
257
  mercadopago: {
@@ -203,7 +268,11 @@ var BLUEPRINTS = {
203
268
  identifierLabel: "Email",
204
269
  placeholder: "email",
205
270
  helperText: "Registered Zelle email",
206
- validation: z.string().email()
271
+ // Curator's ZelleProcessor rejects any non-lowercase email outright, so
272
+ // canonicalize to lowercase here rather than letting /v2/makers/create
273
+ // reject after the maker has already approved USDC + paid gas.
274
+ validation: z.string().email(),
275
+ transform: (v) => v.trim().toLowerCase()
207
276
  },
208
277
  paypal: {
209
278
  id: "paypal",
@@ -211,14 +280,17 @@ var BLUEPRINTS = {
211
280
  identifierLabel: "PayPal.me Username",
212
281
  placeholder: "paypal.me/your-username",
213
282
  helperText: "Your PayPal.me username, not your email",
214
- validation: z.string().min(1, "PayPal.me username is required").regex(/^[a-z0-9._-]+$/i, "Use your PayPal.me username (letters, numbers, . _ -)"),
283
+ validation: z.string().min(1, "PayPal.me username is required").regex(
284
+ /^[a-z0-9._-]+$/i,
285
+ "Use your PayPal.me username or paypal.me link, not a shortened URL."
286
+ ),
215
287
  transform: normalizePaypalMeUsername,
216
288
  extensionRegistration: {
217
289
  providerId: "paypal",
218
- requiredPrompt: "This PayPal username is not registered yet. Verify it in the Peer extension, then refresh and retry with the same PayPal username.",
219
- ctaLabel: "Install Peer Extension",
290
+ requiredPrompt: "This PayPal username is not registered yet. Register it with PeerAuth, then retry with the same PayPal username.",
291
+ ctaLabel: "Register PayPal Username",
220
292
  ctaSubtext: "PayPal Business accounts are not supported at this time.",
221
- minExtensionVersion: "0.4.14"
293
+ minExtensionVersion: "0.5.0"
222
294
  }
223
295
  },
224
296
  monzo: {
@@ -235,8 +307,20 @@ var BLUEPRINTS = {
235
307
  identifierLabel: "IBAN",
236
308
  placeholder: "IBAN (e.g. DE89...)",
237
309
  helperText: "Your IBAN (spaces will be removed)",
238
- validation: z.string().min(15).max(34).regex(/^[A-Z]{2}[0-9]{2}[A-Z0-9]+$/i),
310
+ // transform runs before validation, so the schema sees a spaceless,
311
+ // uppercased IBAN. The MOD-97 refine mirrors curator's N26Processor so a
312
+ // typo'd-but-well-formed IBAN fails here, not after USDC approval + gas.
313
+ validation: z.string().regex(/^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$/, "Enter a valid IBAN").refine(isValidIBAN, "That IBAN looks wrong \u2014 its checksum doesn't match"),
239
314
  transform: (v) => v.replace(/\s/g, "").toUpperCase()
315
+ },
316
+ luxon: {
317
+ id: "luxon",
318
+ name: "Luxon",
319
+ identifierLabel: "Email",
320
+ placeholder: "your-luxon-email@example.com",
321
+ helperText: "Email address registered with Luxon",
322
+ validation: z.string().email(),
323
+ transform: (v) => v.trim()
240
324
  }
241
325
  };
242
326
  function buildPlatformEntry(bp) {
@@ -275,8 +359,18 @@ var PLATFORMS = {
275
359
  ZELLE: buildPlatformEntry(BLUEPRINTS.zelle),
276
360
  PAYPAL: buildPlatformEntry(BLUEPRINTS.paypal),
277
361
  MONZO: buildPlatformEntry(BLUEPRINTS.monzo),
278
- N26: buildPlatformEntry(BLUEPRINTS.n26)
362
+ N26: buildPlatformEntry(BLUEPRINTS.n26),
363
+ LUXON: buildPlatformEntry(BLUEPRINTS.luxon)
364
+ };
365
+ var PLATFORM_LIMITS = {
366
+ venmo: { platformCapUsd: 5e3 },
367
+ n26: { platformCapUsd: 1e4 },
368
+ paypal: { minTier: "PLUS" }
279
369
  };
370
+ function getPlatformLimits(platform) {
371
+ const id = platform.trim().toLowerCase();
372
+ return PLATFORM_LIMITS[id] ?? null;
373
+ }
280
374
  function getPlatformById(id) {
281
375
  return Object.values(PLATFORMS).find((p) => p.id === id) ?? null;
282
376
  }
@@ -338,6 +432,146 @@ function getPeerExtensionRegistrationInfo(platform) {
338
432
  const entry = getPlatformById(platform);
339
433
  return entry?.extensionRegistration ?? null;
340
434
  }
435
+ function getPeerExtensionRegistrationAuthParams(platform, options = {}) {
436
+ const info = getPeerExtensionRegistrationInfo(platform);
437
+ if (!info) return null;
438
+ const params = {
439
+ actionType: `transfer_${info.providerId}`,
440
+ captureMode: "sellerCredential",
441
+ platform: info.providerId
442
+ };
443
+ if (options.attestationServiceUrl) {
444
+ params.attestationServiceUrl = options.attestationServiceUrl;
445
+ }
446
+ return params;
447
+ }
448
+ function resolveRegistrationProcessor(platform) {
449
+ const info = getPeerExtensionRegistrationInfo(platform.id);
450
+ if (info?.providerId === "paypal" || info?.providerId === "wise") {
451
+ return info.providerId;
452
+ }
453
+ throw new Error(`${platform.name} does not require Peer extension registration.`);
454
+ }
455
+ async function postMakerCreate(processorName, payload, apiBaseUrl = API_BASE_URL) {
456
+ const controller = new AbortController();
457
+ const timeout = setTimeout(() => controller.abort(), PAYEE_REGISTRATION_TIMEOUT_MS);
458
+ try {
459
+ const res = await fetch(`${apiBaseUrl}/v2/makers/create`, {
460
+ method: "POST",
461
+ headers: { "Content-Type": "application/json" },
462
+ body: JSON.stringify({ processorName, ...payload }),
463
+ signal: controller.signal
464
+ });
465
+ if (!res.ok) {
466
+ const txt = await res.text().catch(() => "");
467
+ let detail = txt || res.statusText;
468
+ try {
469
+ const parsed = JSON.parse(txt);
470
+ if (typeof parsed.message === "string" && parsed.message.trim()) {
471
+ detail = parsed.message;
472
+ }
473
+ } catch {
474
+ }
475
+ throw new MakersCreateError(
476
+ `makers/create failed (${res.status}): ${detail}`,
477
+ res.status,
478
+ txt
479
+ );
480
+ }
481
+ const json = await res.json();
482
+ if (!json.success || !json.responseObject?.hashedOnchainId) {
483
+ throw new MakersCreateError(
484
+ json.message || "makers/create returned no hashedOnchainId",
485
+ json.statusCode ?? null,
486
+ ""
487
+ );
488
+ }
489
+ return json.responseObject.hashedOnchainId;
490
+ } finally {
491
+ clearTimeout(timeout);
492
+ }
493
+ }
494
+ function requireSarCredentialCapture(capturedMetadata) {
495
+ const capture = capturedMetadata.sarCredentialCapture;
496
+ if (!capture?.credentialBundle) {
497
+ throw new Error("Peer metadata did not include a seller credential bundle.");
498
+ }
499
+ if (!capture.offchainId) {
500
+ throw new Error("Peer metadata did not include seller credential offchainId.");
501
+ }
502
+ return capture;
503
+ }
504
+ async function uploadSellerCredentialBundle(processorName, payeeHash, bundle) {
505
+ const controller = new AbortController();
506
+ const timeout = setTimeout(() => controller.abort(), PAYEE_REGISTRATION_TIMEOUT_MS);
507
+ try {
508
+ const res = await fetch(
509
+ `${API_BASE_URL}/v2/makers/${encodeURIComponent(processorName)}/${encodeURIComponent(payeeHash)}/seller-credential`,
510
+ {
511
+ method: "POST",
512
+ headers: { "Content-Type": "application/json" },
513
+ body: JSON.stringify(bundle),
514
+ signal: controller.signal
515
+ }
516
+ );
517
+ if (!res.ok) {
518
+ const txt = await res.text().catch(() => "");
519
+ let detail = txt || res.statusText;
520
+ try {
521
+ const parsed = JSON.parse(txt);
522
+ if (typeof parsed.message === "string" && parsed.message.trim()) {
523
+ detail = parsed.message;
524
+ }
525
+ } catch {
526
+ }
527
+ throw new Error(`seller-credential upload failed (${res.status}): ${detail}`);
528
+ }
529
+ const json = await res.json();
530
+ if (!json.success || !json.responseObject) {
531
+ throw new Error(json.message || "seller-credential upload returned no status.");
532
+ }
533
+ return json;
534
+ } finally {
535
+ clearTimeout(timeout);
536
+ }
537
+ }
538
+ async function completePeerExtensionRegistration(input) {
539
+ const processorName = resolveRegistrationProcessor(input.platform);
540
+ if (input.capturedMetadata.platform !== processorName) {
541
+ throw new Error(
542
+ `Peer metadata is for ${input.capturedMetadata.platform}, not ${processorName}.`
543
+ );
544
+ }
545
+ const sarCredentialCapture = requireSarCredentialCapture(input.capturedMetadata);
546
+ const bundle = sarCredentialCapture.credentialBundle;
547
+ if (bundle.platform.toLowerCase() !== processorName) {
548
+ throw new Error("Seller credential bundle platform does not match registration platform.");
549
+ }
550
+ const validation = input.platform.validate(input.identifier);
551
+ if (!validation.valid) {
552
+ throw new Error(validation.error || "Invalid identifier");
553
+ }
554
+ const payload = buildDepositData(processorName, sarCredentialCapture.offchainId);
555
+ if (payload.offchainId !== validation.normalized) {
556
+ throw new Error("Seller credential offchainId does not match registration identifier.");
557
+ }
558
+ const hashedOnchainId = processorName === "wise" ? bundle.payeeIdHash : await postMakerCreate(processorName, payload);
559
+ if (hashedOnchainId.toLowerCase() !== bundle.payeeIdHash.toLowerCase()) {
560
+ throw new Error("Seller credential payee hash does not match registered payee details.");
561
+ }
562
+ const sellerCredentialResponse = await uploadSellerCredentialBundle(
563
+ processorName,
564
+ hashedOnchainId,
565
+ bundle
566
+ );
567
+ return {
568
+ processorName,
569
+ offchainId: payload.offchainId,
570
+ hashedOnchainId,
571
+ sellerCredentialResponse,
572
+ sellerCredentialStatus: sellerCredentialResponse.responseObject
573
+ };
574
+ }
341
575
  var EXTENSION_REGISTRATION_ERROR_PATTERNS = ["creating the maker", "create the maker"];
342
576
  function isPeerExtensionRegistrationError(platform, message, statusCode) {
343
577
  const info = getPeerExtensionRegistrationInfo(platform);
@@ -349,401 +583,26 @@ function isPeerExtensionRegistrationError(platform, message, statusCode) {
349
583
  return EXTENSION_REGISTRATION_ERROR_PATTERNS.some((p) => lower.includes(p));
350
584
  }
351
585
 
352
- // src/errors.ts
353
- var MakersCreateError = class extends Error {
354
- status;
355
- body;
356
- constructor(message, status, body) {
357
- super(message);
358
- this.name = "MakersCreateError";
359
- this.status = status;
360
- this.body = body;
361
- }
362
- };
363
- var OfframpError = class extends Error {
364
- code;
365
- step;
366
- cause;
367
- txHash;
368
- depositId;
369
- constructor(message, code, step, cause, details) {
370
- super(message);
371
- this.name = "OfframpError";
372
- this.code = code;
373
- this.step = step;
374
- this.cause = cause;
375
- this.txHash = details?.txHash;
376
- this.depositId = details?.depositId;
377
- }
378
- };
379
- function isUserCancellation(error) {
380
- if (!(error instanceof Error)) return false;
381
- const msg = error.message.toLowerCase();
382
- return msg.includes("user rejected") || msg.includes("user denied") || msg.includes("user cancelled") || msg.includes("rejected the request") || msg.includes("action_rejected");
383
- }
384
-
385
586
  // src/otc.ts
386
587
  import { createPublicClient, http, isAddress, zeroAddress } from "viem";
387
588
  import { base } from "viem/chains";
388
-
389
- // ../../node_modules/@zkp2p/contracts-v2/abis/base/WhitelistPreIntentHook.json
390
- var WhitelistPreIntentHook_default = [
391
- {
392
- inputs: [
393
- {
394
- internalType: "address",
395
- name: "_orchestratorRegistry",
396
- type: "address"
397
- }
398
- ],
399
- stateMutability: "nonpayable",
400
- type: "constructor"
401
- },
402
- {
403
- inputs: [],
404
- name: "EmptyArray",
405
- type: "error"
406
- },
407
- {
408
- inputs: [
409
- {
410
- internalType: "address",
411
- name: "taker",
412
- type: "address"
413
- },
414
- {
415
- internalType: "address",
416
- name: "escrow",
417
- type: "address"
418
- },
419
- {
420
- internalType: "uint256",
421
- name: "depositId",
422
- type: "uint256"
423
- }
424
- ],
425
- name: "TakerNotInWhitelist",
426
- type: "error"
427
- },
428
- {
429
- inputs: [
430
- {
431
- internalType: "address",
432
- name: "taker",
433
- type: "address"
434
- },
435
- {
436
- internalType: "address",
437
- name: "escrow",
438
- type: "address"
439
- },
440
- {
441
- internalType: "uint256",
442
- name: "depositId",
443
- type: "uint256"
444
- }
445
- ],
446
- name: "TakerNotWhitelisted",
447
- type: "error"
448
- },
449
- {
450
- inputs: [
451
- {
452
- internalType: "address",
453
- name: "caller",
454
- type: "address"
455
- },
456
- {
457
- internalType: "address",
458
- name: "owner",
459
- type: "address"
460
- },
461
- {
462
- internalType: "address",
463
- name: "delegate",
464
- type: "address"
465
- }
466
- ],
467
- name: "UnauthorizedCallerOrDelegate",
468
- type: "error"
469
- },
470
- {
471
- inputs: [
472
- {
473
- internalType: "address",
474
- name: "caller",
475
- type: "address"
476
- }
477
- ],
478
- name: "UnauthorizedOrchestratorCaller",
479
- type: "error"
480
- },
481
- {
482
- inputs: [],
483
- name: "ZeroAddress",
484
- type: "error"
485
- },
486
- {
487
- anonymous: false,
488
- inputs: [
489
- {
490
- indexed: true,
491
- internalType: "address",
492
- name: "escrow",
493
- type: "address"
494
- },
495
- {
496
- indexed: true,
497
- internalType: "uint256",
498
- name: "depositId",
499
- type: "uint256"
500
- },
501
- {
502
- indexed: true,
503
- internalType: "address",
504
- name: "taker",
505
- type: "address"
506
- }
507
- ],
508
- name: "TakerRemovedFromWhitelist",
509
- type: "event"
510
- },
511
- {
512
- anonymous: false,
513
- inputs: [
514
- {
515
- indexed: true,
516
- internalType: "address",
517
- name: "escrow",
518
- type: "address"
519
- },
520
- {
521
- indexed: true,
522
- internalType: "uint256",
523
- name: "depositId",
524
- type: "uint256"
525
- },
526
- {
527
- indexed: true,
528
- internalType: "address",
529
- name: "taker",
530
- type: "address"
531
- }
532
- ],
533
- name: "TakerWhitelisted",
534
- type: "event"
535
- },
536
- {
537
- inputs: [
538
- {
539
- internalType: "address",
540
- name: "_escrow",
541
- type: "address"
542
- },
543
- {
544
- internalType: "uint256",
545
- name: "_depositId",
546
- type: "uint256"
547
- },
548
- {
549
- internalType: "address[]",
550
- name: "_takers",
551
- type: "address[]"
552
- }
553
- ],
554
- name: "addToWhitelist",
555
- outputs: [],
556
- stateMutability: "nonpayable",
557
- type: "function"
558
- },
559
- {
560
- inputs: [
561
- {
562
- internalType: "address",
563
- name: "_escrow",
564
- type: "address"
565
- },
566
- {
567
- internalType: "uint256",
568
- name: "_depositId",
569
- type: "uint256"
570
- },
571
- {
572
- internalType: "address",
573
- name: "_taker",
574
- type: "address"
575
- }
576
- ],
577
- name: "isWhitelisted",
578
- outputs: [
579
- {
580
- internalType: "bool",
581
- name: "",
582
- type: "bool"
583
- }
584
- ],
585
- stateMutability: "view",
586
- type: "function"
587
- },
588
- {
589
- inputs: [],
590
- name: "orchestratorRegistry",
591
- outputs: [
592
- {
593
- internalType: "contract IOrchestratorRegistry",
594
- name: "",
595
- type: "address"
596
- }
597
- ],
598
- stateMutability: "view",
599
- type: "function"
600
- },
601
- {
602
- inputs: [
603
- {
604
- internalType: "address",
605
- name: "_escrow",
606
- type: "address"
607
- },
608
- {
609
- internalType: "uint256",
610
- name: "_depositId",
611
- type: "uint256"
612
- },
613
- {
614
- internalType: "address[]",
615
- name: "_takers",
616
- type: "address[]"
617
- }
618
- ],
619
- name: "removeFromWhitelist",
620
- outputs: [],
621
- stateMutability: "nonpayable",
622
- type: "function"
623
- },
624
- {
625
- inputs: [
626
- {
627
- components: [
628
- {
629
- internalType: "address",
630
- name: "taker",
631
- type: "address"
632
- },
633
- {
634
- internalType: "address",
635
- name: "escrow",
636
- type: "address"
637
- },
638
- {
639
- internalType: "uint256",
640
- name: "depositId",
641
- type: "uint256"
642
- },
643
- {
644
- internalType: "uint256",
645
- name: "amount",
646
- type: "uint256"
647
- },
648
- {
649
- internalType: "address",
650
- name: "to",
651
- type: "address"
652
- },
653
- {
654
- internalType: "bytes32",
655
- name: "paymentMethod",
656
- type: "bytes32"
657
- },
658
- {
659
- internalType: "bytes32",
660
- name: "fiatCurrency",
661
- type: "bytes32"
662
- },
663
- {
664
- internalType: "uint256",
665
- name: "conversionRate",
666
- type: "uint256"
667
- },
668
- {
669
- components: [
670
- {
671
- internalType: "address",
672
- name: "recipient",
673
- type: "address"
674
- },
675
- {
676
- internalType: "uint256",
677
- name: "fee",
678
- type: "uint256"
679
- }
680
- ],
681
- internalType: "struct IReferralFee.ReferralFee[]",
682
- name: "referralFees",
683
- type: "tuple[]"
684
- },
685
- {
686
- internalType: "bytes",
687
- name: "preIntentHookData",
688
- type: "bytes"
689
- }
690
- ],
691
- internalType: "struct IPreIntentHook.PreIntentContext",
692
- name: "_ctx",
693
- type: "tuple"
694
- }
695
- ],
696
- name: "validateSignalIntent",
697
- outputs: [],
698
- stateMutability: "view",
699
- type: "function"
700
- },
701
- {
702
- inputs: [
703
- {
704
- internalType: "address",
705
- name: "",
706
- type: "address"
707
- },
708
- {
709
- internalType: "uint256",
710
- name: "",
711
- type: "uint256"
712
- },
713
- {
714
- internalType: "address",
715
- name: "",
716
- type: "address"
717
- }
718
- ],
719
- name: "whitelist",
720
- outputs: [
721
- {
722
- internalType: "bool",
723
- name: "",
724
- type: "bool"
725
- }
726
- ],
727
- stateMutability: "view",
728
- type: "function"
729
- }
730
- ];
589
+ import WhitelistPreIntentHookABI from "@zkp2p/contracts-v2/abis/base/WhitelistPreIntentHook.json";
731
590
 
732
591
  // src/sdk-client.ts
733
592
  import { OfframpClient } from "@zkp2p/sdk";
734
- function createSdkClient(walletClient) {
593
+ function createSdkClient(walletClient, apiBaseUrl = API_BASE_URL) {
735
594
  return new OfframpClient({
736
595
  walletClient,
737
596
  chainId: BASE_CHAIN_ID,
738
597
  runtimeEnv: RUNTIME_ENV,
739
598
  rpcUrl: BASE_RPC_URL,
740
- baseApiUrl: API_BASE_URL
599
+ baseApiUrl: apiBaseUrl
741
600
  });
742
601
  }
743
602
 
744
603
  // src/otc.ts
745
604
  var WHITELIST_HOOK_ADDRESS = "0xda023Ea0d789A41BcF5866F7B6BBd2CaDF9b79B8";
746
- var OTC_BASE_URL = "https://usdctofiat.xyz";
605
+ var OTC_BASE_URL = "https://otc.usdctofiat.xyz";
747
606
  async function enableOtc(walletClient, depositId, takerAddress, escrowAddress) {
748
607
  if (!isAddress(takerAddress)) {
749
608
  throw new OfframpError("Invalid taker address", "VALIDATION");
@@ -815,7 +674,7 @@ async function disableOtc(walletClient, depositId, escrowAddress) {
815
674
  }
816
675
  function getOtcLink(depositId, escrowAddress) {
817
676
  const escrow = escrowAddress || ESCROW_ADDRESS;
818
- return `${OTC_BASE_URL}/deposit/${escrow}/${depositId}`;
677
+ return `${OTC_BASE_URL}/d/${escrow}/${depositId}`;
819
678
  }
820
679
  async function writeContract(walletClient, functionName, args) {
821
680
  const wc = walletClient;
@@ -824,7 +683,7 @@ async function writeContract(walletClient, functionName, args) {
824
683
  }
825
684
  await wc.writeContract({
826
685
  address: WHITELIST_HOOK_ADDRESS,
827
- abi: WhitelistPreIntentHook_default,
686
+ abi: WhitelistPreIntentHookABI,
828
687
  functionName,
829
688
  args
830
689
  });
@@ -833,7 +692,7 @@ async function readIsWhitelisted(escrow, depositId, taker) {
833
692
  const publicClient = createPublicClient({ chain: base, transport: http(BASE_RPC_URL) });
834
693
  return publicClient.readContract({
835
694
  address: WHITELIST_HOOK_ADDRESS,
836
- abi: WhitelistPreIntentHook_default,
695
+ abi: WhitelistPreIntentHookABI,
837
696
  functionName: "isWhitelisted",
838
697
  args: [escrow, depositId, taker]
839
698
  });
@@ -1161,41 +1020,24 @@ function extractDepositIdFromLogs(logs) {
1161
1020
  }
1162
1021
  return null;
1163
1022
  }
1164
- async function registerPayeeDetails(processorName, payload) {
1023
+ async function validatePayeeDetails(processorName, offchainId, apiBaseUrl = API_BASE_URL) {
1165
1024
  const controller = new AbortController();
1166
1025
  const timeout = setTimeout(() => controller.abort(), PAYEE_REGISTRATION_TIMEOUT_MS);
1167
1026
  try {
1168
- const res = await fetch(`${API_BASE_URL}/v2/makers/create`, {
1027
+ const res = await fetch(`${apiBaseUrl}/v2/makers/validate`, {
1169
1028
  method: "POST",
1170
1029
  headers: { "Content-Type": "application/json" },
1171
- body: JSON.stringify({ processorName, ...payload }),
1030
+ body: JSON.stringify({ processorName, offchainId }),
1172
1031
  signal: controller.signal
1173
1032
  });
1174
- if (!res.ok) {
1175
- const txt = await res.text().catch(() => "");
1176
- let detail = txt || res.statusText;
1177
- try {
1178
- const parsed = JSON.parse(txt);
1179
- if (typeof parsed.message === "string" && parsed.message.trim()) {
1180
- detail = parsed.message;
1181
- }
1182
- } catch {
1183
- }
1184
- throw new MakersCreateError(
1185
- `makers/create failed (${res.status}): ${detail}`,
1186
- res.status,
1187
- txt
1188
- );
1189
- }
1033
+ if (!res.ok) return "unknown";
1190
1034
  const json = await res.json();
1191
- if (!json.success || !json.responseObject?.hashedOnchainId) {
1192
- throw new MakersCreateError(
1193
- json.message || "makers/create returned no hashedOnchainId",
1194
- json.statusCode ?? null,
1195
- ""
1196
- );
1035
+ if (typeof json.responseObject === "boolean") {
1036
+ return json.responseObject ? "valid" : "invalid";
1197
1037
  }
1198
- return json.responseObject.hashedOnchainId;
1038
+ return "unknown";
1039
+ } catch {
1040
+ return "unknown";
1199
1041
  } finally {
1200
1042
  clearTimeout(timeout);
1201
1043
  }
@@ -1260,10 +1102,11 @@ async function findUndelegatedDeposit(walletAddress) {
1260
1102
  return null;
1261
1103
  }
1262
1104
  }
1263
- async function offramp(walletClient, params, onProgress, telemetryContext) {
1105
+ async function offramp(walletClient, params, onProgress, telemetryContext, apiBaseUrl) {
1264
1106
  const { amount, platform, currency, identifier } = params;
1265
1107
  const platformId = platform.id;
1266
1108
  const currencyCode = currency.code;
1109
+ const resolvedApiBaseUrl = resolveApiBaseUrl(apiBaseUrl);
1267
1110
  const telemetry = telemetryContext ?? createTelemetryContext({
1268
1111
  sdkVersion: SDK_VERSION,
1269
1112
  telemetry: true,
@@ -1308,7 +1151,7 @@ async function offramp(walletClient, params, onProgress, telemetryContext) {
1308
1151
  const existing = await findUndelegatedDeposit(walletAddress);
1309
1152
  if (existing) {
1310
1153
  emitProgress({ step: "resuming", depositId: existing.depositId });
1311
- const client2 = createSdkClient(walletClient);
1154
+ const client2 = createSdkClient(walletClient, resolvedApiBaseUrl);
1312
1155
  const txOverrides2 = { referrer: [REFERRER] };
1313
1156
  try {
1314
1157
  const result2 = await client2.setRateManager({
@@ -1344,7 +1187,7 @@ async function offramp(walletClient, params, onProgress, telemetryContext) {
1344
1187
  const truncatedAmount = amt.toFixed(6).replace(/\.?0+$/, "");
1345
1188
  const amountUnits = usdcToUnits(truncatedAmount);
1346
1189
  const minUnits = usdcToUnits(String(MIN_ORDER_USDC));
1347
- const maxUnits = usdcToUnits(String(Math.min(amt, 2500)));
1190
+ const maxUnits = usdcToUnits(String(Math.min(amt, MAX_ORDER_USDC)));
1348
1191
  try {
1349
1192
  const publicClient = createPublicClient3({ chain: base3, transport: http3(BASE_RPC_URL) });
1350
1193
  const balance = await publicClient.readContract({
@@ -1371,7 +1214,22 @@ async function offramp(walletClient, params, onProgress, telemetryContext) {
1371
1214
  } catch (err) {
1372
1215
  if (err instanceof OfframpError) throw err;
1373
1216
  }
1374
- const client = createSdkClient(walletClient);
1217
+ const canonicalName = platformId.startsWith("zelle") ? "zelle" : platformId;
1218
+ if (!getPeerExtensionRegistrationInfo(platformId)) {
1219
+ const outcome = await validatePayeeDetails(
1220
+ canonicalName,
1221
+ normalizedIdentifier,
1222
+ resolvedApiBaseUrl
1223
+ );
1224
+ if (outcome === "invalid") {
1225
+ throw new OfframpError(
1226
+ `Couldn't verify that ${platform.name} ${platform.identifier.label.toLowerCase()}. Double-check it and try again.`,
1227
+ "VALIDATION",
1228
+ "registering"
1229
+ );
1230
+ }
1231
+ }
1232
+ const client = createSdkClient(walletClient, resolvedApiBaseUrl);
1375
1233
  const txOverrides = { referrer: [REFERRER] };
1376
1234
  emitProgress({ step: "approving" });
1377
1235
  try {
@@ -1395,16 +1253,15 @@ async function offramp(walletClient, params, onProgress, telemetryContext) {
1395
1253
  }
1396
1254
  emitProgress({ step: "registering" });
1397
1255
  let hashedOnchainId;
1398
- const canonicalName = platformId.startsWith("zelle") ? "zelle" : platformId;
1399
1256
  try {
1400
1257
  const payeePayload = buildDepositData(canonicalName, normalizedIdentifier);
1401
- hashedOnchainId = await registerPayeeDetails(canonicalName, payeePayload);
1258
+ hashedOnchainId = await postMakerCreate(canonicalName, payeePayload, resolvedApiBaseUrl);
1402
1259
  } catch (err) {
1403
1260
  const status = err instanceof MakersCreateError ? err.status : null;
1404
1261
  const message = err instanceof Error ? err.message : String(err);
1405
1262
  if (isPeerExtensionRegistrationError(canonicalName, message, status)) {
1406
1263
  throw new OfframpError(
1407
- `${platform.name} maker is not yet registered in the Peer extension. Verify this handle in the extension, then retry.`,
1264
+ `${platform.name} maker is not yet registered in the Peer extension. Register this handle with Peer, then retry.`,
1408
1265
  "EXTENSION_REGISTRATION_REQUIRED",
1409
1266
  "registering",
1410
1267
  err
@@ -1596,6 +1453,14 @@ var CURRENCIES = buildCurrencies();
1596
1453
  // src/offramp.ts
1597
1454
  var IDEMPOTENCY_TTL_MS = 10 * 60 * 1e3;
1598
1455
  var IDEMPOTENCY_STORAGE_PREFIX = "offramp:idempotency:";
1456
+ var warnedIdempotencyUnavailable = false;
1457
+ function warnIdempotencyUnavailableOnce() {
1458
+ if (warnedIdempotencyUnavailable) return;
1459
+ warnedIdempotencyUnavailable = true;
1460
+ console.warn(
1461
+ "[offramp] idempotencyKey was provided but sessionStorage is unavailable (Node/worker runtime). The idempotency cache is browser-only and no-ops here, so retries can create duplicate deposits. For server-side dedup, call deposits(walletAddress) and reuse an existing open deposit before creating one."
1462
+ );
1463
+ }
1599
1464
  function getWalletAddress(walletClient) {
1600
1465
  const address = walletClient.account?.address;
1601
1466
  if (!address) {
@@ -1666,8 +1531,10 @@ function buildCurrencyGroups() {
1666
1531
  var Offramp = class {
1667
1532
  walletClient;
1668
1533
  telemetry;
1534
+ apiBaseUrl;
1669
1535
  constructor(options) {
1670
1536
  this.walletClient = options.walletClient;
1537
+ this.apiBaseUrl = options.apiBaseUrl;
1671
1538
  this.telemetry = createTelemetryContext({
1672
1539
  sdkVersion: SDK_VERSION,
1673
1540
  telemetry: options.telemetry,
@@ -1690,6 +1557,9 @@ var Offramp = class {
1690
1557
  referralId: flowTelemetry.referralId
1691
1558
  };
1692
1559
  const sanitizedKey = sanitizeAttributionId(params.idempotencyKey);
1560
+ if (sanitizedKey && typeof sessionStorage === "undefined") {
1561
+ warnIdempotencyUnavailableOnce();
1562
+ }
1693
1563
  const walletAddress = sanitizedKey ? getWalletAddress(this.walletClient) : null;
1694
1564
  const storageKey = walletAddress && sanitizedKey ? getIdempotencyStorageKey(walletAddress, sanitizedKey) : null;
1695
1565
  if (storageKey) {
@@ -1700,7 +1570,8 @@ var Offramp = class {
1700
1570
  this.walletClient,
1701
1571
  sanitizedParams,
1702
1572
  onProgress,
1703
- flowTelemetry
1573
+ flowTelemetry,
1574
+ this.apiBaseUrl
1704
1575
  );
1705
1576
  if (storageKey) {
1706
1577
  writeIdempotencyResult(storageKey, result);
@@ -1749,21 +1620,94 @@ function createOfframp(options) {
1749
1620
  // src/extension.ts
1750
1621
  import {
1751
1622
  PEER_EXTENSION_CHROME_URL,
1752
- peerExtensionSdk,
1753
- createPeerExtensionSdk,
1754
- isPeerExtensionAvailable,
1755
- openPeerExtensionInstallPage,
1756
- getPeerExtensionState
1623
+ createPeerExtensionSdk as createLegacyPeerExtensionSdk
1757
1624
  } from "@zkp2p/sdk";
1625
+ var resolveWindow = (options) => {
1626
+ if (options?.window) {
1627
+ return options.window;
1628
+ }
1629
+ if (typeof window === "undefined") {
1630
+ return void 0;
1631
+ }
1632
+ return window;
1633
+ };
1634
+ var requirePeer = (options) => {
1635
+ const resolvedWindow = resolveWindow(options);
1636
+ if (!resolvedWindow) {
1637
+ throw new Error("Peer extension SDK requires a browser window.");
1638
+ }
1639
+ if (!resolvedWindow.peer) {
1640
+ throw new Error("Peer extension not available. Install or enable the Peer extension.");
1641
+ }
1642
+ return resolvedWindow.peer;
1643
+ };
1644
+ var isPeerExtensionAvailable = (options) => {
1645
+ const resolvedWindow = resolveWindow(options);
1646
+ return Boolean(resolvedWindow?.peer);
1647
+ };
1648
+ var hasHeadlessMetadataBridge = (peer) => typeof peer.authenticate === "function" && typeof peer.onMetadataMessage === "function";
1649
+ var requirePeerMetadataBridge = (options) => {
1650
+ const peer = requirePeer(options);
1651
+ if (!hasHeadlessMetadataBridge(peer)) {
1652
+ throw new Error("Peer extension metadata bridge unavailable. Install or update Peer.");
1653
+ }
1654
+ return peer;
1655
+ };
1656
+ var isPeerExtensionMetadataBridgeAvailable = (options) => {
1657
+ const resolvedWindow = resolveWindow(options);
1658
+ return Boolean(resolvedWindow?.peer && hasHeadlessMetadataBridge(resolvedWindow.peer));
1659
+ };
1660
+ var openPeerExtensionInstallPage = (options) => {
1661
+ const resolvedWindow = resolveWindow(options);
1662
+ if (!resolvedWindow) {
1663
+ throw new Error("Peer extension SDK requires a browser window.");
1664
+ }
1665
+ resolvedWindow.open(PEER_EXTENSION_CHROME_URL, "_blank", "noopener,noreferrer");
1666
+ };
1667
+ var getPeerExtensionState = async (options) => {
1668
+ if (!isPeerExtensionAvailable(options)) {
1669
+ return "needs_install";
1670
+ }
1671
+ try {
1672
+ const status = await requirePeer(options).checkConnectionStatus();
1673
+ return status === "connected" ? "ready" : "needs_connection";
1674
+ } catch {
1675
+ return "needs_connection";
1676
+ }
1677
+ };
1678
+ var createPeerExtensionSdk = (options = {}) => {
1679
+ const legacySdk = createLegacyPeerExtensionSdk(
1680
+ options
1681
+ );
1682
+ return {
1683
+ isAvailable: () => isPeerExtensionAvailable(options),
1684
+ requestConnection: () => requirePeer(options).requestConnection(),
1685
+ checkConnectionStatus: () => requirePeer(options).checkConnectionStatus(),
1686
+ getVersion: () => requirePeer(options).getVersion(),
1687
+ authenticate: (params) => requirePeerMetadataBridge(options).authenticate(params),
1688
+ onMetadataMessage: (callback) => requirePeerMetadataBridge(options).onMetadataMessage(callback),
1689
+ openSidebar: (route) => legacySdk.openSidebar(route),
1690
+ onramp: (params, callback) => legacySdk.onramp(params, callback),
1691
+ getOnrampTransaction: (intentHash) => legacySdk.getOnrampTransaction(intentHash),
1692
+ openInstallPage: () => openPeerExtensionInstallPage(options),
1693
+ getState: () => getPeerExtensionState(options)
1694
+ };
1695
+ };
1696
+ var peerExtensionSdk = createPeerExtensionSdk();
1758
1697
 
1759
1698
  export {
1760
1699
  ESCROW_ADDRESS,
1700
+ MakersCreateError,
1701
+ OfframpError,
1761
1702
  normalizePaypalMeUsername,
1703
+ isValidIBAN,
1762
1704
  PLATFORMS,
1705
+ PLATFORM_LIMITS,
1706
+ getPlatformLimits,
1763
1707
  getPeerExtensionRegistrationInfo,
1708
+ getPeerExtensionRegistrationAuthParams,
1709
+ completePeerExtensionRegistration,
1764
1710
  isPeerExtensionRegistrationError,
1765
- MakersCreateError,
1766
- OfframpError,
1767
1711
  enableOtc,
1768
1712
  disableOtc,
1769
1713
  getOtcLink,
@@ -1780,10 +1724,11 @@ export {
1780
1724
  Offramp,
1781
1725
  createOfframp,
1782
1726
  PEER_EXTENSION_CHROME_URL,
1783
- peerExtensionSdk,
1784
- createPeerExtensionSdk,
1785
1727
  isPeerExtensionAvailable,
1728
+ isPeerExtensionMetadataBridgeAvailable,
1786
1729
  openPeerExtensionInstallPage,
1787
- getPeerExtensionState
1730
+ getPeerExtensionState,
1731
+ createPeerExtensionSdk,
1732
+ peerExtensionSdk
1788
1733
  };
1789
- //# sourceMappingURL=chunk-F7DVUBMT.js.map
1734
+ //# sourceMappingURL=chunk-XQ7HOLLB.js.map