@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/react.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/react.ts
@@ -30,11 +40,15 @@ var import_react = require("react");
30
40
 
31
41
  // src/config.ts
32
42
  var import_sdk = require("@zkp2p/sdk");
33
- var SDK_VERSION = "3.0.2";
43
+ var SDK_VERSION = "3.1.0";
34
44
  var BASE_CHAIN_ID = 8453;
35
45
  var RUNTIME_ENV = "production";
36
46
  var API_BASE_URL = "https://api.zkp2p.xyz";
37
47
  var BASE_RPC_URL = "https://mainnet.base.org";
48
+ function resolveApiBaseUrl(override) {
49
+ const trimmed = override?.trim();
50
+ return (trimmed && trimmed.length > 0 ? trimmed : API_BASE_URL).replace(/\/$/, "");
51
+ }
38
52
  var contracts = (0, import_sdk.getContracts)(BASE_CHAIN_ID, RUNTIME_ENV);
39
53
  var addresses = contracts.addresses;
40
54
  var addrs = addresses;
@@ -51,6 +65,7 @@ var DEFAULT_INTENT_GUARDIAN_ADDRESS = "0xe29a5BD4D0CEbA6C125A0361E7D20ab4eA275C2
51
65
  var REFERRER = "galleonlabs";
52
66
  var MIN_DEPOSIT_USDC = 1;
53
67
  var MIN_ORDER_USDC = 1;
68
+ var MAX_ORDER_USDC = 2500;
54
69
  var INDEXER_MAX_ATTEMPTS = 12;
55
70
  var INDEXER_INITIAL_DELAY_MS = 1e3;
56
71
  var INDEXER_MAX_DELAY_MS = 1e4;
@@ -82,6 +97,41 @@ var import_sdk6 = require("@zkp2p/sdk");
82
97
  // src/platforms.ts
83
98
  var import_sdk3 = require("@zkp2p/sdk");
84
99
  var import_zod = require("zod");
100
+
101
+ // src/errors.ts
102
+ var MakersCreateError = class extends Error {
103
+ status;
104
+ body;
105
+ constructor(message, status, body) {
106
+ super(message);
107
+ this.name = "MakersCreateError";
108
+ this.status = status;
109
+ this.body = body;
110
+ }
111
+ };
112
+ var OfframpError = class extends Error {
113
+ code;
114
+ step;
115
+ cause;
116
+ txHash;
117
+ depositId;
118
+ constructor(message, code, step, cause, details) {
119
+ super(message);
120
+ this.name = "OfframpError";
121
+ this.code = code;
122
+ this.step = step;
123
+ this.cause = cause;
124
+ this.txHash = details?.txHash;
125
+ this.depositId = details?.depositId;
126
+ }
127
+ };
128
+ function isUserCancellation(error) {
129
+ if (!(error instanceof Error)) return false;
130
+ const msg = error.message.toLowerCase();
131
+ return msg.includes("user rejected") || msg.includes("user denied") || msg.includes("user cancelled") || msg.includes("rejected the request") || msg.includes("action_rejected");
132
+ }
133
+
134
+ // src/platforms.ts
85
135
  var PAYMENT_CATALOG = (0, import_sdk3.getPaymentMethodsCatalog)(BASE_CHAIN_ID, RUNTIME_ENV);
86
136
  var ZELLE_HASH_LOOKUP_NAMES = ["zelle", "zelle-bofa", "zelle-chase", "zelle-citi"];
