@usdctofiat/offramp 3.0.3 → 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
@@ -40,11 +40,15 @@ var import_react = require("react");
40
40
 
41
41
  // src/config.ts
42
42
  var import_sdk = require("@zkp2p/sdk");
43
- var SDK_VERSION = "3.0.3";
43
+ var SDK_VERSION = "3.1.0";
44
44
  var BASE_CHAIN_ID = 8453;
45
45
  var RUNTIME_ENV = "production";
46
46
  var API_BASE_URL = "https://api.zkp2p.xyz";
47
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
+ }
48
52
  var contracts = (0, import_sdk.getContracts)(BASE_CHAIN_ID, RUNTIME_ENV);
49
53
  var addresses = contracts.addresses;
50
54
  var addrs = addresses;
@@ -61,6 +65,7 @@ var DEFAULT_INTENT_GUARDIAN_ADDRESS = "0xe29a5BD4D0CEbA6C125A0361E7D20ab4eA275C2
61
65
  var REFERRER = "galleonlabs";
62
66
  var MIN_DEPOSIT_USDC = 1;
63
67
  var MIN_ORDER_USDC = 1;
68
+ var MAX_ORDER_USDC = 2500;
64
69
  var INDEXER_MAX_ATTEMPTS = 12;
65
70
  var INDEXER_INITIAL_DELAY_MS = 1e3;
66
71
  var INDEXER_MAX_DELAY_MS = 1e4;
@@ -92,6 +97,41 @@ var import_sdk6 = require("@zkp2p/sdk");
92
97
  // src/platforms.ts
93
98
  var import_sdk3 = require("@zkp2p/sdk");
94
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
95
135
  var PAYMENT_CATALOG = (0, import_sdk3.getPaymentMethodsCatalog)(BASE_CHAIN_ID, RUNTIME_ENV);
96
136
  var ZELLE_HASH_LOOKUP_NAMES = ["zelle", "zelle-bofa", "zelle-chase", "zelle-citi"];
