@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/CHANGELOG.md +5 -0
- package/README.md +128 -23
- package/dist/{chunk-4KKKDDES.js → chunk-XQ7HOLLB.js} +292 -96
- package/dist/chunk-XQ7HOLLB.js.map +1 -0
- package/dist/{errors-D9bV_DWE.d.cts → errors-DzTiflBh.d.cts} +161 -6
- package/dist/{errors-D9bV_DWE.d.ts → errors-DzTiflBh.d.ts} +161 -6
- package/dist/index.cjs +297 -92
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -5
- package/dist/index.d.ts +9 -5
- package/dist/index.js +9 -3
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +385 -111
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +23 -13
- package/dist/react.d.ts +23 -13
- package/dist/react.js +106 -27
- package/dist/react.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-4KKKDDES.js.map +0 -1
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
|
|
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:
|
|
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
|
|
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(`${
|
|
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,
|
|
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(
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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 (!
|
|
1787
|
+
if (!authParams) return false;
|
|
1562
1788
|
setError(null);
|
|
1563
1789
|
setBusy(true);
|
|
1564
1790
|
try {
|
|
1565
|
-
const approved = await
|
|
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
|
-
}, [
|
|
1585
|
-
const
|
|
1586
|
-
if (!
|
|
1809
|
+
}, [authParams, refresh]);
|
|
1810
|
+
const startRegistrationCapture = (0, import_react2.useCallback)(async () => {
|
|
1811
|
+
if (!authParams) return;
|
|
1587
1812
|
setError(null);
|
|
1588
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
}, [
|
|
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
|
-
|
|
1889
|
+
startRegistrationCapture,
|
|
1890
|
+
openVerifySidebar,
|
|
1891
|
+
completeRegistration
|
|
1618
1892
|
};
|
|
1619
1893
|
}
|
|
1620
1894
|
function mapSdkState(state) {
|