87
137
  var FALLBACK_CURRENCIES = {
@@ -150,7 +200,8 @@ var FALLBACK_CURRENCIES = {
150
200
  zelle: ["USD"],
151
201
  paypal: ["USD", "EUR", "GBP", "SGD", "NZD", "AUD", "CAD"],
152
202
  monzo: ["GBP"],
153
- n26: ["EUR"]
203
+ n26: ["EUR"],
204
+ luxon: ["USD", "EUR", "GBP"]
154
205
  };
155
206
  function gatherCatalogHashes(platform) {
156
207
  if (platform === "zelle") {
@@ -174,13 +225,39 @@ function resolveSupportedCurrencies(platform) {
174
225
  function normalizePaypalMeUsername(value) {
175
226
  const trimmed = value.trim();
176
227
  if (!trimmed) return "";
177
- const withoutProtocol = trimmed.replace(/^https?:\/\//i, "").replace(/^www\./i, "");
178
- if (/^paypal\.me(?:[?#].*)?$/i.test(withoutProtocol)) return "";
179
- const withoutDomain = withoutProtocol.replace(/^paypal\.me\//i, "");
180
- const [pathWithoutQuery] = withoutDomain.split(/[?#]/, 1);
228
+ const withoutProtocol = trimmed.replace(/^https?:\/\//i, "");
229
+ const [pathWithoutQuery] = withoutProtocol.split(/[?#]/, 1);
181
230
  const sanitizedPath = pathWithoutQuery.replace(/^\/+/, "");
182
- const [username] = sanitizedPath.split("/", 1);
183
- return username.replace(/^@+/, "").trim().toLowerCase();
231
+ const [host = "", ...pathSegments] = sanitizedPath.split("/");
232
+ const normalizedHost = host.toLowerCase().replace(/^www\./i, "");
233
+ if (normalizedHost === "paypal.me") {
234
+ const [username = ""] = pathSegments;
235
+ return username.replace(/^@+/, "").trim().toLowerCase();
236
+ }
237
+ if (normalizedHost === "paypal.com") {
238
+ const [paypalMePath = "", username = ""] = pathSegments;
239
+ if (paypalMePath.toLowerCase() !== "paypalme") return "";
240
+ return username.replace(/^@+/, "").trim().toLowerCase();
241
+ }
242
+ if (/^https?:\/\//i.test(trimmed) || trimmed.includes("/")) {
243
+ return trimmed;
244
+ }
245
+ return trimmed.replace(/^@+/, "").toLowerCase();
246
+ }
247
+ function isValidIBAN(iban) {
248
+ const upper = iban.toUpperCase();
249
+ if (!/^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$/.test(upper)) return false;
250
+ const rearranged = upper.slice(4) + upper.slice(0, 4);
251
+ const numeric = rearranged.split("").map((ch) => {
252
+ const code = ch.charCodeAt(0);
253
+ return code >= 65 && code <= 90 ? (code - 55).toString() : ch;
254
+ }).join("");
255
+ let remainder = numeric;
256
+ while (remainder.length > 2) {
257
+ const block = remainder.slice(0, 9);
258
+ remainder = (parseInt(block, 10) % 97).toString() + remainder.slice(block.length);
259
+ }
260
+ return parseInt(remainder, 10) % 97 === 1;
184
261
  }
185
262
  var BLUEPRINTS = {
186
263
  venmo: {
@@ -232,9 +309,9 @@ var BLUEPRINTS = {
232
309
  transform: (v) => v.replace(/^@+/, "").trim(),
233
310
  extensionRegistration: {
234
311
  providerId: "wise",
235
- requiredPrompt: "This Wisetag is not registered yet. Verify it in the Peer extension, then refresh and retry with the same Wisetag.",
236
- ctaLabel: "Install Peer Extension",
237
- minExtensionVersion: "0.4.12"
312
+ requiredPrompt: "This Wisetag is not registered yet. Register it with PeerAuth, then retry with the same Wisetag.",
313
+ ctaLabel: "Register Wisetag",
314
+ minExtensionVersion: "0.5.0"
238
315
  }
239
316
  },
240
317
  mercadopago: {
@@ -251,7 +328,11 @@ var BLUEPRINTS = {
251
328
  identifierLabel: "Email",
252
329
  placeholder: "email",
253
330
  helperText: "Registered Zelle email",
254
- validation: import_zod.z.string().email()
331
+ // Curator's ZelleProcessor rejects any non-lowercase email outright, so
332
+ // canonicalize to lowercase here rather than letting /v2/makers/create
333
+ // reject after the maker has already approved USDC + paid gas.
334
+ validation: import_zod.z.string().email(),
335
+ transform: (v) => v.trim().toLowerCase()
255
336
  },
256
337
  paypal: {
257
338
  id: "paypal",
@@ -259,14 +340,17 @@ var BLUEPRINTS = {
259
340
  identifierLabel: "PayPal.me Username",
260
341
  placeholder: "paypal.me/your-username",
261
342
  helperText: "Your PayPal.me username, not your email",
262
- validation: import_zod.z.string().min(1, "PayPal.me username is required").regex(/^[a-z0-9._-]+$/i, "Use your PayPal.me username (letters, numbers, . _ -)"),
343
+ validation: import_zod.z.string().min(1, "PayPal.me username is required").regex(
344
+ /^[a-z0-9._-]+$/i,
345
+ "Use your PayPal.me username or paypal.me link, not a shortened URL."
346
+ ),
263
347
  transform: normalizePaypalMeUsername,
264
348
  extensionRegistration: {
265
349
  providerId: "paypal",
266
- requiredPrompt: "This PayPal username is not registered yet. Verify it in the Peer extension, then refresh and retry with the same PayPal username.",
267
- ctaLabel: "Install Peer Extension",
350
+ requiredPrompt: "This PayPal username is not registered yet. Register it with PeerAuth, then retry with the same PayPal username.",
351
+ ctaLabel: "Register PayPal Username",
268
352
  ctaSubtext: "PayPal Business accounts are not supported at this time.",
269
- minExtensionVersion: "0.4.14"
353
+ minExtensionVersion: "0.5.0"
270
354
  }
271
355
  },
272
356
  monzo: {
@@ -283,8 +367,20 @@ var BLUEPRINTS = {
283
367
  identifierLabel: "IBAN",
284
368
  placeholder: "IBAN (e.g. DE89...)",
285
369
  helperText: "Your IBAN (spaces will be removed)",
286
- validation: import_zod.z.string().min(15).max(34).regex(/^[A-Z]{2}[0-9]{2}[A-Z0-9]+$/i),
370
+ // transform runs before validation, so the schema sees a spaceless,
371
+ // uppercased IBAN. The MOD-97 refine mirrors curator's N26Processor so a
372
+ // typo'd-but-well-formed IBAN fails here, not after USDC approval + gas.
373
+ 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"),
287
374
  transform: (v) => v.replace(/\s/g, "").toUpperCase()
375
+ },
376
+ luxon: {
377
+ id: "luxon",
378
+ name: "Luxon",
379
+ identifierLabel: "Email",
380
+ placeholder: "your-luxon-email@example.com",
381
+ helperText: "Email address registered with Luxon",
382
+ validation: import_zod.z.string().email(),
383
+ transform: (v) => v.trim()
288
384
  }
289
385
  };
290
386
  function buildPlatformEntry(bp) {
@@ -323,7 +419,8 @@ var PLATFORMS = {
323
419
  ZELLE: buildPlatformEntry(BLUEPRINTS.zelle),
324
420
  PAYPAL: buildPlatformEntry(BLUEPRINTS.paypal),
325
421
  MONZO: buildPlatformEntry(BLUEPRINTS.monzo),
326
- N26: buildPlatformEntry(BLUEPRINTS.n26)
422
+ N26: buildPlatformEntry(BLUEPRINTS.n26),
423
+ LUXON: buildPlatformEntry(BLUEPRINTS.luxon)
327
424
  };
328
425
  function getPlatformById(id) {
329
426
  return Object.values(PLATFORMS).find((p) => p.id === id) ?? null;
@@ -386,6 +483,146 @@ function getPeerExtensionRegistrationInfo(platform) {
386
483
  const entry = getPlatformById(platform);
387
484
  return entry?.extensionRegistration ?? null;
388
485
  }
486
+ function getPeerExtensionRegistrationAuthParams(platform, options = {}) {
487
+ const info = getPeerExtensionRegistrationInfo(platform);
488
+ if (!info) return null;
489
+ const params = {
490
+ actionType: `transfer_${info.providerId}`,
491
+ captureMode: "sellerCredential",
492
+ platform: info.providerId
493
+ };
494
+ if (options.attestationServiceUrl) {
495
+ params.attestationServiceUrl = options.attestationServiceUrl;
496
+ }
497
+ return params;
498
+ }
499
+ function resolveRegistrationProcessor(platform) {
500
+ const info = getPeerExtensionRegistrationInfo(platform.id);
501
+ if (info?.providerId === "paypal" || info?.providerId === "wise") {
502
+ return info.providerId;
503
+ }
504
+ throw new Error(`${platform.name} does not require Peer extension registration.`);
505
+ }
506
+ async function postMakerCreate(processorName, payload, apiBaseUrl = API_BASE_URL) {
507
+ const controller = new AbortController();
508
+ const timeout = setTimeout(() => controller.abort(), PAYEE_REGISTRATION_TIMEOUT_MS);
509
+ try {
510
+ const res = await fetch(`${apiBaseUrl}/v2/makers/create`, {
511
+ method: "POST",
512
+ headers: { "Content-Type": "application/json" },
513
+ body: JSON.stringify({ processorName, ...payload }),
514
+ signal: controller.signal
515
+ });
516
+ if (!res.ok) {
517
+ const txt = await res.text().catch(() => "");
518
+ let detail = txt || res.statusText;
519
+ try {
520
+ const parsed = JSON.parse(txt);
521
+ if (typeof parsed.message === "string" && parsed.message.trim()) {
522
+ detail = parsed.message;
523
+ }
524
+ } catch {
525
+ }
526
+ throw new MakersCreateError(
527
+ `makers/create failed (${res.status}): ${detail}`,
528
+ res.status,
529
+ txt
530
+ );
531
+ }
532
+ const json = await res.json();
533
+ if (!json.success || !json.responseObject?.hashedOnchainId) {
534
+ throw new MakersCreateError(
535
+ json.message || "makers/create returned no hashedOnchainId",
536
+ json.statusCode ?? null,
537
+ ""
538
+ );
539
+ }
540
+ return json.responseObject.hashedOnchainId;
541
+ } finally {
542
+ clearTimeout(timeout);
543
+ }
544
+ }
545
+ function requireSarCredentialCapture(capturedMetadata) {
546
+ const capture = capturedMetadata.sarCredentialCapture;
547
+ if (!capture?.credentialBundle) {
548
+ throw new Error("Peer metadata did not include a seller credential bundle.");
549
+ }
550
+ if (!capture.offchainId) {
551
+ throw new Error("Peer metadata did not include seller credential offchainId.");
552
+ }
553
+ return capture;
554
+ }
555
+ async function uploadSellerCredentialBundle(processorName, payeeHash, bundle) {
556
+ const controller = new AbortController();
557
+ const timeout = setTimeout(() => controller.abort(), PAYEE_REGISTRATION_TIMEOUT_MS);
558
+ try {
559
+ const res = await fetch(
560
+ `${API_BASE_URL}/v2/makers/${encodeURIComponent(processorName)}/${encodeURIComponent(payeeHash)}/seller-credential`,
561
+ {
562
+ method: "POST",
563
+ headers: { "Content-Type": "application/json" },
564
+ body: JSON.stringify(bundle),
565
+ signal: controller.signal
566
+ }
567
+ );
568
+ if (!res.ok) {
569
+ const txt = await res.text().catch(() => "");
570
+ let detail = txt || res.statusText;
571
+ try {
572
+ const parsed = JSON.parse(txt);
573
+ if (typeof parsed.message === "string" && parsed.message.trim()) {
574
+ detail = parsed.message;
575
+ }
576
+ } catch {
577
+ }
578
+ throw new Error(`seller-credential upload failed (${res.status}): ${detail}`);
579
+ }
580
+ const json = await res.json();
581
+ if (!json.success || !json.responseObject) {
582
+ throw new Error(json.message || "seller-credential upload returned no status.");
583
+ }
584
+ return json;
585
+ } finally {
586
+ clearTimeout(timeout);
587
+ }
588
+ }
589
+ async function completePeerExtensionRegistration(input) {
590
+ const processorName = resolveRegistrationProcessor(input.platform);
591
+ if (input.capturedMetadata.platform !== processorName) {
592
+ throw new Error(
593
+ `Peer metadata is for ${input.capturedMetadata.platform}, not ${processorName}.`
594
+ );
595
+ }
596
+ const sarCredentialCapture = requireSarCredentialCapture(input.capturedMetadata);
597
+ const bundle = sarCredentialCapture.credentialBundle;
598
+ if (bundle.platform.toLowerCase() !== processorName) {
599
+ throw new Error("Seller credential bundle platform does not match registration platform.");
600
+ }
601
+ const validation = input.platform.validate(input.identifier);
602
+ if (!validation.valid) {
603
+ throw new Error(validation.error || "Invalid identifier");
604
+ }
605
+ const payload = buildDepositData(processorName, sarCredentialCapture.offchainId);
606
+ if (payload.offchainId !== validation.normalized) {
607
+ throw new Error("Seller credential offchainId does not match registration identifier.");
608
+ }
609
+ const hashedOnchainId = processorName === "wise" ? bundle.payeeIdHash : await postMakerCreate(processorName, payload);
610
+ if (hashedOnchainId.toLowerCase() !== bundle.payeeIdHash.toLowerCase()) {
611
+ throw new Error("Seller credential payee hash does not match registered payee details.");
612
+ }
613
+ const sellerCredentialResponse = await uploadSellerCredentialBundle(
614
+ processorName,
615
+ hashedOnchainId,
616
+ bundle
617
+ );
618
+ return {
619
+ processorName,
620
+ offchainId: payload.offchainId,
621
+ hashedOnchainId,
622
+ sellerCredentialResponse,
623
+ sellerCredentialStatus: sellerCredentialResponse.responseObject
624
+ };
625
+ }
389
626
  var EXTENSION_REGISTRATION_ERROR_PATTERNS = ["creating the maker", "create the maker"];
390
627
  function isPeerExtensionRegistrationError(platform, message, statusCode) {
391
628
  const info = getPeerExtensionRegistrationInfo(platform);
@@ -397,401 +634,26 @@ function isPeerExtensionRegistrationError(platform, message, statusCode) {
397
634
  return EXTENSION_REGISTRATION_ERROR_PATTERNS.some((p) => lower.includes(p));
398
635
  }
399
636
 
400
- // src/errors.ts
401
- var MakersCreateError = class extends Error {
402
- status;
403
- body;
404
- constructor(message, status, body) {
405
- super(message);
406
- this.name = "MakersCreateError";
407
- this.status = status;
408
- this.body = body;
409
- }
410
- };
411
- var OfframpError = class extends Error {
412
- code;
413
- step;
414
- cause;
415
- txHash;
416
- depositId;
417
- constructor(message, code, step, cause, details) {
418
- super(message);
419
- this.name = "OfframpError";
420
- this.code = code;
421
- this.step = step;
422
- this.cause = cause;
423
- this.txHash = details?.txHash;
424
- this.depositId = details?.depositId;
425
- }
426
- };
427
- function isUserCancellation(error) {
428
- if (!(error instanceof Error)) return false;
429
- const msg = error.message.toLowerCase();
430
- return msg.includes("user rejected") || msg.includes("user denied") || msg.includes("user cancelled") || msg.includes("rejected the request") || msg.includes("action_rejected");
431
- }
432
-
433
637
  // src/otc.ts
434
638
  var import_viem = require("viem");
435
639
  var import_chains = require("viem/chains");
436
-
437
- // ../../node_modules/@zkp2p/contracts-v2/abis/base/WhitelistPreIntentHook.json
438
- var WhitelistPreIntentHook_default = [
439
- {
440
- inputs: [
441
- {
442
- internalType: "address",
443
- name: "_orchestratorRegistry",
444
- type: "address"
445
- }
446
- ],
447
- stateMutability: "nonpayable",
448
- type: "constructor"
449
- },
450
- {
451
- inputs: [],
452
- name: "EmptyArray",
453
- type: "error"
454
- },
455
- {
456
- inputs: [
457
- {
458
- internalType: "address",
459
- name: "taker",
460
- type: "address"
461
- },
462
- {
463
- internalType: "address",
464
- name: "escrow",
465
- type: "address"
466
- },
467
- {
468
- internalType: "uint256",
469
- name: "depositId",
470
- type: "uint256"
471
- }
472
- ],
473
- name: "TakerNotInWhitelist",
474
- type: "error"
475
- },
476
- {
477
- inputs: [
478
- {
479
- internalType: "address",
480
- name: "taker",
481
- type: "address"
482
- },
483
- {
484
- internalType: "address",
485
- name: "escrow",
486
- type: "address"
487
- },
488
- {
489
- internalType: "uint256",
490
- name: "depositId",
491
- type: "uint256"
492
- }
493
- ],
494
- name: "TakerNotWhitelisted",
495
- type: "error"
496
- },
497
- {
498
- inputs: [
499
- {
500
- internalType: "address",
501
- name: "caller",
502
- type: "address"
503
- },
504
- {
505
- internalType: "address",
506
- name: "owner",
507
- type: "address"
508
- },
509
- {
510
- internalType: "address",
511
- name: "delegate",
512
- type: "address"
513
- }
514
- ],
515
- name: "UnauthorizedCallerOrDelegate",
516
- type: "error"
517
- },
518
- {
519
- inputs: [
520
- {
521
- internalType: "address",
522
- name: "caller",
523
- type: "address"
524
- }
525
- ],
526
- name: "UnauthorizedOrchestratorCaller",
527
- type: "error"
528
- },
529
- {
530
- inputs: [],
531
- name: "ZeroAddress",
532
- type: "error"
533
- },
534
- {
535
- anonymous: false,
536
- inputs: [
537
- {
538
- indexed: true,
539
- internalType: "address",
540
- name: "escrow",
541
- type: "address"
542
- },
543
- {
544
- indexed: true,
545
- internalType: "uint256",
546
- name: "depositId",
547
- type: "uint256"
548
- },
549
- {
550
- indexed: true,
551
- internalType: "address",
552
- name: "taker",
553
- type: "address"
554
- }
555
- ],
556
- name: "TakerRemovedFromWhitelist",
557
- type: "event"
558
- },
559
- {
560
- anonymous: false,
561
- inputs: [
562
- {
563
- indexed: true,
564
- internalType: "address",
565
- name: "escrow",
566
- type: "address"
567
- },
568
- {
569
- indexed: true,
570
- internalType: "uint256",
571
- name: "depositId",
572
- type: "uint256"
573
- },
574
- {
575
- indexed: true,
576
- internalType: "address",
577
- name: "taker",
578
- type: "address"
579
- }
580
- ],
581
- name: "TakerWhitelisted",
582
- type: "event"
583
- },
584
- {
585
- inputs: [
586
- {
587
- internalType: "address",
588
- name: "_escrow",
589
- type: "address"
590
- },
591
- {
592
- internalType: "uint256",
593
- name: "_depositId",
594
- type: "uint256"
595
- },
596
- {
597
- internalType: "address[]",
598
- name: "_takers",
599
- type: "address[]"
600
- }
601
- ],
602
- name: "addToWhitelist",
603
- outputs: [],
604
- stateMutability: "nonpayable",
605
- type: "function"
606
- },
607
- {
608
- inputs: [
609
- {
610
- internalType: "address",
611
- name: "_escrow",
612
- type: "address"
613
- },
614
- {
615
- internalType: "uint256",
616
- name: "_depositId",
617
- type: "uint256"
618
- },
619
- {
620
- internalType: "address",
621
- name: "_taker",
622
- type: "address"
623
- }
624
- ],
625
- name: "isWhitelisted",
626
- outputs: [
627
- {
628
- internalType: "bool",
629
- name: "",
630
- type: "bool"
631
- }
632
- ],
633
- stateMutability: "view",
634
- type: "function"
635
- },
636
- {
637
- inputs: [],
638
- name: "orchestratorRegistry",
639
- outputs: [
640
- {
641
- internalType: "contract IOrchestratorRegistry",
642
- name: "",
643
- type: "address"
644
- }
645
- ],
646
- stateMutability: "view",
647
- type: "function"
648
- },
649
- {
650
- inputs: [
651
- {
652
- internalType: "address",
653
- name: "_escrow",
654
- type: "address"
655
- },
656
- {
657
- internalType: "uint256",
658
- name: "_depositId",
659
- type: "uint256"
660
- },
661
- {
662
- internalType: "address[]",
663
- name: "_takers",
664
- type: "address[]"
665
- }
666
- ],
667
- name: "removeFromWhitelist",
668
- outputs: [],
669
- stateMutability: "nonpayable",
670
- type: "function"
671
- },
672
- {
673
- inputs: [
674
- {
675
- components: [
676
- {
677
- internalType: "address",
678
- name: "taker",
679
- type: "address"
680
- },
681
- {
682
- internalType: "address",
683
- name: "escrow",
684
- type: "address"
685
- },
686
- {
687
- internalType: "uint256",
688
- name: "depositId",
689
- type: "uint256"
690
- },
691
- {
692
- internalType: "uint256",
693
- name: "amount",
694
- type: "uint256"
695
- },
696
- {
697
- internalType: "address",
698
- name: "to",
699
- type: "address"
700
- },
701
- {
702
- internalType: "bytes32",
703
- name: "paymentMethod",
704
- type: "bytes32"
705
- },
706
- {
707
- internalType: "bytes32",
708
- name: "fiatCurrency",
709
- type: "bytes32"
710
- },
711
- {
712
- internalType: "uint256",
713
- name: "conversionRate",
714
- type: "uint256"
715
- },
716
- {
717
- components: [
718
- {
719
- internalType: "address",
720
- name: "recipient",
721
- type: "address"
722
- },
723
- {
724
- internalType: "uint256",
725
- name: "fee",
726
- type: "uint256"
727
- }
728
- ],
729
- internalType: "struct IReferralFee.ReferralFee[]",
730
- name: "referralFees",
731
- type: "tuple[]"
732
- },
733
- {
734
- internalType: "bytes",
735
- name: "preIntentHookData",
736
- type: "bytes"
737
- }
738
- ],
739
- internalType: "struct IPreIntentHook.PreIntentContext",
740
- name: "_ctx",
741
- type: "tuple"
742
- }
743
- ],
744
- name: "validateSignalIntent",
745
- outputs: [],
746
- stateMutability: "view",
747
- type: "function"
748
- },
749
- {
750
- inputs: [
751
- {
752
- internalType: "address",
753
- name: "",
754
- type: "address"
755
- },
756
- {
757
- internalType: "uint256",
758
- name: "",
759
- type: "uint256"
760
- },
761
- {
762
- internalType: "address",
763
- name: "",
764
- type: "address"
765
- }
766
- ],
767
- name: "whitelist",
768
- outputs: [
769
- {
770
- internalType: "bool",
771
- name: "",
772
- type: "bool"
773
- }
774
- ],
775
- stateMutability: "view",
776
- type: "function"
777
- }
778
- ];
640
+ var import_WhitelistPreIntentHook = __toESM(require("@zkp2p/contracts-v2/abis/base/WhitelistPreIntentHook.json"), 1);
779
641
 
780
642
  // src/sdk-client.ts
781
643
  var import_sdk4 = require("@zkp2p/sdk");
782
- function createSdkClient(walletClient) {
644
+ function createSdkClient(walletClient, apiBaseUrl = API_BASE_URL) {
783
645
  return new import_sdk4.OfframpClient({
784
646
  walletClient,
785
647
  chainId: BASE_CHAIN_ID,
786
648
  runtimeEnv: RUNTIME_ENV,
787
649
  rpcUrl: BASE_RPC_URL,
788
- baseApiUrl: API_BASE_URL
650
+ baseApiUrl: apiBaseUrl
789
651
  });
790
652
  }
791
653
 
792
654
  // src/otc.ts
793
655
  var WHITELIST_HOOK_ADDRESS = "0xda023Ea0d789A41BcF5866F7B6BBd2CaDF9b79B8";
794
- var OTC_BASE_URL = "https://usdctofiat.xyz";
656
+ var OTC_BASE_URL = "https://otc.usdctofiat.xyz";
795
657
  async function enableOtc(walletClient, depositId, takerAddress, escrowAddress) {
796
658
  if (!(0, import_viem.isAddress)(takerAddress)) {
797
659
  throw new OfframpError("Invalid taker address", "VALIDATION");
@@ -842,7 +704,7 @@ async function enableOtc(walletClient, depositId, takerAddress, escrowAddress) {
842
704
  }
843
705
  function getOtcLink(depositId, escrowAddress) {
844
706
  const escrow = escrowAddress || ESCROW_ADDRESS;
845
- return `${OTC_BASE_URL}/deposit/${escrow}/${depositId}`;
707
+ return `${OTC_BASE_URL}/d/${escrow}/${depositId}`;
846
708
  }
847
709
  async function writeContract(walletClient, functionName, args) {
848
710
  const wc = walletClient;
@@ -851,7 +713,7 @@ async function writeContract(walletClient, functionName, args) {
851
713
  }
852
714
  await wc.writeContract({
853
715
  address: WHITELIST_HOOK_ADDRESS,
854
- abi: WhitelistPreIntentHook_default,
716
+ abi: import_WhitelistPreIntentHook.default,
855
717
  functionName,
856
718
  args
857
719
  });
@@ -860,7 +722,7 @@ async function readIsWhitelisted(escrow, depositId, taker) {
860
722
  const publicClient = (0, import_viem.createPublicClient)({ chain: import_chains.base, transport: (0, import_viem.http)(BASE_RPC_URL) });
861
723
  return publicClient.readContract({
862
724
  address: WHITELIST_HOOK_ADDRESS,
863
- abi: WhitelistPreIntentHook_default,
725
+ abi: import_WhitelistPreIntentHook.default,
864
726
  functionName: "isWhitelisted",
865
727
  args: [escrow, depositId, taker]
866
728
  });
@@ -1109,41 +971,24 @@ function extractDepositIdFromLogs(logs) {
1109
971
  }
1110
972
  return null;
1111
973
  }
1112
- async function registerPayeeDetails(processorName, payload) {
974
+ async function validatePayeeDetails(processorName, offchainId, apiBaseUrl = API_BASE_URL) {
1113
975
  const controller = new AbortController();
1114
976
  const timeout = setTimeout(() => controller.abort(), PAYEE_REGISTRATION_TIMEOUT_MS);
1115
977
  try {
1116
- const res = await fetch(`${API_BASE_URL}/v2/makers/create`, {
978
+ const res = await fetch(`${apiBaseUrl}/v2/makers/validate`, {
1117
979
  method: "POST",
1118
980
  headers: { "Content-Type": "application/json" },
1119
- body: JSON.stringify({ processorName, ...payload }),
981
+ body: JSON.stringify({ processorName, offchainId }),
1120
982
  signal: controller.signal
1121
983
  });
1122
- if (!res.ok) {
1123
- const txt = await res.text().catch(() => "");
1124
- let detail = txt || res.statusText;
1125
- try {
1126
- const parsed = JSON.parse(txt);
1127
- if (typeof parsed.message === "string" && parsed.message.trim()) {
1128
- detail = parsed.message;
1129
- }
1130
- } catch {
1131
- }
1132
- throw new MakersCreateError(
1133
- `makers/create failed (${res.status}): ${detail}`,
1134
- res.status,
1135
- txt
1136
- );
1137
- }
984
+ if (!res.ok) return "unknown";
1138
985
  const json = await res.json();
1139
- if (!json.success || !json.responseObject?.hashedOnchainId) {
1140
- throw new MakersCreateError(
1141
- json.message || "makers/create returned no hashedOnchainId",
1142
- json.statusCode ?? null,
1143
- ""
1144
- );
986
+ if (typeof json.responseObject === "boolean") {
987
+ return json.responseObject ? "valid" : "invalid";
1145
988
  }
1146
- return json.responseObject.hashedOnchainId;
989
+ return "unknown";
990
+ } catch {
991
+ return "unknown";
1147
992
  } finally {
1148
993
  clearTimeout(timeout);
1149
994
  }
@@ -1208,10 +1053,11 @@ async function findUndelegatedDeposit(walletAddress) {
1208
1053
  return null;
1209
1054
  }
1210
1055
  }
1211
- async function offramp(walletClient, params, onProgress, telemetryContext) {
1056
+ async function offramp(walletClient, params, onProgress, telemetryContext, apiBaseUrl) {
1212
1057
  const { amount, platform, currency, identifier } = params;
1213
1058
  const platformId = platform.id;
1214
1059
  const currencyCode = currency.code;
1060
+ const resolvedApiBaseUrl = resolveApiBaseUrl(apiBaseUrl);
1215
1061
  const telemetry = telemetryContext ?? createTelemetryContext({
1216
1062
  sdkVersion: SDK_VERSION,
1217
1063
  telemetry: true,
@@ -1256,7 +1102,7 @@ async function offramp(walletClient, params, onProgress, telemetryContext) {
1256
1102
  const existing = await findUndelegatedDeposit(walletAddress);
1257
1103
  if (existing) {
1258
1104
  emitProgress({ step: "resuming", depositId: existing.depositId });
1259
- const client2 = createSdkClient(walletClient);
1105
+ const client2 = createSdkClient(walletClient, resolvedApiBaseUrl);
1260
1106
  const txOverrides2 = { referrer: [REFERRER] };
1261
1107
  try {
1262
1108
  const result2 = await client2.setRateManager({
@@ -1292,7 +1138,7 @@ async function offramp(walletClient, params, onProgress, telemetryContext) {
1292
1138
  const truncatedAmount = amt.toFixed(6).replace(/\.?0+$/, "");
1293
1139
  const amountUnits = usdcToUnits(truncatedAmount);
1294
1140
  const minUnits = usdcToUnits(String(MIN_ORDER_USDC));
1295
- const maxUnits = usdcToUnits(String(Math.min(amt, 2500)));
1141
+ const maxUnits = usdcToUnits(String(Math.min(amt, MAX_ORDER_USDC)));
1296
1142
  try {
1297
1143
  const publicClient = (0, import_viem3.createPublicClient)({ chain: import_chains3.base, transport: (0, import_viem3.http)(BASE_RPC_URL) });
1298
1144
  const balance = await publicClient.readContract({
@@ -1319,7 +1165,22 @@ async function offramp(walletClient, params, onProgress, telemetryContext) {
1319
1165
  } catch (err) {
1320
1166
  if (err instanceof OfframpError) throw err;
1321
1167
  }
1322
- const client = createSdkClient(walletClient);
1168
+ const canonicalName = platformId.startsWith("zelle") ? "zelle" : platformId;
1169
+ if (!getPeerExtensionRegistrationInfo(platformId)) {
1170
+ const outcome = await validatePayeeDetails(
1171
+ canonicalName,
1172
+ normalizedIdentifier,
1173
+ resolvedApiBaseUrl
1174
+ );
1175
+ if (outcome === "invalid") {
1176
+ throw new OfframpError(
1177
+ `Couldn't verify that ${platform.name} ${platform.identifier.label.toLowerCase()}. Double-check it and try again.`,
1178
+ "VALIDATION",
1179
+ "registering"
1180
+ );
1181
+ }
1182
+ }
1183
+ const client = createSdkClient(walletClient, resolvedApiBaseUrl);
1323
1184
  const txOverrides = { referrer: [REFERRER] };
1324
1185
  emitProgress({ step: "approving" });
1325
1186
  try {
@@ -1343,16 +1204,15 @@ async function offramp(walletClient, params, onProgress, telemetryContext) {
1343
1204
  }
1344
1205
  emitProgress({ step: "registering" });
1345
1206
  let hashedOnchainId;
1346
- const canonicalName = platformId.startsWith("zelle") ? "zelle" : platformId;
1347
1207
  try {
1348
1208
  const payeePayload = buildDepositData(canonicalName, normalizedIdentifier);
1349
- hashedOnchainId = await registerPayeeDetails(canonicalName, payeePayload);
1209
+ hashedOnchainId = await postMakerCreate(canonicalName, payeePayload, resolvedApiBaseUrl);
1350
1210
  } catch (err) {
1351
1211
  const status = err instanceof MakersCreateError ? err.status : null;
1352
1212
  const message = err instanceof Error ? err.message : String(err);
1353
1213
  if (isPeerExtensionRegistrationError(canonicalName, message, status)) {
1354
1214
  throw new OfframpError(
1355
- `${platform.name} maker is not yet registered in the Peer extension. Verify this handle in the extension, then retry.`,
1215
+ `${platform.name} maker is not yet registered in the Peer extension. Register this handle with Peer, then retry.`,
1356
1216
  "EXTENSION_REGISTRATION_REQUIRED",
1357
1217
  "registering",
1358
1218
  err
@@ -1526,6 +1386,14 @@ async function offramp(walletClient, params, onProgress, telemetryContext) {
1526
1386
  // src/offramp.ts
1527
1387
  var IDEMPOTENCY_TTL_MS = 10 * 60 * 1e3;
1528
1388
  var IDEMPOTENCY_STORAGE_PREFIX = "offramp:idempotency:";
1389
+ var warnedIdempotencyUnavailable = false;
1390
+ function warnIdempotencyUnavailableOnce() {
1391
+ if (warnedIdempotencyUnavailable) return;
1392
+ warnedIdempotencyUnavailable = true;
1393
+ console.warn(
1394
+ "[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."
1395
+ );
1396
+ }
1529
1397
  function getWalletAddress(walletClient) {
1530
1398
  const address = walletClient.account?.address;
1531
1399
  if (!address) {
@@ -1596,8 +1464,10 @@ function buildCurrencyGroups() {
1596
1464
  var Offramp = class {
1597
1465
  walletClient;
1598
1466
  telemetry;
1467
+ apiBaseUrl;
1599
1468
  constructor(options) {
1600
1469
  this.walletClient = options.walletClient;
1470
+ this.apiBaseUrl = options.apiBaseUrl;
1601
1471
  this.telemetry = createTelemetryContext({
1602
1472
  sdkVersion: SDK_VERSION,
1603
1473
  telemetry: options.telemetry,
@@ -1620,6 +1490,9 @@ var Offramp = class {
1620
1490
  referralId: flowTelemetry.referralId
1621
1491
  };
1622
1492
  const sanitizedKey = sanitizeAttributionId(params.idempotencyKey);
1493
+ if (sanitizedKey && typeof sessionStorage === "undefined") {
1494
+ warnIdempotencyUnavailableOnce();
1495
+ }
1623
1496
  const walletAddress = sanitizedKey ? getWalletAddress(this.walletClient) : null;
1624
1497
  const storageKey = walletAddress && sanitizedKey ? getIdempotencyStorageKey(walletAddress, sanitizedKey) : null;
1625
1498
  if (storageKey) {
@@ -1630,7 +1503,8 @@ var Offramp = class {
1630
1503
  this.walletClient,
1631
1504
  sanitizedParams,
1632
1505
  onProgress,
1633
- flowTelemetry
1506
+ flowTelemetry,
1507
+ this.apiBaseUrl
1634
1508
  );
1635
1509
  if (storageKey) {
1636
1510
  writeIdempotencyResult(storageKey, result);
@@ -1761,25 +1635,108 @@ var import_react2 = require("react");
1761
1635
 
1762
1636
  // src/extension.ts
1763
1637
  var import_sdk7 = require("@zkp2p/sdk");
1638
+ var resolveWindow = (options) => {
1639
+ if (options?.window) {
1640
+ return options.window;
1641
+ }
1642
+ if (typeof window === "undefined") {
1643
+ return void 0;
1644
+ }
1645
+ return window;
1646
+ };
1647
+ var requirePeer = (options) => {
1648
+ const resolvedWindow = resolveWindow(options);
1649
+ if (!resolvedWindow) {
1650
+ throw new Error("Peer extension SDK requires a browser window.");
1651
+ }
1652
+ if (!resolvedWindow.peer) {
1653
+ throw new Error("Peer extension not available. Install or enable the Peer extension.");
1654
+ }
1655
+ return resolvedWindow.peer;
1656
+ };
1657
+ var isPeerExtensionAvailable = (options) => {
1658
+ const resolvedWindow = resolveWindow(options);
1659
+ return Boolean(resolvedWindow?.peer);
1660
+ };
1661
+ var hasHeadlessMetadataBridge = (peer) => typeof peer.authenticate === "function" && typeof peer.onMetadataMessage === "function";
1662
+ var requirePeerMetadataBridge = (options) => {
1663
+ const peer = requirePeer(options);
1664
+ if (!hasHeadlessMetadataBridge(peer)) {
1665
+ throw new Error("Peer extension metadata bridge unavailable. Install or update Peer.");
1666
+ }
1667
+ return peer;
1668
+ };
1669
+ var isPeerExtensionMetadataBridgeAvailable = (options) => {
1670
+ const resolvedWindow = resolveWindow(options);
1671
+ return Boolean(resolvedWindow?.peer && hasHeadlessMetadataBridge(resolvedWindow.peer));
1672
+ };
1673
+ var openPeerExtensionInstallPage = (options) => {
1674
+ const resolvedWindow = resolveWindow(options);
1675
+ if (!resolvedWindow) {
1676
+ throw new Error("Peer extension SDK requires a browser window.");
1677
+ }
1678
+ resolvedWindow.open(import_sdk7.PEER_EXTENSION_CHROME_URL, "_blank", "noopener,noreferrer");
1679
+ };
1680
+ var getPeerExtensionState = async (options) => {
1681
+ if (!isPeerExtensionAvailable(options)) {
1682
+ return "needs_install";
1683
+ }
1684
+ try {
1685
+ const status = await requirePeer(options).checkConnectionStatus();
1686
+ return status === "connected" ? "ready" : "needs_connection";
1687
+ } catch {
1688
+ return "needs_connection";
1689
+ }
1690
+ };
1691
+ var createPeerExtensionSdk = (options = {}) => {
1692
+ const legacySdk = (0, import_sdk7.createPeerExtensionSdk)(
1693
+ options
1694
+ );
1695
+ return {
1696
+ isAvailable: () => isPeerExtensionAvailable(options),
1697
+ requestConnection: () => requirePeer(options).requestConnection(),
1698
+ checkConnectionStatus: () => requirePeer(options).checkConnectionStatus(),
1699
+ getVersion: () => requirePeer(options).getVersion(),
1700
+ authenticate: (params) => requirePeerMetadataBridge(options).authenticate(params),
1701
+ onMetadataMessage: (callback) => requirePeerMetadataBridge(options).onMetadataMessage(callback),
1702
+ openSidebar: (route) => legacySdk.openSidebar(route),
1703
+ onramp: (params, callback) => legacySdk.onramp(params, callback),
1704
+ getOnrampTransaction: (intentHash) => legacySdk.getOnrampTransaction(intentHash),
1705
+ openInstallPage: () => openPeerExtensionInstallPage(options),
1706
+ getState: () => getPeerExtensionState(options)
1707
+ };
1708
+ };
1709
+ var peerExtensionSdk = createPeerExtensionSdk();
1764
1710
 
1765
1711
  // src/hooks/usePeerExtensionRegistration.ts
1766
1712
  function usePeerExtensionRegistration(platform) {
1767
- const info = platform ? getPeerExtensionRegistrationInfo(platform.id) : null;
1713
+ const platformId = platform?.id ?? null;
1714
+ const info = platformId ? getPeerExtensionRegistrationInfo(platformId) : null;
1715
+ const authParams = (0, import_react2.useMemo)(
1716
+ () => platformId ? getPeerExtensionRegistrationAuthParams(platformId) : null,
1717
+ [platformId]
1718
+ );
1768
1719
  const [phase, setPhase] = (0, import_react2.useState)(
1769
1720
  info ? "checking" : "unsupported"
1770
1721
  );
1771
1722
  const [busy, setBusy] = (0, import_react2.useState)(false);
1772
1723
  const [error, setError] = (0, import_react2.useState)(null);
1724
+ const [capturedMetadata, setCapturedMetadata] = (0, import_react2.useState)(null);
1773
1725
  const mountedRef = (0, import_react2.useRef)(true);
1774
1726
  const providerId = info?.providerId ?? null;
1727
+ const metadataPlatform = authParams?.platform ?? null;
1775
1728
  const refresh = (0, import_react2.useCallback)(async () => {
1776
1729
  if (!providerId) {
1777
1730
  setPhase("unsupported");
1778
1731
  return;
1779
1732
  }
1780
1733
  try {
1781
- const state = await import_sdk7.peerExtensionSdk.getState();
1734
+ const state = await peerExtensionSdk.getState();
1782
1735
  if (!mountedRef.current) return;
1736
+ if (state === "ready" && !isPeerExtensionMetadataBridgeAvailable()) {
1737
+ setPhase("needs_install");
1738
+ return;
1739
+ }
1783
1740
  setPhase(mapSdkState(state));
1784
1741
  } catch {
1785
1742
  if (!mountedRef.current) return;
@@ -1790,6 +1747,7 @@ function usePeerExtensionRegistration(platform) {
1790
1747
  mountedRef.current = true;
1791
1748
  setError(null);
1792
1749
  setBusy(false);
1750
+ setCapturedMetadata(null);
1793
1751
  if (!providerId) {
1794
1752
  setPhase("unsupported");
1795
1753
  } else {
@@ -1800,10 +1758,25 @@ function usePeerExtensionRegistration(platform) {
1800
1758
  mountedRef.current = false;
1801
1759
  };
1802
1760
  }, [providerId, refresh]);
1761
+ (0, import_react2.useEffect)(() => {
1762
+ setCapturedMetadata(null);
1763
+ if (!metadataPlatform) return;
1764
+ try {
1765
+ return peerExtensionSdk.onMetadataMessage((message) => {
1766
+ if (!mountedRef.current || message.platform !== metadataPlatform) return;
1767
+ setCapturedMetadata(message);
1768
+ if (message.errorMessage) {
1769
+ setError(message.errorMessage);
1770
+ }
1771
+ });
1772
+ } catch {
1773
+ return;
1774
+ }
1775
+ }, [metadataPlatform, phase]);
1803
1776
  const installExtension = (0, import_react2.useCallback)(() => {
1804
1777
  setError(null);
1805
1778
  try {
1806
- import_sdk7.peerExtensionSdk.openInstallPage();
1779
+ peerExtensionSdk.openInstallPage();
1807
1780
  } catch {
1808
1781
  if (typeof window !== "undefined") {
1809
1782
  window.open(import_sdk7.PEER_EXTENSION_CHROME_URL, "_blank", "noopener,noreferrer");
@@ -1811,15 +1784,14 @@ function usePeerExtensionRegistration(platform) {
1811
1784
  }
1812
1785
  }, []);
1813
1786
  const connectExtension = (0, import_react2.useCallback)(async () => {
1814
- if (!providerId) return false;
1787
+ if (!authParams) return false;
1815
1788
  setError(null);
1816
1789
  setBusy(true);
1817
1790
  try {
1818
- const approved = await import_sdk7.peerExtensionSdk.requestConnection();
1791
+ const approved = await peerExtensionSdk.requestConnection();
1819
1792
  if (!mountedRef.current) return approved;
1820
1793
  if (approved) {
1821
- setPhase("ready");
1822
- import_sdk7.peerExtensionSdk.openSidebar(`/verify/${providerId}`);
1794
+ setPhase(isPeerExtensionMetadataBridgeAvailable() ? "ready" : "needs_install");
1823
1795
  } else {
1824
1796
  setError("Connection was not approved. Try again once you've approved Peer.");
1825
1797
  }
@@ -1834,40 +1806,89 @@ function usePeerExtensionRegistration(platform) {
1834
1806
  } finally {
1835
1807
  if (mountedRef.current) setBusy(false);
1836
1808
  }
1837
- }, [providerId, refresh]);
1838
- const openVerifySidebar = (0, import_react2.useCallback)(async () => {
1839
- if (!providerId) return;
1809
+ }, [authParams, refresh]);
1810
+ const startRegistrationCapture = (0, import_react2.useCallback)(async () => {
1811
+ if (!authParams) return;
1840
1812
  setError(null);
1841
- const state = await import_sdk7.peerExtensionSdk.getState().catch(() => "needs_install");
1842
- if (!mountedRef.current) return;
1843
- const nextPhase = mapSdkState(state);
1844
- setPhase(nextPhase);
1845
- if (nextPhase === "needs_install") {
1846
- installExtension();
1847
- return;
1848
- }
1849
- if (nextPhase === "needs_connection") {
1850
- await connectExtension();
1851
- return;
1852
- }
1813
+ setBusy(true);
1853
1814
  try {
1854
- import_sdk7.peerExtensionSdk.openSidebar(`/verify/${providerId}`);
1815
+ const state = await peerExtensionSdk.getState().catch(() => "needs_install");
1816
+ if (!mountedRef.current) return;
1817
+ const nextPhase = state === "ready" && !isPeerExtensionMetadataBridgeAvailable() ? "needs_install" : mapSdkState(state);
1818
+ setPhase(nextPhase);
1819
+ if (nextPhase === "needs_install") {
1820
+ installExtension();
1821
+ return;
1822
+ }
1823
+ if (nextPhase === "needs_connection") {
1824
+ const approved = await peerExtensionSdk.requestConnection();
1825
+ if (!mountedRef.current) return;
1826
+ if (!approved) {
1827
+ setError("Connection was not approved. Try again once you've approved Peer.");
1828
+ return;
1829
+ }
1830
+ if (!isPeerExtensionMetadataBridgeAvailable()) {
1831
+ setPhase("needs_install");
1832
+ installExtension();
1833
+ return;
1834
+ }
1835
+ setPhase("ready");
1836
+ }
1837
+ setCapturedMetadata(null);
1838
+ peerExtensionSdk.authenticate(authParams);
1855
1839
  } catch (err) {
1856
1840
  setError(
1857
- err instanceof Error ? err.message : "Couldn't open the Peer extension. Make sure it's enabled."
1841
+ err instanceof Error ? err.message : "Couldn't start Peer extension registration. Make sure Peer is enabled."
1858
1842
  );
1859
1843
  refresh();
1844
+ } finally {
1845
+ if (mountedRef.current) setBusy(false);
1860
1846
  }
1861
- }, [connectExtension, installExtension, providerId, refresh]);
1847
+ }, [authParams, installExtension, refresh]);
1848
+ const openVerifySidebar = (0, import_react2.useCallback)(
1849
+ async () => startRegistrationCapture(),
1850
+ [startRegistrationCapture]
1851
+ );
1852
+ const completeRegistration = (0, import_react2.useCallback)(
1853
+ async (walletClient, params, options = {}) => {
1854
+ const metadata = options.capturedMetadata ?? capturedMetadata;
1855
+ if (!metadata) {
1856
+ const message = "No Peer metadata captured yet. Start registration first.";
1857
+ setError(message);
1858
+ throw new Error(message);
1859
+ }
1860
+ setError(null);
1861
+ setBusy(true);
1862
+ try {
1863
+ await completePeerExtensionRegistration({
1864
+ platform: params.platform,
1865
+ identifier: params.identifier,
1866
+ capturedMetadata: metadata
1867
+ });
1868
+ return await offramp(walletClient, params, options.onProgress);
1869
+ } catch (err) {
1870
+ setError(
1871
+ err instanceof Error ? err.message : "Couldn't complete Peer extension registration. Try capturing metadata again."
1872
+ );
1873
+ throw err;
1874
+ } finally {
1875
+ if (mountedRef.current) setBusy(false);
1876
+ }
1877
+ },
1878
+ [capturedMetadata]
1879
+ );
1862
1880
  return {
1863
1881
  phase,
1864
1882
  info,
1865
1883
  busy,
1866
1884
  error,
1885
+ capturedMetadata,
1867
1886
  refresh,
1868
1887
  installExtension,
1869
1888
  connectExtension,
1870
- openVerifySidebar
1889
+ startRegistrationCapture,
1890
+ openVerifySidebar,
1891
+ completeRegistration
1871
1892
  };
1872
1893
  }
1873
1894
  function mapSdkState(state) {