97
137
  var FALLBACK_CURRENCIES = {
@@ -443,6 +483,146 @@ function getPeerExtensionRegistrationInfo(platform) {
443
483
  const entry = getPlatformById(platform);
444
484
  return entry?.extensionRegistration ?? null;
445
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
+ }
446
626
  var EXTENSION_REGISTRATION_ERROR_PATTERNS = ["creating the maker", "create the maker"];
447
627
  function isPeerExtensionRegistrationError(platform, message, statusCode) {
448
628
  const info = getPeerExtensionRegistrationInfo(platform);
@@ -454,39 +634,6 @@ function isPeerExtensionRegistrationError(platform, message, statusCode) {
454
634
  return EXTENSION_REGISTRATION_ERROR_PATTERNS.some((p) => lower.includes(p));
455
635
  }
456
636
 
457
- // src/errors.ts
458
- var MakersCreateError = class extends Error {
459
- status;
460
- body;
461
- constructor(message, status, body) {
462
- super(message);
463
- this.name = "MakersCreateError";
464
- this.status = status;
465
- this.body = body;
466
- }
467
- };
468
- var OfframpError = class extends Error {
469
- code;
470
- step;
471
- cause;
472
- txHash;
473
- depositId;
474
- constructor(message, code, step, cause, details) {
475
- super(message);
476
- this.name = "OfframpError";
477
- this.code = code;
478
- this.step = step;
479
- this.cause = cause;
480
- this.txHash = details?.txHash;
481
- this.depositId = details?.depositId;
482
- }
483
- };
484
- function isUserCancellation(error) {
485
- if (!(error instanceof Error)) return false;
486
- const msg = error.message.toLowerCase();
487
- return msg.includes("user rejected") || msg.includes("user denied") || msg.includes("user cancelled") || msg.includes("rejected the request") || msg.includes("action_rejected");
488
- }
489
-
490
637
  // src/otc.ts
491
638
  var import_viem = require("viem");
492
639
  var import_chains = require("viem/chains");
@@ -494,13 +641,13 @@ var import_WhitelistPreIntentHook = __toESM(require("@zkp2p/contracts-v2/abis/ba
494
641
 
495
642
  // src/sdk-client.ts
496
643
  var import_sdk4 = require("@zkp2p/sdk");
497
- function createSdkClient(walletClient) {
644
+ function createSdkClient(walletClient, apiBaseUrl = API_BASE_URL) {
498
645
  return new import_sdk4.OfframpClient({
499
646
  walletClient,
500
647
  chainId: BASE_CHAIN_ID,
501
648
  runtimeEnv: RUNTIME_ENV,
502
649
  rpcUrl: BASE_RPC_URL,
503
- baseApiUrl: API_BASE_URL
650
+ baseApiUrl: apiBaseUrl
504
651
  });
505
652
  }
506
653
 
@@ -824,50 +971,11 @@ function extractDepositIdFromLogs(logs) {
824
971
  }
825
972
  return null;
826
973
  }
827
- async function registerPayeeDetails(processorName, payload) {
828
- const controller = new AbortController();
829
- const timeout = setTimeout(() => controller.abort(), PAYEE_REGISTRATION_TIMEOUT_MS);
830
- try {
831
- const res = await fetch(`${API_BASE_URL}/v2/makers/create`, {
832
- method: "POST",
833
- headers: { "Content-Type": "application/json" },
834
- body: JSON.stringify({ processorName, ...payload }),
835
- signal: controller.signal
836
- });
837
- if (!res.ok) {
838
- const txt = await res.text().catch(() => "");
839
- let detail = txt || res.statusText;
840
- try {
841
- const parsed = JSON.parse(txt);
842
- if (typeof parsed.message === "string" && parsed.message.trim()) {
843
- detail = parsed.message;
844
- }
845
- } catch {
846
- }
847
- throw new MakersCreateError(
848
- `makers/create failed (${res.status}): ${detail}`,
849
- res.status,
850
- txt
851
- );
852
- }
853
- const json = await res.json();
854
- if (!json.success || !json.responseObject?.hashedOnchainId) {
855
- throw new MakersCreateError(
856
- json.message || "makers/create returned no hashedOnchainId",
857
- json.statusCode ?? null,
858
- ""
859
- );
860
- }
861
- return json.responseObject.hashedOnchainId;
862
- } finally {
863
- clearTimeout(timeout);
864
- }
865
- }
866
- async function validatePayeeDetails(processorName, offchainId) {
974
+ async function validatePayeeDetails(processorName, offchainId, apiBaseUrl = API_BASE_URL) {
867
975
  const controller = new AbortController();
868
976
  const timeout = setTimeout(() => controller.abort(), PAYEE_REGISTRATION_TIMEOUT_MS);
869
977
  try {
870
- const res = await fetch(`${API_BASE_URL}/v2/makers/validate`, {
978
+ const res = await fetch(`${apiBaseUrl}/v2/makers/validate`, {
871
979
  method: "POST",
872
980
  headers: { "Content-Type": "application/json" },
873
981
  body: JSON.stringify({ processorName, offchainId }),
@@ -945,10 +1053,11 @@ async function findUndelegatedDeposit(walletAddress) {
945
1053
  return null;
946
1054
  }
947
1055
  }
948
- async function offramp(walletClient, params, onProgress, telemetryContext) {
1056
+ async function offramp(walletClient, params, onProgress, telemetryContext, apiBaseUrl) {
949
1057
  const { amount, platform, currency, identifier } = params;
950
1058
  const platformId = platform.id;
951
1059
  const currencyCode = currency.code;
1060
+ const resolvedApiBaseUrl = resolveApiBaseUrl(apiBaseUrl);
952
1061
  const telemetry = telemetryContext ?? createTelemetryContext({
953
1062
  sdkVersion: SDK_VERSION,
954
1063
  telemetry: true,
@@ -993,7 +1102,7 @@ async function offramp(walletClient, params, onProgress, telemetryContext) {
993
1102
  const existing = await findUndelegatedDeposit(walletAddress);
994
1103
  if (existing) {
995
1104
  emitProgress({ step: "resuming", depositId: existing.depositId });
996
- const client2 = createSdkClient(walletClient);
1105
+ const client2 = createSdkClient(walletClient, resolvedApiBaseUrl);
997
1106
  const txOverrides2 = { referrer: [REFERRER] };
998
1107
  try {
999
1108
  const result2 = await client2.setRateManager({
@@ -1029,7 +1138,7 @@ async function offramp(walletClient, params, onProgress, telemetryContext) {
1029
1138
  const truncatedAmount = amt.toFixed(6).replace(/\.?0+$/, "");
1030
1139
  const amountUnits = usdcToUnits(truncatedAmount);
1031
1140
  const minUnits = usdcToUnits(String(MIN_ORDER_USDC));
1032
- const maxUnits = usdcToUnits(String(Math.min(amt, 2500)));
1141
+ const maxUnits = usdcToUnits(String(Math.min(amt, MAX_ORDER_USDC)));
1033
1142
  try {
1034
1143
  const publicClient = (0, import_viem3.createPublicClient)({ chain: import_chains3.base, transport: (0, import_viem3.http)(BASE_RPC_URL) });
1035
1144
  const balance = await publicClient.readContract({
@@ -1058,7 +1167,11 @@ async function offramp(walletClient, params, onProgress, telemetryContext) {
1058
1167
  }
1059
1168
  const canonicalName = platformId.startsWith("zelle") ? "zelle" : platformId;
1060
1169
  if (!getPeerExtensionRegistrationInfo(platformId)) {
1061
- const outcome = await validatePayeeDetails(canonicalName, normalizedIdentifier);
1170
+ const outcome = await validatePayeeDetails(
1171
+ canonicalName,
1172
+ normalizedIdentifier,
1173
+ resolvedApiBaseUrl
1174
+ );
1062
1175
  if (outcome === "invalid") {
1063
1176
  throw new OfframpError(
1064
1177
  `Couldn't verify that ${platform.name} ${platform.identifier.label.toLowerCase()}. Double-check it and try again.`,
@@ -1067,7 +1180,7 @@ async function offramp(walletClient, params, onProgress, telemetryContext) {
1067
1180
  );
1068
1181
  }
1069
1182
  }
1070
- const client = createSdkClient(walletClient);
1183
+ const client = createSdkClient(walletClient, resolvedApiBaseUrl);
1071
1184
  const txOverrides = { referrer: [REFERRER] };
1072
1185
  emitProgress({ step: "approving" });
1073
1186
  try {
@@ -1093,13 +1206,13 @@ async function offramp(walletClient, params, onProgress, telemetryContext) {
1093
1206
  let hashedOnchainId;
1094
1207
  try {
1095
1208
  const payeePayload = buildDepositData(canonicalName, normalizedIdentifier);
1096
- hashedOnchainId = await registerPayeeDetails(canonicalName, payeePayload);
1209
+ hashedOnchainId = await postMakerCreate(canonicalName, payeePayload, resolvedApiBaseUrl);
1097
1210
  } catch (err) {
1098
1211
  const status = err instanceof MakersCreateError ? err.status : null;
1099
1212
  const message = err instanceof Error ? err.message : String(err);
1100
1213
  if (isPeerExtensionRegistrationError(canonicalName, message, status)) {
1101
1214
  throw new OfframpError(
1102
- `${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.`,
1103
1216
  "EXTENSION_REGISTRATION_REQUIRED",
1104
1217
  "registering",
1105
1218
  err
@@ -1273,6 +1386,14 @@ async function offramp(walletClient, params, onProgress, telemetryContext) {
1273
1386
  // src/offramp.ts
1274
1387
  var IDEMPOTENCY_TTL_MS = 10 * 60 * 1e3;
1275
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
+ }
1276
1397
  function getWalletAddress(walletClient) {
1277
1398
  const address = walletClient.account?.address;
1278
1399
  if (!address) {
@@ -1343,8 +1464,10 @@ function buildCurrencyGroups() {
1343
1464
  var Offramp = class {
1344
1465
  walletClient;
1345
1466
  telemetry;
1467
+ apiBaseUrl;
1346
1468
  constructor(options) {
1347
1469
  this.walletClient = options.walletClient;
1470
+ this.apiBaseUrl = options.apiBaseUrl;
1348
1471
  this.telemetry = createTelemetryContext({
1349
1472
  sdkVersion: SDK_VERSION,
1350
1473
  telemetry: options.telemetry,
@@ -1367,6 +1490,9 @@ var Offramp = class {
1367
1490
  referralId: flowTelemetry.referralId
1368
1491
  };
1369
1492
  const sanitizedKey = sanitizeAttributionId(params.idempotencyKey);
1493
+ if (sanitizedKey && typeof sessionStorage === "undefined") {
1494
+ warnIdempotencyUnavailableOnce();
1495
+ }
1370
1496
  const walletAddress = sanitizedKey ? getWalletAddress(this.walletClient) : null;
1371
1497
  const storageKey = walletAddress && sanitizedKey ? getIdempotencyStorageKey(walletAddress, sanitizedKey) : null;
1372
1498
  if (storageKey) {
@@ -1377,7 +1503,8 @@ var Offramp = class {
1377
1503
  this.walletClient,
1378
1504
  sanitizedParams,
1379
1505
  onProgress,
1380
- flowTelemetry
1506
+ flowTelemetry,
1507
+ this.apiBaseUrl
1381
1508
  );
1382
1509
  if (storageKey) {
1383
1510
  writeIdempotencyResult(storageKey, result);
@@ -1508,25 +1635,108 @@ var import_react2 = require("react");
1508
1635
 
1509
1636
  // src/extension.ts
1510
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();
1511
1710
 
1512
1711
  // src/hooks/usePeerExtensionRegistration.ts
1513
1712
  function usePeerExtensionRegistration(platform) {
1514
- 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
+ );
1515
1719
  const [phase, setPhase] = (0, import_react2.useState)(
1516
1720
  info ? "checking" : "unsupported"
1517
1721
  );
1518
1722
  const [busy, setBusy] = (0, import_react2.useState)(false);
1519
1723
  const [error, setError] = (0, import_react2.useState)(null);
1724
+ const [capturedMetadata, setCapturedMetadata] = (0, import_react2.useState)(null);
1520
1725
  const mountedRef = (0, import_react2.useRef)(true);
1521
1726
  const providerId = info?.providerId ?? null;
1727
+ const metadataPlatform = authParams?.platform ?? null;
1522
1728
  const refresh = (0, import_react2.useCallback)(async () => {
1523
1729
  if (!providerId) {
1524
1730
  setPhase("unsupported");
1525
1731
  return;
1526
1732
  }
1527
1733
  try {
1528
- const state = await import_sdk7.peerExtensionSdk.getState();
1734
+ const state = await peerExtensionSdk.getState();
1529
1735
  if (!mountedRef.current) return;
1736
+ if (state === "ready" && !isPeerExtensionMetadataBridgeAvailable()) {
1737
+ setPhase("needs_install");
1738
+ return;
1739
+ }
1530
1740
  setPhase(mapSdkState(state));
1531
1741
  } catch {
1532
1742
  if (!mountedRef.current) return;
@@ -1537,6 +1747,7 @@ function usePeerExtensionRegistration(platform) {
1537
1747
  mountedRef.current = true;
1538
1748
  setError(null);
1539
1749
  setBusy(false);
1750
+ setCapturedMetadata(null);
1540
1751
  if (!providerId) {
1541
1752
  setPhase("unsupported");
1542
1753
  } else {
@@ -1547,10 +1758,25 @@ function usePeerExtensionRegistration(platform) {
1547
1758
  mountedRef.current = false;
1548
1759
  };
1549
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]);
1550
1776
  const installExtension = (0, import_react2.useCallback)(() => {
1551
1777
  setError(null);
1552
1778
  try {
1553
- import_sdk7.peerExtensionSdk.openInstallPage();
1779
+ peerExtensionSdk.openInstallPage();
1554
1780
  } catch {
1555
1781
  if (typeof window !== "undefined") {
1556
1782
  window.open(import_sdk7.PEER_EXTENSION_CHROME_URL, "_blank", "noopener,noreferrer");
@@ -1558,15 +1784,14 @@ function usePeerExtensionRegistration(platform) {
1558
1784
  }
1559
1785
  }, []);
1560
1786
  const connectExtension = (0, import_react2.useCallback)(async () => {
1561
- if (!providerId) return false;
1787
+ if (!authParams) return false;
1562
1788
  setError(null);
1563
1789
  setBusy(true);
1564
1790
  try {
1565
- const approved = await import_sdk7.peerExtensionSdk.requestConnection();
1791
+ const approved = await peerExtensionSdk.requestConnection();
1566
1792
  if (!mountedRef.current) return approved;
1567
1793
  if (approved) {
1568
- setPhase("ready");
1569
- import_sdk7.peerExtensionSdk.openSidebar(`/verify/${providerId}`);
1794
+ setPhase(isPeerExtensionMetadataBridgeAvailable() ? "ready" : "needs_install");
1570
1795
  } else {
1571
1796
  setError("Connection was not approved. Try again once you've approved Peer.");
1572
1797
  }
@@ -1581,40 +1806,89 @@ function usePeerExtensionRegistration(platform) {
1581
1806
  } finally {
1582
1807
  if (mountedRef.current) setBusy(false);
1583
1808
  }
1584
- }, [providerId, refresh]);
1585
- const openVerifySidebar = (0, import_react2.useCallback)(async () => {
1586
- if (!providerId) return;
1809
+ }, [authParams, refresh]);
1810
+ const startRegistrationCapture = (0, import_react2.useCallback)(async () => {
1811
+ if (!authParams) return;
1587
1812
  setError(null);
1588
- const state = await import_sdk7.peerExtensionSdk.getState().catch(() => "needs_install");
1589
- if (!mountedRef.current) return;
1590
- const nextPhase = mapSdkState(state);
1591
- setPhase(nextPhase);
1592
- if (nextPhase === "needs_install") {
1593
- installExtension();
1594
- return;
1595
- }
1596
- if (nextPhase === "needs_connection") {
1597
- await connectExtension();
1598
- return;
1599
- }
1813
+ setBusy(true);
1600
1814
  try {
1601
- 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);
1602
1839
  } catch (err) {
1603
1840
  setError(
1604
- 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."
1605
1842
  );
1606
1843
  refresh();
1844
+ } finally {
1845
+ if (mountedRef.current) setBusy(false);
1607
1846
  }
1608
- }, [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
+ );
1609
1880
  return {
1610
1881
  phase,
1611
1882
  info,
1612
1883
  busy,
1613
1884
  error,
1885
+ capturedMetadata,
1614
1886
  refresh,
1615
1887
  installExtension,
1616
1888
  connectExtension,
1617
- openVerifySidebar
1889
+ startRegistrationCapture,
1890
+ openVerifySidebar,
1891
+ completeRegistration
1618
1892
  };
1619
1893
  }
1620
1894
  function mapSdkState(state) {