@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.
package/dist/index.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -28,9 +38,11 @@ __export(index_exports, {
28
38
  OfframpError: () => OfframpError,
29
39
  PEER_EXTENSION_CHROME_URL: () => import_sdk7.PEER_EXTENSION_CHROME_URL,
30
40
  PLATFORMS: () => PLATFORMS,
41
+ PLATFORM_LIMITS: () => PLATFORM_LIMITS,
31
42
  close: () => close,
43
+ completePeerExtensionRegistration: () => completePeerExtensionRegistration,
32
44
  createOfframp: () => createOfframp,
33
- createPeerExtensionSdk: () => import_sdk7.createPeerExtensionSdk,
45
+ createPeerExtensionSdk: () => createPeerExtensionSdk,
34
46
  createTelemetryContext: () => createTelemetryContext,
35
47
  delegate: () => delegate,
36
48
  deposits: () => deposits,
@@ -38,14 +50,18 @@ __export(index_exports, {
38
50
  emitEvent: () => emitEvent,
39
51
  enableOtc: () => enableOtc,
40
52
  getOtcLink: () => getOtcLink,
53
+ getPeerExtensionRegistrationAuthParams: () => getPeerExtensionRegistrationAuthParams,
41
54
  getPeerExtensionRegistrationInfo: () => getPeerExtensionRegistrationInfo,
42
- getPeerExtensionState: () => import_sdk7.getPeerExtensionState,
43
- isPeerExtensionAvailable: () => import_sdk7.isPeerExtensionAvailable,
55
+ getPeerExtensionState: () => getPeerExtensionState,
56
+ getPlatformLimits: () => getPlatformLimits,
57
+ isPeerExtensionAvailable: () => isPeerExtensionAvailable,
58
+ isPeerExtensionMetadataBridgeAvailable: () => isPeerExtensionMetadataBridgeAvailable,
44
59
  isPeerExtensionRegistrationError: () => isPeerExtensionRegistrationError,
60
+ isValidIBAN: () => isValidIBAN,
45
61
  normalizePaypalMeUsername: () => normalizePaypalMeUsername,
46
62
  offramp: () => offramp,
47
- openPeerExtensionInstallPage: () => import_sdk7.openPeerExtensionInstallPage,
48
- peerExtensionSdk: () => import_sdk7.peerExtensionSdk,
63
+ openPeerExtensionInstallPage: () => openPeerExtensionInstallPage,
64
+ peerExtensionSdk: () => peerExtensionSdk,
49
65
  sanitizeAttributionId: () => sanitizeAttributionId,
50
66
  sendTelemetryEvent: () => sendTelemetryEvent,
51
67
  undelegate: () => undelegate
@@ -59,11 +75,15 @@ var import_sdk5 = require("@zkp2p/sdk");
59
75
 
60
76
  // src/config.ts
61
77
  var import_sdk = require("@zkp2p/sdk");
62
- var SDK_VERSION = "3.0.2";
78
+ var SDK_VERSION = "3.1.0";
63
79
  var BASE_CHAIN_ID = 8453;
64
80
  var RUNTIME_ENV = "production";
65
81
  var API_BASE_URL = "https://api.zkp2p.xyz";
66
82
  var BASE_RPC_URL = "https://mainnet.base.org";
83
+ function resolveApiBaseUrl(override) {
84
+ const trimmed = override?.trim();
85
+ return (trimmed && trimmed.length > 0 ? trimmed : API_BASE_URL).replace(/\/$/, "");
86
+ }
67
87
  var contracts = (0, import_sdk.getContracts)(BASE_CHAIN_ID, RUNTIME_ENV);
68
88
  var addresses = contracts.addresses;
69
89
  var addrs = addresses;
@@ -80,6 +100,7 @@ var DEFAULT_INTENT_GUARDIAN_ADDRESS = "0xe29a5BD4D0CEbA6C125A0361E7D20ab4eA275C2
80
100
  var REFERRER = "galleonlabs";
81
101
  var MIN_DEPOSIT_USDC = 1;
82
102
  var MIN_ORDER_USDC = 1;
103
+ var MAX_ORDER_USDC = 2500;
83
104
  var INDEXER_MAX_ATTEMPTS = 12;
84
105
  var INDEXER_INITIAL_DELAY_MS = 1e3;
85
106
  var INDEXER_MAX_DELAY_MS = 1e4;
@@ -88,6 +109,41 @@ var PAYEE_REGISTRATION_TIMEOUT_MS = 8e3;
88
109
  // src/platforms.ts
89
110
  var import_sdk2 = require("@zkp2p/sdk");
90
111
  var import_zod = require("zod");
112
+
113
+ // src/errors.ts
114
+ var MakersCreateError = class extends Error {
115
+ status;
116
+ body;
117
+ constructor(message, status, body) {
118
+ super(message);
119
+ this.name = "MakersCreateError";
120
+ this.status = status;
121
+ this.body = body;
122
+ }
123
+ };
124
+ var OfframpError = class extends Error {
125
+ code;
126
+ step;
127
+ cause;
128
+ txHash;
129
+ depositId;
130
+ constructor(message, code, step, cause, details) {
131
+ super(message);
132
+ this.name = "OfframpError";
133
+ this.code = code;
134
+ this.step = step;
135
+ this.cause = cause;
136
+ this.txHash = details?.txHash;
137
+ this.depositId = details?.depositId;
138
+ }
139
+ };
140
+ function isUserCancellation(error) {
141
+ if (!(error instanceof Error)) return false;
142
+ const msg = error.message.toLowerCase();
143
+ return msg.includes("user rejected") || msg.includes("user denied") || msg.includes("user cancelled") || msg.includes("rejected the request") || msg.includes("action_rejected");
144
+ }
145
+
146
+ // src/platforms.ts
91
147
  var PAYMENT_CATALOG = (0, import_sdk2.getPaymentMethodsCatalog)(BASE_CHAIN_ID, RUNTIME_ENV);
92
148
  var ZELLE_HASH_LOOKUP_NAMES = ["zelle", "zelle-bofa", "zelle-chase", "zelle-citi"];
93
149
  var FALLBACK_CURRENCIES = {
@@ -156,7 +212,8 @@ var FALLBACK_CURRENCIES = {
156
212
  zelle: ["USD"],
157
213
  paypal: ["USD", "EUR", "GBP", "SGD", "NZD", "AUD", "CAD"],
158
214
  monzo: ["GBP"],
159
- n26: ["EUR"]
215
+ n26: ["EUR"],
216
+ luxon: ["USD", "EUR", "GBP"]
160
217
  };
161
218
  function gatherCatalogHashes(platform) {
162
219
  if (platform === "zelle") {
@@ -180,13 +237,39 @@ function resolveSupportedCurrencies(platform) {
180
237
  function normalizePaypalMeUsername(value) {
181
238
  const trimmed = value.trim();
182
239
  if (!trimmed) return "";
183
- const withoutProtocol = trimmed.replace(/^https?:\/\//i, "").replace(/^www\./i, "");
184
- if (/^paypal\.me(?:[?#].*)?$/i.test(withoutProtocol)) return "";
185
- const withoutDomain = withoutProtocol.replace(/^paypal\.me\//i, "");
186
- const [pathWithoutQuery] = withoutDomain.split(/[?#]/, 1);
240
+ const withoutProtocol = trimmed.replace(/^https?:\/\//i, "");
241
+ const [pathWithoutQuery] = withoutProtocol.split(/[?#]/, 1);
187
242
  const sanitizedPath = pathWithoutQuery.replace(/^\/+/, "");
188
- const [username] = sanitizedPath.split("/", 1);
189
- return username.replace(/^@+/, "").trim().toLowerCase();
243
+ const [host = "", ...pathSegments] = sanitizedPath.split("/");
244
+ const normalizedHost = host.toLowerCase().replace(/^www\./i, "");
245
+ if (normalizedHost === "paypal.me") {
246
+ const [username = ""] = pathSegments;
247
+ return username.replace(/^@+/, "").trim().toLowerCase();
248
+ }
249
+ if (normalizedHost === "paypal.com") {
250
+ const [paypalMePath = "", username = ""] = pathSegments;
251
+ if (paypalMePath.toLowerCase() !== "paypalme") return "";
252
+ return username.replace(/^@+/, "").trim().toLowerCase();
253
+ }
254
+ if (/^https?:\/\//i.test(trimmed) || trimmed.includes("/")) {
255
+ return trimmed;
256
+ }
257
+ return trimmed.replace(/^@+/, "").toLowerCase();
258
+ }
259
+ function isValidIBAN(iban) {
260
+ const upper = iban.toUpperCase();
261
+ if (!/^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$/.test(upper)) return false;
262
+ const rearranged = upper.slice(4) + upper.slice(0, 4);
263
+ const numeric = rearranged.split("").map((ch) => {
264
+ const code = ch.charCodeAt(0);
265
+ return code >= 65 && code <= 90 ? (code - 55).toString() : ch;
266
+ }).join("");
267
+ let remainder = numeric;
268
+ while (remainder.length > 2) {
269
+ const block = remainder.slice(0, 9);
270
+ remainder = (parseInt(block, 10) % 97).toString() + remainder.slice(block.length);
271
+ }
272
+ return parseInt(remainder, 10) % 97 === 1;
190
273
  }
191
274
  var BLUEPRINTS = {
192
275
  venmo: {
@@ -238,9 +321,9 @@ var BLUEPRINTS = {
238
321
  transform: (v) => v.replace(/^@+/, "").trim(),
239
322
  extensionRegistration: {
240
323
  providerId: "wise",
241
- requiredPrompt: "This Wisetag is not registered yet. Verify it in the Peer extension, then refresh and retry with the same Wisetag.",
242
- ctaLabel: "Install Peer Extension",
243
- minExtensionVersion: "0.4.12"
324
+ requiredPrompt: "This Wisetag is not registered yet. Register it with PeerAuth, then retry with the same Wisetag.",
325
+ ctaLabel: "Register Wisetag",
326
+ minExtensionVersion: "0.5.0"
244
327
  }
245
328
  },
246
329
  mercadopago: {
@@ -257,7 +340,11 @@ var BLUEPRINTS = {
257
340
  identifierLabel: "Email",
258
341
  placeholder: "email",
259
342
  helperText: "Registered Zelle email",
260
- validation: import_zod.z.string().email()
343
+ // Curator's ZelleProcessor rejects any non-lowercase email outright, so
344
+ // canonicalize to lowercase here rather than letting /v2/makers/create
345
+ // reject after the maker has already approved USDC + paid gas.
346
+ validation: import_zod.z.string().email(),
347
+ transform: (v) => v.trim().toLowerCase()
261
348
  },
262
349
  paypal: {
263
350
  id: "paypal",
@@ -265,14 +352,17 @@ var BLUEPRINTS = {
265
352
  identifierLabel: "PayPal.me Username",
266
353
  placeholder: "paypal.me/your-username",
267
354
  helperText: "Your PayPal.me username, not your email",
268
- validation: import_zod.z.string().min(1, "PayPal.me username is required").regex(/^[a-z0-9._-]+$/i, "Use your PayPal.me username (letters, numbers, . _ -)"),
355
+ validation: import_zod.z.string().min(1, "PayPal.me username is required").regex(
356
+ /^[a-z0-9._-]+$/i,
357
+ "Use your PayPal.me username or paypal.me link, not a shortened URL."
358
+ ),
269
359
  transform: normalizePaypalMeUsername,
270
360
  extensionRegistration: {
271
361
  providerId: "paypal",
272
- requiredPrompt: "This PayPal username is not registered yet. Verify it in the Peer extension, then refresh and retry with the same PayPal username.",
273
- ctaLabel: "Install Peer Extension",
362
+ requiredPrompt: "This PayPal username is not registered yet. Register it with PeerAuth, then retry with the same PayPal username.",
363
+ ctaLabel: "Register PayPal Username",
274
364
  ctaSubtext: "PayPal Business accounts are not supported at this time.",
275
- minExtensionVersion: "0.4.14"
365
+ minExtensionVersion: "0.5.0"
276
366
  }
277
367
  },
278
368
  monzo: {
@@ -289,8 +379,20 @@ var BLUEPRINTS = {
289
379
  identifierLabel: "IBAN",
290
380
  placeholder: "IBAN (e.g. DE89...)",
291
381
  helperText: "Your IBAN (spaces will be removed)",
292
- validation: import_zod.z.string().min(15).max(34).regex(/^[A-Z]{2}[0-9]{2}[A-Z0-9]+$/i),
382
+ // transform runs before validation, so the schema sees a spaceless,
383
+ // uppercased IBAN. The MOD-97 refine mirrors curator's N26Processor so a
384
+ // typo'd-but-well-formed IBAN fails here, not after USDC approval + gas.
385
+ validation: import_zod.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"),
293
386
  transform: (v) => v.replace(/\s/g, "").toUpperCase()
387
+ },
388
+ luxon: {
389
+ id: "luxon",
390
+ name: "Luxon",
391
+ identifierLabel: "Email",
392
+ placeholder: "your-luxon-email@example.com",
393
+ helperText: "Email address registered with Luxon",
394
+ validation: import_zod.z.string().email(),
395
+ transform: (v) => v.trim()
294
396
  }
295
397
  };
296
398
  function buildPlatformEntry(bp) {
@@ -329,8 +431,18 @@ var PLATFORMS = {
329
431
  ZELLE: buildPlatformEntry(BLUEPRINTS.zelle),
330
432
  PAYPAL: buildPlatformEntry(BLUEPRINTS.paypal),
331
433
  MONZO: buildPlatformEntry(BLUEPRINTS.monzo),
332
- N26: buildPlatformEntry(BLUEPRINTS.n26)
434
+ N26: buildPlatformEntry(BLUEPRINTS.n26),
435
+ LUXON: buildPlatformEntry(BLUEPRINTS.luxon)
333
436
  };
437
+ var PLATFORM_LIMITS = {
438
+ venmo: { platformCapUsd: 5e3 },
439
+ n26: { platformCapUsd: 1e4 },
440
+ paypal: { minTier: "PLUS" }
441
+ };
442
+ function getPlatformLimits(platform) {
443
+ const id = platform.trim().toLowerCase();
444
+ return PLATFORM_LIMITS[id] ?? null;
445
+ }
334
446
  function getPlatformById(id) {
335
447
  return Object.values(PLATFORMS).find((p) => p.id === id) ?? null;
336
448
  }
@@ -392,6 +504,146 @@ function getPeerExtensionRegistrationInfo(platform) {
392
504
  const entry = getPlatformById(platform);
393
505
  return entry?.extensionRegistration ?? null;
394
506
  }
507
+ function getPeerExtensionRegistrationAuthParams(platform, options = {}) {
508
+ const info = getPeerExtensionRegistrationInfo(platform);
509
+ if (!info) return null;
510
+ const params = {
511
+ actionType: `transfer_${info.providerId}`,
512
+ captureMode: "sellerCredential",
513
+ platform: info.providerId
514
+ };
515
+ if (options.attestationServiceUrl) {
516
+ params.attestationServiceUrl = options.attestationServiceUrl;
517
+ }
518
+ return params;
519
+ }
520
+ function resolveRegistrationProcessor(platform) {
521
+ const info = getPeerExtensionRegistrationInfo(platform.id);
522
+ if (info?.providerId === "paypal" || info?.providerId === "wise") {
523
+ return info.providerId;
524
+ }
525
+ throw new Error(`${platform.name} does not require Peer extension registration.`);
526
+ }
527
+ async function postMakerCreate(processorName, payload, apiBaseUrl = API_BASE_URL) {
528
+ const controller = new AbortController();
529
+ const timeout = setTimeout(() => controller.abort(), PAYEE_REGISTRATION_TIMEOUT_MS);
530
+ try {
531
+ const res = await fetch(`${apiBaseUrl}/v2/makers/create`, {
532
+ method: "POST",
533
+ headers: { "Content-Type": "application/json" },
534
+ body: JSON.stringify({ processorName, ...payload }),
535
+ signal: controller.signal
536
+ });
537
+ if (!res.ok) {
538
+ const txt = await res.text().catch(() => "");
539
+ let detail = txt || res.statusText;
540
+ try {
541
+ const parsed = JSON.parse(txt);
542
+ if (typeof parsed.message === "string" && parsed.message.trim()) {
543
+ detail = parsed.message;
544
+ }
545
+ } catch {
546
+ }
547
+ throw new MakersCreateError(
548
+ `makers/create failed (${res.status}): ${detail}`,
549
+ res.status,
550
+ txt
551
+ );
552
+ }
553
+ const json = await res.json();
554
+ if (!json.success || !json.responseObject?.hashedOnchainId) {
555
+ throw new MakersCreateError(
556
+ json.message || "makers/create returned no hashedOnchainId",
557
+ json.statusCode ?? null,
558
+ ""
559
+ );
560
+ }
561
+ return json.responseObject.hashedOnchainId;
562
+ } finally {
563
+ clearTimeout(timeout);
564
+ }
565
+ }
566
+ function requireSarCredentialCapture(capturedMetadata) {
567
+ const capture = capturedMetadata.sarCredentialCapture;
568
+ if (!capture?.credentialBundle) {
569
+ throw new Error("Peer metadata did not include a seller credential bundle.");
570
+ }
571
+ if (!capture.offchainId) {
572
+ throw new Error("Peer metadata did not include seller credential offchainId.");
573
+ }
574
+ return capture;
575
+ }
576
+ async function uploadSellerCredentialBundle(processorName, payeeHash, bundle) {
577
+ const controller = new AbortController();
578
+ const timeout = setTimeout(() => controller.abort(), PAYEE_REGISTRATION_TIMEOUT_MS);
579
+ try {
580
+ const res = await fetch(
581
+ `${API_BASE_URL}/v2/makers/${encodeURIComponent(processorName)}/${encodeURIComponent(payeeHash)}/seller-credential`,
582
+ {
583
+ method: "POST",
584
+ headers: { "Content-Type": "application/json" },
585
+ body: JSON.stringify(bundle),
586
+ signal: controller.signal
587
+ }
588
+ );
589
+ if (!res.ok) {
590
+ const txt = await res.text().catch(() => "");
591
+ let detail = txt || res.statusText;
592
+ try {
593
+ const parsed = JSON.parse(txt);
594
+ if (typeof parsed.message === "string" && parsed.message.trim()) {
595
+ detail = parsed.message;
596
+ }
597
+ } catch {
598
+ }
599
+ throw new Error(`seller-credential upload failed (${res.status}): ${detail}`);
600
+ }
601
+ const json = await res.json();
602
+ if (!json.success || !json.responseObject) {
603
+ throw new Error(json.message || "seller-credential upload returned no status.");
604
+ }
605
+ return json;
606
+ } finally {
607
+ clearTimeout(timeout);
608
+ }
609
+ }
610
+ async function completePeerExtensionRegistration(input) {
611
+ const processorName = resolveRegistrationProcessor(input.platform);
612
+ if (input.capturedMetadata.platform !== processorName) {
613
+ throw new Error(
614
+ `Peer metadata is for ${input.capturedMetadata.platform}, not ${processorName}.`
615
+ );
616
+ }
617
+ const sarCredentialCapture = requireSarCredentialCapture(input.capturedMetadata);
618
+ const bundle = sarCredentialCapture.credentialBundle;
619
+ if (bundle.platform.toLowerCase() !== processorName) {
620
+ throw new Error("Seller credential bundle platform does not match registration platform.");
621
+ }
622
+ const validation = input.platform.validate(input.identifier);
623
+ if (!validation.valid) {
624
+ throw new Error(validation.error || "Invalid identifier");
625
+ }
626
+ const payload = buildDepositData(processorName, sarCredentialCapture.offchainId);
627
+ if (payload.offchainId !== validation.normalized) {
628
+ throw new Error("Seller credential offchainId does not match registration identifier.");
629
+ }
630
+ const hashedOnchainId = processorName === "wise" ? bundle.payeeIdHash : await postMakerCreate(processorName, payload);
631
+ if (hashedOnchainId.toLowerCase() !== bundle.payeeIdHash.toLowerCase()) {
632
+ throw new Error("Seller credential payee hash does not match registered payee details.");
633
+ }
634
+ const sellerCredentialResponse = await uploadSellerCredentialBundle(
635
+ processorName,
636
+ hashedOnchainId,
637
+ bundle
638
+ );
639
+ return {
640
+ processorName,
641
+ offchainId: payload.offchainId,
642
+ hashedOnchainId,
643
+ sellerCredentialResponse,
644
+ sellerCredentialStatus: sellerCredentialResponse.responseObject
645
+ };
646
+ }
395
647
  var EXTENSION_REGISTRATION_ERROR_PATTERNS = ["creating the maker", "create the maker"];
396
648
  function isPeerExtensionRegistrationError(platform, message, statusCode) {
397
649
  const info = getPeerExtensionRegistrationInfo(platform);
@@ -403,401 +655,26 @@ function isPeerExtensionRegistrationError(platform, message, statusCode) {
403
655
  return EXTENSION_REGISTRATION_ERROR_PATTERNS.some((p) => lower.includes(p));
404
656
  }
405
657
 
406
- // src/errors.ts
407
- var MakersCreateError = class extends Error {
408
- status;
409
- body;
410
- constructor(message, status, body) {
411
- super(message);
412
- this.name = "MakersCreateError";
413
- this.status = status;
414
- this.body = body;
415
- }
416
- };
417
- var OfframpError = class extends Error {
418
- code;
419
- step;
420
- cause;
421
- txHash;
422
- depositId;
423
- constructor(message, code, step, cause, details) {
424
- super(message);
425
- this.name = "OfframpError";
426
- this.code = code;
427
- this.step = step;
428
- this.cause = cause;
429
- this.txHash = details?.txHash;
430
- this.depositId = details?.depositId;
431
- }
432
- };
433
- function isUserCancellation(error) {
434
- if (!(error instanceof Error)) return false;
435
- const msg = error.message.toLowerCase();
436
- return msg.includes("user rejected") || msg.includes("user denied") || msg.includes("user cancelled") || msg.includes("rejected the request") || msg.includes("action_rejected");
437
- }
438
-
439
658
  // src/otc.ts
440
659
  var import_viem = require("viem");
441
660
  var import_chains = require("viem/chains");
442
-
443
- // ../../node_modules/@zkp2p/contracts-v2/abis/base/WhitelistPreIntentHook.json
444
- var WhitelistPreIntentHook_default = [
445
- {
446
- inputs: [
447
- {
448
- internalType: "address",
449
- name: "_orchestratorRegistry",
450
- type: "address"
451
- }
452
- ],
453
- stateMutability: "nonpayable",
454
- type: "constructor"
455
- },
456
- {
457
- inputs: [],
458
- name: "EmptyArray",
459
- type: "error"
460
- },
461
- {
462
- inputs: [
463
- {
464
- internalType: "address",
465
- name: "taker",
466
- type: "address"
467
- },
468
- {
469
- internalType: "address",
470
- name: "escrow",
471
- type: "address"
472
- },
473
- {
474
- internalType: "uint256",
475
- name: "depositId",
476
- type: "uint256"
477
- }
478
- ],
479
- name: "TakerNotInWhitelist",
480
- type: "error"
481
- },
482
- {
483
- inputs: [
484
- {
485
- internalType: "address",
486
- name: "taker",
487
- type: "address"
488
- },
489
- {
490
- internalType: "address",
491
- name: "escrow",
492
- type: "address"
493
- },
494
- {
495
- internalType: "uint256",
496
- name: "depositId",
497
- type: "uint256"
498
- }
499
- ],
500
- name: "TakerNotWhitelisted",
501
- type: "error"
502
- },
503
- {
504
- inputs: [
505
- {
506
- internalType: "address",
507
- name: "caller",
508
- type: "address"
509
- },
510
- {
511
- internalType: "address",
512
- name: "owner",
513
- type: "address"
514
- },
515
- {
516
- internalType: "address",
517
- name: "delegate",
518
- type: "address"
519
- }
520
- ],
521
- name: "UnauthorizedCallerOrDelegate",
522
- type: "error"
523
- },
524
- {
525
- inputs: [
526
- {
527
- internalType: "address",
528
- name: "caller",
529
- type: "address"
530
- }
531
- ],
532
- name: "UnauthorizedOrchestratorCaller",
533
- type: "error"
534
- },
535
- {
536
- inputs: [],
537
- name: "ZeroAddress",
538
- type: "error"
539
- },
540
- {
541
- anonymous: false,
542
- inputs: [
543
- {
544
- indexed: true,
545
- internalType: "address",
546
- name: "escrow",
547
- type: "address"
548
- },
549
- {
550
- indexed: true,
551
- internalType: "uint256",
552
- name: "depositId",
553
- type: "uint256"
554
- },
555
- {
556
- indexed: true,
557
- internalType: "address",
558
- name: "taker",
559
- type: "address"
560
- }
561
- ],
562
- name: "TakerRemovedFromWhitelist",
563
- type: "event"
564
- },
565
- {
566
- anonymous: false,
567
- inputs: [
568
- {
569
- indexed: true,
570
- internalType: "address",
571
- name: "escrow",
572
- type: "address"
573
- },
574
- {
575
- indexed: true,
576
- internalType: "uint256",
577
- name: "depositId",
578
- type: "uint256"
579
- },
580
- {
581
- indexed: true,
582
- internalType: "address",
583
- name: "taker",
584
- type: "address"
585
- }
586
- ],
587
- name: "TakerWhitelisted",
588
- type: "event"
589
- },
590
- {
591
- inputs: [
592
- {
593
- internalType: "address",
594
- name: "_escrow",
595
- type: "address"
596
- },
597
- {
598
- internalType: "uint256",
599
- name: "_depositId",
600
- type: "uint256"
601
- },
602
- {
603
- internalType: "address[]",
604
- name: "_takers",
605
- type: "address[]"
606
- }
607
- ],
608
- name: "addToWhitelist",
609
- outputs: [],
610
- stateMutability: "nonpayable",
611
- type: "function"
612
- },
613
- {
614
- inputs: [
615
- {
616
- internalType: "address",
617
- name: "_escrow",
618
- type: "address"
619
- },
620
- {
621
- internalType: "uint256",
622
- name: "_depositId",
623
- type: "uint256"
624
- },
625
- {
626
- internalType: "address",
627
- name: "_taker",
628
- type: "address"
629
- }
630
- ],
631
- name: "isWhitelisted",
632
- outputs: [
633
- {
634
- internalType: "bool",
635
- name: "",
636
- type: "bool"
637
- }
638
- ],
639
- stateMutability: "view",
640
- type: "function"
641
- },
642
- {
643
- inputs: [],
644
- name: "orchestratorRegistry",
645
- outputs: [
646
- {
647
- internalType: "contract IOrchestratorRegistry",
648
- name: "",
649
- type: "address"
650
- }
651
- ],
652
- stateMutability: "view",
653
- type: "function"
654
- },
655
- {
656
- inputs: [
657
- {
658
- internalType: "address",
659
- name: "_escrow",
660
- type: "address"
661
- },
662
- {
663
- internalType: "uint256",
664
- name: "_depositId",
665
- type: "uint256"
666
- },
667
- {
668
- internalType: "address[]",
669
- name: "_takers",
670
- type: "address[]"
671
- }
672
- ],
673
- name: "removeFromWhitelist",
674
- outputs: [],
675
- stateMutability: "nonpayable",
676
- type: "function"
677
- },
678
- {
679
- inputs: [
680
- {
681
- components: [
682
- {
683
- internalType: "address",
684
- name: "taker",
685
- type: "address"
686
- },
687
- {
688
- internalType: "address",
689
- name: "escrow",
690
- type: "address"
691
- },
692
- {
693
- internalType: "uint256",
694
- name: "depositId",
695
- type: "uint256"
696
- },
697
- {
698
- internalType: "uint256",
699
- name: "amount",
700
- type: "uint256"
701
- },
702
- {
703
- internalType: "address",
704
- name: "to",
705
- type: "address"
706
- },
707
- {
708
- internalType: "bytes32",
709
- name: "paymentMethod",
710
- type: "bytes32"
711
- },
712
- {
713
- internalType: "bytes32",
714
- name: "fiatCurrency",
715
- type: "bytes32"
716
- },
717
- {
718
- internalType: "uint256",
719
- name: "conversionRate",
720
- type: "uint256"
721
- },
722
- {
723
- components: [
724
- {
725
- internalType: "address",
726
- name: "recipient",
727
- type: "address"
728
- },
729
- {
730
- internalType: "uint256",
731
- name: "fee",
732
- type: "uint256"
733
- }
734
- ],
735
- internalType: "struct IReferralFee.ReferralFee[]",
736
- name: "referralFees",
737
- type: "tuple[]"
738
- },
739
- {
740
- internalType: "bytes",
741
- name: "preIntentHookData",
742
- type: "bytes"
743
- }
744
- ],
745
- internalType: "struct IPreIntentHook.PreIntentContext",
746
- name: "_ctx",
747
- type: "tuple"
748
- }
749
- ],
750
- name: "validateSignalIntent",
751
- outputs: [],
752
- stateMutability: "view",
753
- type: "function"
754
- },
755
- {
756
- inputs: [
757
- {
758
- internalType: "address",
759
- name: "",
760
- type: "address"
761
- },
762
- {
763
- internalType: "uint256",
764
- name: "",
765
- type: "uint256"
766
- },
767
- {
768
- internalType: "address",
769
- name: "",
770
- type: "address"
771
- }
772
- ],
773
- name: "whitelist",
774
- outputs: [
775
- {
776
- internalType: "bool",
777
- name: "",
778
- type: "bool"
779
- }
780
- ],
781
- stateMutability: "view",
782
- type: "function"
783
- }
784
- ];
661
+ var import_WhitelistPreIntentHook = __toESM(require("@zkp2p/contracts-v2/abis/base/WhitelistPreIntentHook.json"), 1);
785
662
 
786
663
  // src/sdk-client.ts
787
664
  var import_sdk3 = require("@zkp2p/sdk");
788
- function createSdkClient(walletClient) {
665
+ function createSdkClient(walletClient, apiBaseUrl = API_BASE_URL) {
789
666
  return new import_sdk3.OfframpClient({
790
667
  walletClient,
791
668
  chainId: BASE_CHAIN_ID,
792
669
  runtimeEnv: RUNTIME_ENV,
793
670
  rpcUrl: BASE_RPC_URL,
794
- baseApiUrl: API_BASE_URL
671
+ baseApiUrl: apiBaseUrl
795
672
  });
796
673
  }
797
674
 
798
675
  // src/otc.ts
799
676
  var WHITELIST_HOOK_ADDRESS = "0xda023Ea0d789A41BcF5866F7B6BBd2CaDF9b79B8";
800
- var OTC_BASE_URL = "https://usdctofiat.xyz";
677
+ var OTC_BASE_URL = "https://otc.usdctofiat.xyz";
801
678
  async function enableOtc(walletClient, depositId, takerAddress, escrowAddress) {
802
679
  if (!(0, import_viem.isAddress)(takerAddress)) {
803
680
  throw new OfframpError("Invalid taker address", "VALIDATION");
@@ -869,7 +746,7 @@ async function disableOtc(walletClient, depositId, escrowAddress) {
869
746
  }
870
747
  function getOtcLink(depositId, escrowAddress) {
871
748
  const escrow = escrowAddress || ESCROW_ADDRESS;
872
- return `${OTC_BASE_URL}/deposit/${escrow}/${depositId}`;
749
+ return `${OTC_BASE_URL}/d/${escrow}/${depositId}`;
873
750
  }
874
751
  async function writeContract(walletClient, functionName, args) {
875
752
  const wc = walletClient;
@@ -878,7 +755,7 @@ async function writeContract(walletClient, functionName, args) {
878
755
  }
879
756
  await wc.writeContract({
880
757
  address: WHITELIST_HOOK_ADDRESS,
881
- abi: WhitelistPreIntentHook_default,
758
+ abi: import_WhitelistPreIntentHook.default,
882
759
  functionName,
883
760
  args
884
761
  });
@@ -887,7 +764,7 @@ async function readIsWhitelisted(escrow, depositId, taker) {
887
764
  const publicClient = (0, import_viem.createPublicClient)({ chain: import_chains.base, transport: (0, import_viem.http)(BASE_RPC_URL) });
888
765
  return publicClient.readContract({
889
766
  address: WHITELIST_HOOK_ADDRESS,
890
- abi: WhitelistPreIntentHook_default,
767
+ abi: import_WhitelistPreIntentHook.default,
891
768
  functionName: "isWhitelisted",
892
769
  args: [escrow, depositId, taker]
893
770
  });
@@ -1202,41 +1079,24 @@ function extractDepositIdFromLogs(logs) {
1202
1079
  }
1203
1080
  return null;
1204
1081
  }
1205
- async function registerPayeeDetails(processorName, payload) {
1082
+ async function validatePayeeDetails(processorName, offchainId, apiBaseUrl = API_BASE_URL) {
1206
1083
  const controller = new AbortController();
1207
1084
  const timeout = setTimeout(() => controller.abort(), PAYEE_REGISTRATION_TIMEOUT_MS);
1208
1085
  try {
1209
- const res = await fetch(`${API_BASE_URL}/v2/makers/create`, {
1086
+ const res = await fetch(`${apiBaseUrl}/v2/makers/validate`, {
1210
1087
  method: "POST",
1211
1088
  headers: { "Content-Type": "application/json" },
1212
- body: JSON.stringify({ processorName, ...payload }),
1089
+ body: JSON.stringify({ processorName, offchainId }),
1213
1090
  signal: controller.signal
1214
1091
  });
1215
- if (!res.ok) {
1216
- const txt = await res.text().catch(() => "");
1217
- let detail = txt || res.statusText;
1218
- try {
1219
- const parsed = JSON.parse(txt);
1220
- if (typeof parsed.message === "string" && parsed.message.trim()) {
1221
- detail = parsed.message;
1222
- }
1223
- } catch {
1224
- }
1225
- throw new MakersCreateError(
1226
- `makers/create failed (${res.status}): ${detail}`,
1227
- res.status,
1228
- txt
1229
- );
1230
- }
1092
+ if (!res.ok) return "unknown";
1231
1093
  const json = await res.json();
1232
- if (!json.success || !json.responseObject?.hashedOnchainId) {
1233
- throw new MakersCreateError(
1234
- json.message || "makers/create returned no hashedOnchainId",
1235
- json.statusCode ?? null,
1236
- ""
1237
- );
1094
+ if (typeof json.responseObject === "boolean") {
1095
+ return json.responseObject ? "valid" : "invalid";
1238
1096
  }
1239
- return json.responseObject.hashedOnchainId;
1097
+ return "unknown";
1098
+ } catch {
1099
+ return "unknown";
1240
1100
  } finally {
1241
1101
  clearTimeout(timeout);
1242
1102
  }
@@ -1301,10 +1161,11 @@ async function findUndelegatedDeposit(walletAddress) {
1301
1161
  return null;
1302
1162
  }
1303
1163
  }
1304
- async function offramp(walletClient, params, onProgress, telemetryContext) {
1164
+ async function offramp(walletClient, params, onProgress, telemetryContext, apiBaseUrl) {
1305
1165
  const { amount, platform, currency, identifier } = params;
1306
1166
  const platformId = platform.id;
1307
1167
  const currencyCode = currency.code;
1168
+ const resolvedApiBaseUrl = resolveApiBaseUrl(apiBaseUrl);
1308
1169
  const telemetry = telemetryContext ?? createTelemetryContext({
1309
1170
  sdkVersion: SDK_VERSION,
1310
1171
  telemetry: true,
@@ -1349,7 +1210,7 @@ async function offramp(walletClient, params, onProgress, telemetryContext) {
1349
1210
  const existing = await findUndelegatedDeposit(walletAddress);
1350
1211
  if (existing) {
1351
1212
  emitProgress({ step: "resuming", depositId: existing.depositId });
1352
- const client2 = createSdkClient(walletClient);
1213
+ const client2 = createSdkClient(walletClient, resolvedApiBaseUrl);
1353
1214
  const txOverrides2 = { referrer: [REFERRER] };
1354
1215
  try {
1355
1216
  const result2 = await client2.setRateManager({
@@ -1385,7 +1246,7 @@ async function offramp(walletClient, params, onProgress, telemetryContext) {
1385
1246
  const truncatedAmount = amt.toFixed(6).replace(/\.?0+$/, "");
1386
1247
  const amountUnits = usdcToUnits(truncatedAmount);
1387
1248
  const minUnits = usdcToUnits(String(MIN_ORDER_USDC));
1388
- const maxUnits = usdcToUnits(String(Math.min(amt, 2500)));
1249
+ const maxUnits = usdcToUnits(String(Math.min(amt, MAX_ORDER_USDC)));
1389
1250
  try {
1390
1251
  const publicClient = (0, import_viem3.createPublicClient)({ chain: import_chains3.base, transport: (0, import_viem3.http)(BASE_RPC_URL) });
1391
1252
  const balance = await publicClient.readContract({
@@ -1412,7 +1273,22 @@ async function offramp(walletClient, params, onProgress, telemetryContext) {
1412
1273
  } catch (err) {
1413
1274
  if (err instanceof OfframpError) throw err;
1414
1275
  }
1415
- const client = createSdkClient(walletClient);
1276
+ const canonicalName = platformId.startsWith("zelle") ? "zelle" : platformId;
1277
+ if (!getPeerExtensionRegistrationInfo(platformId)) {
1278
+ const outcome = await validatePayeeDetails(
1279
+ canonicalName,
1280
+ normalizedIdentifier,
1281
+ resolvedApiBaseUrl
1282
+ );
1283
+ if (outcome === "invalid") {
1284
+ throw new OfframpError(
1285
+ `Couldn't verify that ${platform.name} ${platform.identifier.label.toLowerCase()}. Double-check it and try again.`,
1286
+ "VALIDATION",
1287
+ "registering"
1288
+ );
1289
+ }
1290
+ }
1291
+ const client = createSdkClient(walletClient, resolvedApiBaseUrl);
1416
1292
  const txOverrides = { referrer: [REFERRER] };
1417
1293
  emitProgress({ step: "approving" });
1418
1294
  try {
@@ -1436,16 +1312,15 @@ async function offramp(walletClient, params, onProgress, telemetryContext) {
1436
1312
  }
1437
1313
  emitProgress({ step: "registering" });
1438
1314
  let hashedOnchainId;
1439
- const canonicalName = platformId.startsWith("zelle") ? "zelle" : platformId;
1440
1315
  try {
1441
1316
  const payeePayload = buildDepositData(canonicalName, normalizedIdentifier);
1442
- hashedOnchainId = await registerPayeeDetails(canonicalName, payeePayload);
1317
+ hashedOnchainId = await postMakerCreate(canonicalName, payeePayload, resolvedApiBaseUrl);
1443
1318
  } catch (err) {
1444
1319
  const status = err instanceof MakersCreateError ? err.status : null;
1445
1320
  const message = err instanceof Error ? err.message : String(err);
1446
1321
  if (isPeerExtensionRegistrationError(canonicalName, message, status)) {
1447
1322
  throw new OfframpError(
1448
- `${platform.name} maker is not yet registered in the Peer extension. Verify this handle in the extension, then retry.`,
1323
+ `${platform.name} maker is not yet registered in the Peer extension. Register this handle with Peer, then retry.`,
1449
1324
  "EXTENSION_REGISTRATION_REQUIRED",
1450
1325
  "registering",
1451
1326
  err
@@ -1637,6 +1512,14 @@ var CURRENCIES = buildCurrencies();
1637
1512
  // src/offramp.ts
1638
1513
  var IDEMPOTENCY_TTL_MS = 10 * 60 * 1e3;
1639
1514
  var IDEMPOTENCY_STORAGE_PREFIX = "offramp:idempotency:";
1515
+ var warnedIdempotencyUnavailable = false;
1516
+ function warnIdempotencyUnavailableOnce() {
1517
+ if (warnedIdempotencyUnavailable) return;
1518
+ warnedIdempotencyUnavailable = true;
1519
+ console.warn(
1520
+ "[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."
1521
+ );
1522
+ }
1640
1523
  function getWalletAddress(walletClient) {
1641
1524
  const address = walletClient.account?.address;
1642
1525
  if (!address) {
@@ -1707,8 +1590,10 @@ function buildCurrencyGroups() {
1707
1590
  var Offramp = class {
1708
1591
  walletClient;
1709
1592
  telemetry;
1593
+ apiBaseUrl;
1710
1594
  constructor(options) {
1711
1595
  this.walletClient = options.walletClient;
1596
+ this.apiBaseUrl = options.apiBaseUrl;
1712
1597
  this.telemetry = createTelemetryContext({
1713
1598
  sdkVersion: SDK_VERSION,
1714
1599
  telemetry: options.telemetry,
@@ -1731,6 +1616,9 @@ var Offramp = class {
1731
1616
  referralId: flowTelemetry.referralId
1732
1617
  };
1733
1618
  const sanitizedKey = sanitizeAttributionId(params.idempotencyKey);
1619
+ if (sanitizedKey && typeof sessionStorage === "undefined") {
1620
+ warnIdempotencyUnavailableOnce();
1621
+ }
1734
1622
  const walletAddress = sanitizedKey ? getWalletAddress(this.walletClient) : null;
1735
1623
  const storageKey = walletAddress && sanitizedKey ? getIdempotencyStorageKey(walletAddress, sanitizedKey) : null;
1736
1624
  if (storageKey) {
@@ -1741,7 +1629,8 @@ var Offramp = class {
1741
1629
  this.walletClient,
1742
1630
  sanitizedParams,
1743
1631
  onProgress,
1744
- flowTelemetry
1632
+ flowTelemetry,
1633
+ this.apiBaseUrl
1745
1634
  );
1746
1635
  if (storageKey) {
1747
1636
  writeIdempotencyResult(storageKey, result);
@@ -1797,8 +1686,8 @@ var OFFRAMP_ERROR_CODES = {
1797
1686
  * handle in the Peer extension yet. Thrown from `offramp()` for PayPal
1798
1687
  * and Wise when `/v2/makers/create` returns 400 or a "creating the maker"
1799
1688
  * message. Recover by prompting the user through the Peer extension via
1800
- * `usePeerExtensionRegistration(platform)` (React) or `peerExtensionSdk`
1801
- * directly, then call `offramp()` again.
1689
+ * `usePeerExtensionRegistration(platform)` (React) or
1690
+ * `completePeerExtensionRegistration(...)`, then retrying `offramp()`.
1802
1691
  */
1803
1692
  EXTENSION_REGISTRATION_REQUIRED: "EXTENSION_REGISTRATION_REQUIRED",
1804
1693
  DEPOSIT_FAILED: "DEPOSIT_FAILED",
@@ -1810,6 +1699,78 @@ var OFFRAMP_ERROR_CODES = {
1810
1699
 
1811
1700
  // src/extension.ts
1812
1701
  var import_sdk7 = require("@zkp2p/sdk");
1702
+ var resolveWindow = (options) => {
1703
+ if (options?.window) {
1704
+ return options.window;
1705
+ }
1706
+ if (typeof window === "undefined") {
1707
+ return void 0;
1708
+ }
1709
+ return window;
1710
+ };
1711
+ var requirePeer = (options) => {
1712
+ const resolvedWindow = resolveWindow(options);
1713
+ if (!resolvedWindow) {
1714
+ throw new Error("Peer extension SDK requires a browser window.");
1715
+ }
1716
+ if (!resolvedWindow.peer) {
1717
+ throw new Error("Peer extension not available. Install or enable the Peer extension.");
1718
+ }
1719
+ return resolvedWindow.peer;
1720
+ };
1721
+ var isPeerExtensionAvailable = (options) => {
1722
+ const resolvedWindow = resolveWindow(options);
1723
+ return Boolean(resolvedWindow?.peer);
1724
+ };
1725
+ var hasHeadlessMetadataBridge = (peer) => typeof peer.authenticate === "function" && typeof peer.onMetadataMessage === "function";
1726
+ var requirePeerMetadataBridge = (options) => {
1727
+ const peer = requirePeer(options);
1728
+ if (!hasHeadlessMetadataBridge(peer)) {
1729
+ throw new Error("Peer extension metadata bridge unavailable. Install or update Peer.");
1730
+ }
1731
+ return peer;
1732
+ };
1733
+ var isPeerExtensionMetadataBridgeAvailable = (options) => {
1734
+ const resolvedWindow = resolveWindow(options);
1735
+ return Boolean(resolvedWindow?.peer && hasHeadlessMetadataBridge(resolvedWindow.peer));
1736
+ };
1737
+ var openPeerExtensionInstallPage = (options) => {
1738
+ const resolvedWindow = resolveWindow(options);
1739
+ if (!resolvedWindow) {
1740
+ throw new Error("Peer extension SDK requires a browser window.");
1741
+ }
1742
+ resolvedWindow.open(import_sdk7.PEER_EXTENSION_CHROME_URL, "_blank", "noopener,noreferrer");
1743
+ };
1744
+ var getPeerExtensionState = async (options) => {
1745
+ if (!isPeerExtensionAvailable(options)) {
1746
+ return "needs_install";
1747
+ }
1748
+ try {
1749
+ const status = await requirePeer(options).checkConnectionStatus();
1750
+ return status === "connected" ? "ready" : "needs_connection";
1751
+ } catch {
1752
+ return "needs_connection";
1753
+ }
1754
+ };
1755
+ var createPeerExtensionSdk = (options = {}) => {
1756
+ const legacySdk = (0, import_sdk7.createPeerExtensionSdk)(
1757
+ options
1758
+ );
1759
+ return {
1760
+ isAvailable: () => isPeerExtensionAvailable(options),
1761
+ requestConnection: () => requirePeer(options).requestConnection(),
1762
+ checkConnectionStatus: () => requirePeer(options).checkConnectionStatus(),
1763
+ getVersion: () => requirePeer(options).getVersion(),
1764
+ authenticate: (params) => requirePeerMetadataBridge(options).authenticate(params),
1765
+ onMetadataMessage: (callback) => requirePeerMetadataBridge(options).onMetadataMessage(callback),
1766
+ openSidebar: (route) => legacySdk.openSidebar(route),
1767
+ onramp: (params, callback) => legacySdk.onramp(params, callback),
1768
+ getOnrampTransaction: (intentHash) => legacySdk.getOnrampTransaction(intentHash),
1769
+ openInstallPage: () => openPeerExtensionInstallPage(options),
1770
+ getState: () => getPeerExtensionState(options)
1771
+ };
1772
+ };
1773
+ var peerExtensionSdk = createPeerExtensionSdk();
1813
1774
  // Annotate the CommonJS export names for ESM import in node:
1814
1775
  0 && (module.exports = {
1815
1776
  CURRENCIES,
@@ -1820,7 +1781,9 @@ var import_sdk7 = require("@zkp2p/sdk");
1820
1781
  OfframpError,
1821
1782
  PEER_EXTENSION_CHROME_URL,
1822
1783
  PLATFORMS,
1784
+ PLATFORM_LIMITS,
1823
1785
  close,
1786
+ completePeerExtensionRegistration,
1824
1787
  createOfframp,
1825
1788
  createPeerExtensionSdk,
1826
1789
  createTelemetryContext,
@@ -1830,10 +1793,14 @@ var import_sdk7 = require("@zkp2p/sdk");
1830
1793
  emitEvent,
1831
1794
  enableOtc,
1832
1795
  getOtcLink,
1796
+ getPeerExtensionRegistrationAuthParams,
1833
1797
  getPeerExtensionRegistrationInfo,
1834
1798
  getPeerExtensionState,
1799
+ getPlatformLimits,
1835
1800
  isPeerExtensionAvailable,
1801
+ isPeerExtensionMetadataBridgeAvailable,
1836
1802
  isPeerExtensionRegistrationError,
1803
+ isValidIBAN,
1837
1804
  normalizePaypalMeUsername,
1838
1805
  offramp,
1839
1806
  openPeerExtensionInstallPage,