@usdctofiat/offramp 1.1.4 → 2.0.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
@@ -20,20 +20,17 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/react.ts
21
21
  var react_exports = {};
22
22
  __export(react_exports, {
23
- useOfframp: () => useOfframp
23
+ useOfframp: () => useOfframp,
24
+ usePeerExtensionRegistration: () => usePeerExtensionRegistration
24
25
  });
25
26
  module.exports = __toCommonJS(react_exports);
26
27
 
27
28
  // src/hooks/useOfframp.ts
28
29
  var import_react = require("react");
29
30
 
30
- // src/deposit.ts
31
- var import_viem3 = require("viem");
32
- var import_chains3 = require("viem/chains");
33
- var import_sdk5 = require("@zkp2p/sdk");
34
-
35
31
  // src/config.ts
36
32
  var import_sdk = require("@zkp2p/sdk");
33
+ var SDK_VERSION = "1.2.0";
37
34
  var BASE_CHAIN_ID = 8453;
38
35
  var RUNTIME_ENV = "production";
39
36
  var API_BASE_URL = "https://api.zkp2p.xyz";
@@ -49,6 +46,7 @@ var GATING_SERVICE_ADDRESS = (0, import_sdk.getGatingServiceAddress)(
49
46
  );
50
47
  var DELEGATE_RATE_MANAGER_ID = "0x8666d6fb0f6797c56e95339fd7ca82fdd348b9db200e10a4c4aa0a0b879fc41c";
51
48
  var RATE_MANAGER_REGISTRY_ADDRESS = "0xeed7db23e724ac4590d6db6f78fda6db203535f3";
49
+ var DELEGATE_MANAGER_FEE_BPS = 10;
52
50
  var REFERRER = "galleonlabs";
53
51
  var MIN_DEPOSIT_USDC = 1;
54
52
  var MIN_ORDER_USDC = 1;
@@ -57,10 +55,33 @@ var INDEXER_INITIAL_DELAY_MS = 1e3;
57
55
  var INDEXER_MAX_DELAY_MS = 1e4;
58
56
  var PAYEE_REGISTRATION_TIMEOUT_MS = 8e3;
59
57
 
60
- // src/platforms.ts
58
+ // src/currencies.ts
61
59
  var import_sdk2 = require("@zkp2p/sdk");
60
+ function buildCurrencies() {
61
+ const entries = {};
62
+ for (const [code, info] of Object.entries(
63
+ import_sdk2.currencyInfo
64
+ )) {
65
+ entries[code] = {
66
+ code: info.currencyCode ?? code,
67
+ name: info.currencyName ?? code,
68
+ symbol: info.currencySymbol ?? code,
69
+ countryCode: info.countryCode ?? ""
70
+ };
71
+ }
72
+ return entries;
73
+ }
74
+ var CURRENCIES = buildCurrencies();
75
+
76
+ // src/deposit.ts
77
+ var import_viem3 = require("viem");
78
+ var import_chains3 = require("viem/chains");
79
+ var import_sdk6 = require("@zkp2p/sdk");
80
+
81
+ // src/platforms.ts
82
+ var import_sdk3 = require("@zkp2p/sdk");
62
83
  var import_zod = require("zod");
63
- var PAYMENT_CATALOG = (0, import_sdk2.getPaymentMethodsCatalog)(BASE_CHAIN_ID, RUNTIME_ENV);
84
+ var PAYMENT_CATALOG = (0, import_sdk3.getPaymentMethodsCatalog)(BASE_CHAIN_ID, RUNTIME_ENV);
64
85
  var ZELLE_HASH_LOOKUP_NAMES = ["zelle", "zelle-bofa", "zelle-chase", "zelle-citi"];
65
86
  var FALLBACK_CURRENCIES = {
66
87
  venmo: ["USD"],
@@ -139,8 +160,8 @@ function gatherCatalogHashes(platform) {
139
160
  function resolveSupportedCurrencies(platform) {
140
161
  const codes = /* @__PURE__ */ new Set();
141
162
  for (const hash of gatherCatalogHashes(platform)) {
142
- const info = (0, import_sdk2.getCurrencyInfoFromHash)(hash);
143
- if (info?.currencyCode && import_sdk2.currencyInfo[info.currencyCode]) {
163
+ const info = (0, import_sdk3.getCurrencyInfoFromHash)(hash);
164
+ if (info?.currencyCode && import_sdk3.currencyInfo[info.currencyCode]) {
144
165
  codes.add(info.currencyCode);
145
166
  }
146
167
  }
@@ -149,6 +170,17 @@ function resolveSupportedCurrencies(platform) {
149
170
  }
150
171
  return Array.from(codes).sort();
151
172
  }
173
+ function normalizePaypalMeUsername(value) {
174
+ const trimmed = value.trim();
175
+ if (!trimmed) return "";
176
+ const withoutProtocol = trimmed.replace(/^https?:\/\//i, "").replace(/^www\./i, "");
177
+ if (/^paypal\.me(?:[?#].*)?$/i.test(withoutProtocol)) return "";
178
+ const withoutDomain = withoutProtocol.replace(/^paypal\.me\//i, "");
179
+ const [pathWithoutQuery] = withoutDomain.split(/[?#]/, 1);
180
+ const sanitizedPath = pathWithoutQuery.replace(/^\/+/, "");
181
+ const [username] = sanitizedPath.split("/", 1);
182
+ return username.replace(/^@+/, "").trim().toLowerCase();
183
+ }
152
184
  var BLUEPRINTS = {
153
185
  venmo: {
154
186
  id: "venmo",
@@ -196,7 +228,13 @@ var BLUEPRINTS = {
196
228
  placeholder: "wisetag (no @)",
197
229
  helperText: "Your Wise @wisetag (no @)",
198
230
  validation: import_zod.z.string().min(1).regex(/^[a-zA-Z0-9_-]+$/),
199
- transform: (v) => v.replace(/^@+/, "").trim()
231
+ transform: (v) => v.replace(/^@+/, "").trim(),
232
+ extensionRegistration: {
233
+ providerId: "wise",
234
+ requiredPrompt: "This Wisetag is not registered yet. Verify it in the Peer extension, then refresh and retry with the same Wisetag.",
235
+ ctaLabel: "Install Peer Extension",
236
+ minExtensionVersion: "0.4.12"
237
+ }
200
238
  },
201
239
  mercadopago: {
202
240
  id: "mercadopago",
@@ -217,10 +255,18 @@ var BLUEPRINTS = {
217
255
  paypal: {
218
256
  id: "paypal",
219
257
  name: "PayPal",
220
- identifierLabel: "Email",
221
- placeholder: "email",
222
- helperText: "Email linked to PayPal account",
223
- validation: import_zod.z.string().email()
258
+ identifierLabel: "PayPal.me Username",
259
+ placeholder: "paypal.me/your-username",
260
+ helperText: "Your PayPal.me username, not your email",
261
+ validation: import_zod.z.string().min(1, "PayPal.me username is required").regex(/^[a-z0-9._-]+$/i, "Use your PayPal.me username (letters, numbers, . _ -)"),
262
+ transform: normalizePaypalMeUsername,
263
+ extensionRegistration: {
264
+ providerId: "paypal",
265
+ requiredPrompt: "This PayPal username is not registered yet. Verify it in the Peer extension, then refresh and retry with the same PayPal username.",
266
+ ctaLabel: "Install Peer Extension",
267
+ ctaSubtext: "PayPal Business accounts are not supported at this time.",
268
+ minExtensionVersion: "0.4.14"
269
+ }
224
270
  },
225
271
  monzo: {
226
272
  id: "monzo",
@@ -251,6 +297,7 @@ function buildPlatformEntry(bp) {
251
297
  placeholder: bp.placeholder,
252
298
  help: bp.helperText
253
299
  },
300
+ ...bp.extensionRegistration ? { extensionRegistration: bp.extensionRegistration } : {},
254
301
  validate(input) {
255
302
  const transformed = bp.transform ? bp.transform(input) : input;
256
303
  const result = bp.validation.safeParse(transformed);
@@ -277,6 +324,9 @@ var PLATFORMS = {
277
324
  MONZO: buildPlatformEntry(BLUEPRINTS.monzo),
278
325
  N26: buildPlatformEntry(BLUEPRINTS.n26)
279
326
  };
327
+ function getPlatformById(id) {
328
+ return Object.values(PLATFORMS).find((p) => p.id === id) ?? null;
329
+ }
280
330
  function normalizePaymentMethodLookupName(platform) {
281
331
  const normalized = platform.trim().toLowerCase();
282
332
  if (!normalized) return null;
@@ -300,7 +350,7 @@ function getPaymentMethodHash(platform) {
300
350
  const catalogEntry = PAYMENT_CATALOG[name];
301
351
  if (catalogEntry?.paymentMethodHash) return catalogEntry.paymentMethodHash;
302
352
  if (name === "zelle" || name.startsWith("zelle-")) return resolveCanonicalZelleHash();
303
- const sdkHash = (0, import_sdk2.resolvePaymentMethodHash)(name);
353
+ const sdkHash = (0, import_sdk3.resolvePaymentMethodHash)(name);
304
354
  return sdkHash ? sdkHash : null;
305
355
  }
306
356
  function getPaymentMethodHashes(platform) {
@@ -309,7 +359,7 @@ function getPaymentMethodHashes(platform) {
309
359
  return hash ? [hash] : [];
310
360
  }
311
361
  const hashes = /* @__PURE__ */ new Set();
312
- const generic = (0, import_sdk2.resolvePaymentMethodHash)("zelle");
362
+ const generic = (0, import_sdk3.resolvePaymentMethodHash)("zelle");
313
363
  if (generic) hashes.add(generic);
314
364
  const canonical = resolveCanonicalZelleHash();
315
365
  if (canonical) hashes.add(canonical);
@@ -320,34 +370,37 @@ function getPaymentMethodHashes(platform) {
320
370
  }
321
371
  return Array.from(hashes);
322
372
  }
323
- function buildDepositData(platform, identifier) {
324
- switch (platform) {
325
- case "venmo":
326
- return { venmoUsername: identifier, telegramUsername: "" };
327
- case "cashapp":
328
- return { cashtag: identifier, telegramUsername: "" };
329
- case "chime":
330
- return { chimesign: identifier.toLowerCase(), telegramUsername: "" };
331
- case "revolut":
332
- return { revolutUsername: identifier, telegramUsername: "" };
333
- case "wise":
334
- return { wisetag: identifier, telegramUsername: "" };
335
- case "mercadopago":
336
- return { cvu: identifier, telegramUsername: "" };
337
- case "zelle":
338
- return { zelleEmail: identifier, telegramUsername: "" };
339
- case "paypal":
340
- return { paypalEmail: identifier, telegramUsername: "" };
341
- case "monzo":
342
- return { monzoMeUsername: identifier, telegramUsername: "" };
343
- case "n26":
344
- return { iban: identifier, telegramUsername: "" };
345
- default:
346
- return { identifier, telegramUsername: "" };
347
- }
373
+ function buildDepositData(platform, identifier, telegramUsername = "") {
374
+ const offchainId = platform === "chime" ? identifier.toLowerCase() : identifier;
375
+ return telegramUsername ? { offchainId, telegramUsername } : { offchainId };
376
+ }
377
+ function getPeerExtensionRegistrationInfo(platform) {
378
+ const entry = getPlatformById(platform);
379
+ return entry?.extensionRegistration ?? null;
380
+ }
381
+ var EXTENSION_REGISTRATION_ERROR_PATTERNS = ["creating the maker", "create the maker"];
382
+ function isPeerExtensionRegistrationError(platform, message, statusCode) {
383
+ const info = getPeerExtensionRegistrationInfo(platform);
384
+ if (!info) return false;
385
+ const normalized = (message ?? "").trim();
386
+ if (normalized === info.requiredPrompt) return true;
387
+ if (statusCode === 400) return true;
388
+ if (!normalized) return false;
389
+ const lower = normalized.toLowerCase();
390
+ return EXTENSION_REGISTRATION_ERROR_PATTERNS.some((p) => lower.includes(p));
348
391
  }
349
392
 
350
393
  // src/errors.ts
394
+ var MakersCreateError = class extends Error {
395
+ status;
396
+ body;
397
+ constructor(message, status, body) {
398
+ super(message);
399
+ this.name = "MakersCreateError";
400
+ this.status = status;
401
+ this.body = body;
402
+ }
403
+ };
351
404
  var OfframpError = class extends Error {
352
405
  code;
353
406
  step;
@@ -718,9 +771,9 @@ var WhitelistPreIntentHook_default = [
718
771
  ];
719
772
 
720
773
  // src/sdk-client.ts
721
- var import_sdk3 = require("@zkp2p/sdk");
774
+ var import_sdk4 = require("@zkp2p/sdk");
722
775
  function createSdkClient(walletClient) {
723
- return new import_sdk3.OfframpClient({
776
+ return new import_sdk4.OfframpClient({
724
777
  walletClient,
725
778
  chainId: BASE_CHAIN_ID,
726
779
  runtimeEnv: RUNTIME_ENV,
@@ -809,13 +862,13 @@ async function readIsWhitelisted(escrow, depositId, taker) {
809
862
  // src/queries.ts
810
863
  var import_viem2 = require("viem");
811
864
  var import_chains2 = require("viem/chains");
812
- var import_sdk4 = require("@zkp2p/sdk");
865
+ var import_sdk5 = require("@zkp2p/sdk");
813
866
  var indexerService = null;
814
867
  function getIndexerService() {
815
868
  if (!indexerService) {
816
- const endpoint = (0, import_sdk4.defaultIndexerEndpoint)("PRODUCTION");
817
- const client = new import_sdk4.IndexerClient(endpoint);
818
- indexerService = new import_sdk4.IndexerDepositService(client);
869
+ const endpoint = (0, import_sdk5.defaultIndexerEndpoint)("PRODUCTION");
870
+ const client = new import_sdk5.IndexerClient(endpoint);
871
+ indexerService = new import_sdk5.IndexerDepositService(client);
819
872
  }
820
873
  return indexerService;
821
874
  }
@@ -849,7 +902,7 @@ function resolveMethodNames(hashes) {
849
902
  return Array.from(names);
850
903
  }
851
904
  function mapDeposit(d) {
852
- const delegationState = (0, import_sdk4.classifyDelegationState)(
905
+ const delegationState = (0, import_sdk5.classifyDelegationState)(
853
906
  d.rateManagerId ?? void 0,
854
907
  d.rateManagerAddress ?? void 0,
855
908
  DELEGATE_RATE_MANAGER_ID,
@@ -866,7 +919,7 @@ function mapDeposit(d) {
866
919
  fulfilledIntents: d.fulfilledIntents ?? 0,
867
920
  paymentMethods: resolveMethodNames(d.paymentMethods),
868
921
  currencies: d.currencies.map((c) => {
869
- const info = (0, import_sdk4.getCurrencyInfoFromHash)(c.currencyCode);
922
+ const info = (0, import_sdk5.getCurrencyInfoFromHash)(c.currencyCode);
870
923
  return info?.currencyCode ?? c.currencyCode;
871
924
  }),
872
925
  rateSource: d.currencies[0]?.rateSource || "unknown",
@@ -905,6 +958,105 @@ async function close(walletClient, depositId, escrowAddress) {
905
958
  return extractTxHash(result);
906
959
  }
907
960
 
961
+ // src/telemetry.ts
962
+ var SDK_EVENTS_ENDPOINT = "https://usdctofiat.xyz/api/sdk-events";
963
+ var MAX_BATCH_SIZE = 50;
964
+ var FLUSH_DELAY_MS = 180;
965
+ var TELEMETRY_TIMEOUT_MS = 2e3;
966
+ var queuedEvents = [];
967
+ var flushTimer = null;
968
+ var flushInFlight = false;
969
+ function sanitizeAttributionId(raw) {
970
+ if (typeof raw !== "string") return void 0;
971
+ const stripped = raw.trim().replace(/[^a-zA-Z0-9_-]/g, "");
972
+ if (!stripped) return void 0;
973
+ if (stripped.length > 64) return void 0;
974
+ return stripped;
975
+ }
976
+ function scheduleFlush() {
977
+ if (flushTimer != null) return;
978
+ flushTimer = setTimeout(() => {
979
+ flushTimer = null;
980
+ void flushQueue();
981
+ }, FLUSH_DELAY_MS);
982
+ }
983
+ async function postBatch(batch) {
984
+ if (!batch.length) return;
985
+ const body = JSON.stringify(batch);
986
+ try {
987
+ if (typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function") {
988
+ const blob = new Blob([body], { type: "application/json" });
989
+ const accepted = navigator.sendBeacon(SDK_EVENTS_ENDPOINT, blob);
990
+ if (accepted) return;
991
+ }
992
+ } catch {
993
+ }
994
+ try {
995
+ const controller = typeof AbortController !== "undefined" ? new AbortController() : null;
996
+ const timeout = controller ? setTimeout(() => controller.abort(), TELEMETRY_TIMEOUT_MS) : null;
997
+ await fetch(SDK_EVENTS_ENDPOINT, {
998
+ method: "POST",
999
+ headers: { "Content-Type": "application/json" },
1000
+ body,
1001
+ keepalive: true,
1002
+ signal: controller?.signal
1003
+ }).catch(() => {
1004
+ });
1005
+ if (timeout) clearTimeout(timeout);
1006
+ } catch {
1007
+ }
1008
+ }
1009
+ async function flushQueue() {
1010
+ if (flushInFlight) return;
1011
+ if (!queuedEvents.length) return;
1012
+ flushInFlight = true;
1013
+ try {
1014
+ while (queuedEvents.length > 0) {
1015
+ const batch = queuedEvents.slice(0, MAX_BATCH_SIZE);
1016
+ queuedEvents = queuedEvents.slice(batch.length);
1017
+ await postBatch(batch);
1018
+ }
1019
+ } finally {
1020
+ flushInFlight = false;
1021
+ }
1022
+ }
1023
+ function emitEvent(name, payload) {
1024
+ try {
1025
+ queuedEvents.push({
1026
+ name,
1027
+ payload,
1028
+ ts: Date.now()
1029
+ });
1030
+ if (queuedEvents.length >= MAX_BATCH_SIZE) {
1031
+ void flushQueue();
1032
+ return;
1033
+ }
1034
+ scheduleFlush();
1035
+ } catch {
1036
+ }
1037
+ }
1038
+ function createTelemetryContext(options) {
1039
+ const integratorId = sanitizeAttributionId(options.integratorId);
1040
+ const referralId = sanitizeAttributionId(options.referralId);
1041
+ const enabled = options.telemetry !== false;
1042
+ const sdkVersion = options.sdkVersion;
1043
+ return {
1044
+ enabled,
1045
+ sdkVersion,
1046
+ integratorId,
1047
+ referralId,
1048
+ emit(name, payload = {}) {
1049
+ if (!enabled) return;
1050
+ emitEvent(name, {
1051
+ sdkVersion,
1052
+ integratorId,
1053
+ referralId,
1054
+ ...payload
1055
+ });
1056
+ }
1057
+ };
1058
+ }
1059
+
908
1060
  // src/deposit.ts
909
1061
  function usdcToUnits(amount) {
910
1062
  return (0, import_viem3.parseUnits)(amount, 6);
@@ -950,23 +1102,39 @@ function extractDepositIdFromLogs(logs) {
950
1102
  }
951
1103
  return null;
952
1104
  }
953
- async function registerPayeeDetails(processorName, depositData) {
1105
+ async function registerPayeeDetails(processorName, payload) {
954
1106
  const controller = new AbortController();
955
1107
  const timeout = setTimeout(() => controller.abort(), PAYEE_REGISTRATION_TIMEOUT_MS);
956
1108
  try {
957
- const res = await fetch(`${API_BASE_URL}/v1/makers/create`, {
1109
+ const res = await fetch(`${API_BASE_URL}/v2/makers/create`, {
958
1110
  method: "POST",
959
1111
  headers: { "Content-Type": "application/json" },
960
- body: JSON.stringify({ processorName, depositData }),
1112
+ body: JSON.stringify({ processorName, ...payload }),
961
1113
  signal: controller.signal
962
1114
  });
963
1115
  if (!res.ok) {
964
1116
  const txt = await res.text().catch(() => "");
965
- throw new Error(`makers/create failed (${res.status}): ${txt || res.statusText}`);
1117
+ let detail = txt || res.statusText;
1118
+ try {
1119
+ const parsed = JSON.parse(txt);
1120
+ if (typeof parsed.message === "string" && parsed.message.trim()) {
1121
+ detail = parsed.message;
1122
+ }
1123
+ } catch {
1124
+ }
1125
+ throw new MakersCreateError(
1126
+ `makers/create failed (${res.status}): ${detail}`,
1127
+ res.status,
1128
+ txt
1129
+ );
966
1130
  }
967
1131
  const json = await res.json();
968
1132
  if (!json.success || !json.responseObject?.hashedOnchainId) {
969
- throw new Error(json.message || "makers/create returned no hashedOnchainId");
1133
+ throw new MakersCreateError(
1134
+ json.message || "makers/create returned no hashedOnchainId",
1135
+ json.statusCode ?? null,
1136
+ ""
1137
+ );
970
1138
  }
971
1139
  return json.responseObject.hashedOnchainId;
972
1140
  } finally {
@@ -978,7 +1146,7 @@ function attachOracleConfig(entries, conversionRates) {
978
1146
  (group, gi) => group.map((entry, ci) => {
979
1147
  const currency = conversionRates[gi]?.[ci]?.currency;
980
1148
  if (!currency) return entry;
981
- const oracleConfig = (0, import_sdk5.getSpreadOracleConfig)(currency);
1149
+ const oracleConfig = (0, import_sdk6.getSpreadOracleConfig)(currency);
982
1150
  if (!oracleConfig) return entry;
983
1151
  return {
984
1152
  ...entry,
@@ -1022,271 +1190,501 @@ async function findUndelegatedDeposit(walletAddress) {
1022
1190
  if (remaining === 0n) return false;
1023
1191
  return (d.rateManagerId ?? "").toLowerCase() !== delegateIdLower;
1024
1192
  });
1025
- undelegated.sort((a, b) => Number(BigInt(b.depositId) - BigInt(a.depositId)));
1193
+ undelegated.sort((a, b) => {
1194
+ const left = BigInt(a.depositId);
1195
+ const right = BigInt(b.depositId);
1196
+ if (left === right) return 0;
1197
+ return left > right ? -1 : 1;
1198
+ });
1026
1199
  return undelegated[0] ?? null;
1027
1200
  } catch {
1028
1201
  return null;
1029
1202
  }
1030
1203
  }
1031
- async function offramp(walletClient, params, onProgress) {
1204
+ async function offramp(walletClient, params, onProgress, telemetryContext) {
1032
1205
  const { amount, platform, currency, identifier } = params;
1033
1206
  const platformId = platform.id;
1034
1207
  const currencyCode = currency.code;
1035
- if (!walletClient.account?.address) {
1036
- throw new OfframpError("Wallet client has no account. Connect a wallet first.", "VALIDATION");
1037
- }
1038
- const walletAddress = walletClient.account.address;
1039
- const amt = parseFloat(amount);
1040
- if (!Number.isFinite(amt) || amt < MIN_DEPOSIT_USDC) {
1041
- throw new OfframpError(`Minimum deposit is ${MIN_DEPOSIT_USDC} USDC`, "VALIDATION");
1042
- }
1043
- if (!platform.currencies.includes(currencyCode)) {
1044
- throw new OfframpError(`${currencyCode} is not supported on ${platform.name}`, "UNSUPPORTED");
1045
- }
1046
- const validation = platform.validate(identifier);
1047
- if (!validation.valid) {
1048
- throw new OfframpError(validation.error || "Invalid identifier", "VALIDATION");
1049
- }
1050
- const normalizedIdentifier = validation.normalized;
1051
- const methodHash = getPaymentMethodHash(platformId);
1052
- if (!methodHash) {
1053
- throw new OfframpError(`${platform.name} is not currently supported`, "UNSUPPORTED");
1054
- }
1055
- const existing = await findUndelegatedDeposit(walletAddress);
1056
- if (existing) {
1057
- onProgress?.({ step: "resuming", depositId: existing.depositId });
1058
- const client2 = createSdkClient(walletClient);
1059
- const txOverrides2 = { referrer: [REFERRER] };
1208
+ const telemetry = telemetryContext ?? createTelemetryContext({
1209
+ sdkVersion: SDK_VERSION,
1210
+ telemetry: true,
1211
+ integratorId: params.integratorId,
1212
+ referralId: params.referralId
1213
+ });
1214
+ const flowStartMs = Date.now();
1215
+ const emitProgress = (progress) => {
1216
+ onProgress?.(progress);
1217
+ telemetry.emit("sdk.createDeposit.progress", {
1218
+ step: progress.step,
1219
+ txHash: progress.txHash
1220
+ });
1221
+ };
1222
+ try {
1223
+ if (!walletClient.account?.address) {
1224
+ throw new OfframpError("Wallet client has no account. Connect a wallet first.", "VALIDATION");
1225
+ }
1226
+ const walletAddress = walletClient.account.address.toLowerCase();
1227
+ telemetry.emit("sdk.createDeposit.start", {
1228
+ platform: platformId,
1229
+ currency: currencyCode,
1230
+ amount,
1231
+ wallet: walletAddress
1232
+ });
1233
+ const amt = parseFloat(amount);
1234
+ if (!Number.isFinite(amt) || amt < MIN_DEPOSIT_USDC) {
1235
+ throw new OfframpError(`Minimum deposit is ${MIN_DEPOSIT_USDC} USDC`, "VALIDATION");
1236
+ }
1237
+ if (!platform.currencies.includes(currencyCode)) {
1238
+ throw new OfframpError(`${currencyCode} is not supported on ${platform.name}`, "UNSUPPORTED");
1239
+ }
1240
+ const validation = platform.validate(identifier);
1241
+ if (!validation.valid) {
1242
+ throw new OfframpError(validation.error || "Invalid identifier", "VALIDATION");
1243
+ }
1244
+ const normalizedIdentifier = validation.normalized;
1245
+ const methodHash = getPaymentMethodHash(platformId);
1246
+ if (!methodHash) {
1247
+ throw new OfframpError(`${platform.name} is not currently supported`, "UNSUPPORTED");
1248
+ }
1249
+ const existing = await findUndelegatedDeposit(walletAddress);
1250
+ if (existing) {
1251
+ emitProgress({ step: "resuming", depositId: existing.depositId });
1252
+ const client2 = createSdkClient(walletClient);
1253
+ const txOverrides2 = { referrer: [REFERRER] };
1254
+ try {
1255
+ const result2 = await client2.setRateManager({
1256
+ depositId: BigInt(existing.depositId),
1257
+ rateManagerAddress: RATE_MANAGER_REGISTRY_ADDRESS,
1258
+ rateManagerId: DELEGATE_RATE_MANAGER_ID,
1259
+ escrowAddress: existing.escrowAddress,
1260
+ txOverrides: txOverrides2
1261
+ });
1262
+ const txHash = extractTxHash2(result2);
1263
+ emitProgress({ step: "done", txHash, depositId: existing.depositId });
1264
+ const resumedResult = { depositId: existing.depositId, txHash, resumed: true };
1265
+ telemetry.emit("sdk.createDeposit.success", {
1266
+ depositId: resumedResult.depositId,
1267
+ txHash: resumedResult.txHash,
1268
+ wallet: walletAddress,
1269
+ resumed: true,
1270
+ wallDurationMs: Date.now() - flowStartMs
1271
+ });
1272
+ return resumedResult;
1273
+ } catch (err) {
1274
+ if (isUserCancellation(err))
1275
+ throw new OfframpError("User cancelled", "USER_CANCELLED", "resuming", err);
1276
+ throw new OfframpError(
1277
+ "Found undelegated deposit but delegation failed. Try again.",
1278
+ "DELEGATION_FAILED",
1279
+ "resuming",
1280
+ err,
1281
+ { depositId: existing.depositId, txHash: existing.txHash }
1282
+ );
1283
+ }
1284
+ }
1285
+ const truncatedAmount = amt.toFixed(6).replace(/\.?0+$/, "");
1286
+ const amountUnits = usdcToUnits(truncatedAmount);
1287
+ const minUnits = usdcToUnits(String(MIN_ORDER_USDC));
1288
+ const maxUnits = usdcToUnits(String(Math.min(amt, 2500)));
1060
1289
  try {
1061
- const result = await client2.setRateManager({
1062
- depositId: BigInt(existing.depositId),
1063
- rateManagerAddress: RATE_MANAGER_REGISTRY_ADDRESS,
1064
- rateManagerId: DELEGATE_RATE_MANAGER_ID,
1065
- escrowAddress: existing.escrowAddress,
1066
- txOverrides: txOverrides2
1290
+ const publicClient = (0, import_viem3.createPublicClient)({ chain: import_chains3.base, transport: (0, import_viem3.http)(BASE_RPC_URL) });
1291
+ const balance = await publicClient.readContract({
1292
+ address: USDC_ADDRESS,
1293
+ abi: [
1294
+ {
1295
+ name: "balanceOf",
1296
+ type: "function",
1297
+ stateMutability: "view",
1298
+ inputs: [{ name: "account", type: "address" }],
1299
+ outputs: [{ name: "", type: "uint256" }]
1300
+ }
1301
+ ],
1302
+ functionName: "balanceOf",
1303
+ args: [walletAddress]
1304
+ });
1305
+ if (balance < amountUnits) {
1306
+ const available = Number((0, import_viem3.formatUnits)(balance, 6));
1307
+ throw new OfframpError(
1308
+ `Insufficient USDC balance. Have ${available.toFixed(2)}, need ${truncatedAmount}.`,
1309
+ "VALIDATION"
1310
+ );
1311
+ }
1312
+ } catch (err) {
1313
+ if (err instanceof OfframpError) throw err;
1314
+ }
1315
+ const client = createSdkClient(walletClient);
1316
+ const txOverrides = { referrer: [REFERRER] };
1317
+ emitProgress({ step: "approving" });
1318
+ try {
1319
+ await client.ensureAllowance({
1320
+ token: USDC_ADDRESS,
1321
+ amount: amountUnits,
1322
+ escrowAddress: ESCROW_ADDRESS,
1323
+ maxApprove: false,
1324
+ txOverrides
1067
1325
  });
1068
- const txHash = extractTxHash2(result);
1069
- onProgress?.({ step: "done", txHash, depositId: existing.depositId });
1070
- return { depositId: existing.depositId, txHash, resumed: true };
1071
1326
  } catch (err) {
1072
1327
  if (isUserCancellation(err))
1073
- throw new OfframpError("User cancelled", "USER_CANCELLED", "resuming", err);
1328
+ throw new OfframpError("User cancelled", "USER_CANCELLED", "approving", err);
1329
+ const detail = err instanceof Error ? err.message : String(err);
1074
1330
  throw new OfframpError(
1075
- "Found undelegated deposit but delegation failed. Try again.",
1076
- "DELEGATION_FAILED",
1077
- "resuming",
1078
- err,
1079
- { depositId: existing.depositId, txHash: existing.txHash }
1331
+ `USDC approval failed: ${detail}`,
1332
+ "APPROVAL_FAILED",
1333
+ "approving",
1334
+ err
1080
1335
  );
1081
1336
  }
1082
- }
1083
- const truncatedAmount = amt.toFixed(6).replace(/\.?0+$/, "");
1084
- const amountUnits = usdcToUnits(truncatedAmount);
1085
- const minUnits = usdcToUnits(String(MIN_ORDER_USDC));
1086
- const maxUnits = usdcToUnits(String(Math.min(amt, 2500)));
1087
- try {
1088
- const publicClient = (0, import_viem3.createPublicClient)({ chain: import_chains3.base, transport: (0, import_viem3.http)(BASE_RPC_URL) });
1089
- const balance = await publicClient.readContract({
1090
- address: USDC_ADDRESS,
1091
- abi: [
1092
- {
1093
- name: "balanceOf",
1094
- type: "function",
1095
- stateMutability: "view",
1096
- inputs: [{ name: "account", type: "address" }],
1097
- outputs: [{ name: "", type: "uint256" }]
1098
- }
1099
- ],
1100
- functionName: "balanceOf",
1101
- args: [walletAddress]
1102
- });
1103
- if (balance < amountUnits) {
1104
- const available = Number((0, import_viem3.formatUnits)(balance, 6));
1337
+ emitProgress({ step: "registering" });
1338
+ let hashedOnchainId;
1339
+ const canonicalName = platformId.startsWith("zelle") ? "zelle" : platformId;
1340
+ try {
1341
+ const payeePayload = buildDepositData(canonicalName, normalizedIdentifier);
1342
+ hashedOnchainId = await registerPayeeDetails(canonicalName, payeePayload);
1343
+ } catch (err) {
1344
+ const status = err instanceof MakersCreateError ? err.status : null;
1345
+ const message = err instanceof Error ? err.message : String(err);
1346
+ if (isPeerExtensionRegistrationError(canonicalName, message, status)) {
1347
+ throw new OfframpError(
1348
+ `${platform.name} maker is not yet registered in the Peer extension. Verify this handle in the extension, then retry.`,
1349
+ "EXTENSION_REGISTRATION_REQUIRED",
1350
+ "registering",
1351
+ err
1352
+ );
1353
+ }
1105
1354
  throw new OfframpError(
1106
- `Insufficient USDC balance. Have ${available.toFixed(2)}, need ${truncatedAmount}.`,
1107
- "VALIDATION"
1355
+ "Payee registration failed",
1356
+ "REGISTRATION_FAILED",
1357
+ "registering",
1358
+ err
1108
1359
  );
1109
1360
  }
1110
- } catch (err) {
1111
- if (err instanceof OfframpError) throw err;
1112
- }
1113
- const client = createSdkClient(walletClient);
1114
- const txOverrides = { referrer: [REFERRER] };
1115
- onProgress?.({ step: "approving" });
1116
- try {
1117
- await client.ensureAllowance({
1118
- token: USDC_ADDRESS,
1119
- amount: amountUnits,
1120
- escrowAddress: ESCROW_ADDRESS,
1121
- maxApprove: false,
1122
- txOverrides
1123
- });
1124
- } catch (err) {
1125
- if (isUserCancellation(err))
1126
- throw new OfframpError("User cancelled", "USER_CANCELLED", "approving", err);
1127
- const detail = err instanceof Error ? err.message : String(err);
1128
- throw new OfframpError(`USDC approval failed: ${detail}`, "APPROVAL_FAILED", "approving", err);
1129
- }
1130
- onProgress?.({ step: "registering" });
1131
- let hashedOnchainId;
1132
- try {
1133
- const canonicalName = platformId.startsWith("zelle") ? "zelle" : platformId;
1134
- const depositData = buildDepositData(platformId, normalizedIdentifier);
1135
- hashedOnchainId = await registerPayeeDetails(canonicalName, depositData);
1136
- } catch (err) {
1137
- throw new OfframpError("Payee registration failed", "REGISTRATION_FAILED", "registering", err);
1138
- }
1139
- onProgress?.({ step: "depositing" });
1140
- const conversionRates = [
1141
- [{ currency: currencyCode, conversionRate: "1" }]
1142
- ];
1143
- const baseCurrenciesOverride = (0, import_sdk5.mapConversionRatesToOnchainMinRate)(conversionRates, 1);
1144
- const currenciesOverride = attachOracleConfig(baseCurrenciesOverride, conversionRates);
1145
- let hash;
1146
- try {
1147
- const result = await client.createDeposit({
1148
- token: USDC_ADDRESS,
1149
- amount: amountUnits,
1150
- retainOnEmpty: false,
1151
- intentAmountRange: { min: minUnits, max: maxUnits },
1152
- processorNames: [platformId],
1153
- conversionRates,
1154
- payeeDetailsHashes: [hashedOnchainId],
1155
- paymentMethodsOverride: [methodHash],
1156
- paymentMethodDataOverride: [
1157
- {
1158
- intentGatingService: GATING_SERVICE_ADDRESS,
1159
- payeeDetails: hashedOnchainId,
1160
- data: "0x"
1161
- }
1162
- ],
1163
- currenciesOverride,
1164
- escrowAddress: ESCROW_ADDRESS,
1165
- txOverrides
1166
- });
1167
- if (!result?.hash) throw new Error("No transaction hash returned");
1168
- hash = result.hash;
1169
- } catch (err) {
1170
- if (isUserCancellation(err))
1171
- throw new OfframpError("User cancelled", "USER_CANCELLED", "depositing", err);
1172
- const detail = err instanceof Error ? err.message : String(err);
1173
- throw new OfframpError(
1174
- `Deposit transaction failed: ${detail}`,
1175
- "DEPOSIT_FAILED",
1176
- "depositing",
1177
- err
1178
- );
1179
- }
1180
- onProgress?.({ step: "confirming", txHash: hash });
1181
- let depositId = "";
1182
- const receiptClient = client;
1183
- if (typeof receiptClient.waitForTransactionReceipt === "function") {
1361
+ emitProgress({ step: "depositing" });
1362
+ const conversionRates = [
1363
+ [{ currency: currencyCode, conversionRate: "1" }]
1364
+ ];
1365
+ const baseCurrenciesOverride = (0, import_sdk6.mapConversionRatesToOnchainMinRate)(conversionRates, 1);
1366
+ const currenciesOverride = attachOracleConfig(baseCurrenciesOverride, conversionRates);
1367
+ let hash;
1184
1368
  try {
1185
- const receipt = await receiptClient.waitForTransactionReceipt({ hash, confirmations: 1 });
1186
- depositId = extractDepositIdFromLogs(receipt.logs) || "";
1187
- } catch {
1369
+ const result2 = await client.createDeposit({
1370
+ token: USDC_ADDRESS,
1371
+ amount: amountUnits,
1372
+ retainOnEmpty: false,
1373
+ intentAmountRange: { min: minUnits, max: maxUnits },
1374
+ processorNames: [platformId],
1375
+ conversionRates,
1376
+ payeeDetailsHashes: [hashedOnchainId],
1377
+ paymentMethodsOverride: [methodHash],
1378
+ paymentMethodDataOverride: [
1379
+ {
1380
+ intentGatingService: GATING_SERVICE_ADDRESS,
1381
+ payeeDetails: hashedOnchainId,
1382
+ data: "0x"
1383
+ }
1384
+ ],
1385
+ currenciesOverride,
1386
+ escrowAddress: ESCROW_ADDRESS,
1387
+ txOverrides
1388
+ });
1389
+ if (!result2?.hash) throw new Error("No transaction hash returned");
1390
+ hash = result2.hash;
1391
+ } catch (err) {
1392
+ if (isUserCancellation(err))
1393
+ throw new OfframpError("User cancelled", "USER_CANCELLED", "depositing", err);
1394
+ const detail = err instanceof Error ? err.message : String(err);
1395
+ throw new OfframpError(
1396
+ `Deposit transaction failed: ${detail}`,
1397
+ "DEPOSIT_FAILED",
1398
+ "depositing",
1399
+ err
1400
+ );
1188
1401
  }
1189
- }
1190
- if (!depositId) {
1191
- let delay = INDEXER_INITIAL_DELAY_MS;
1192
- for (let attempt = 0; attempt < INDEXER_MAX_ATTEMPTS && !depositId; attempt++) {
1402
+ emitProgress({ step: "confirming", txHash: hash });
1403
+ let depositId = "";
1404
+ const receiptClient = client;
1405
+ if (typeof receiptClient.waitForTransactionReceipt === "function") {
1193
1406
  try {
1194
- const deps = await client.indexer.getDepositsWithRelations(
1195
- { depositor: walletAddress },
1196
- { limit: 25 }
1197
- );
1198
- const hit = deps.find((d) => (d?.txHash || "").toLowerCase() === hash.toLowerCase());
1199
- if (hit) {
1200
- depositId = String(hit.depositId);
1201
- break;
1202
- }
1407
+ const receipt = await receiptClient.waitForTransactionReceipt({ hash, confirmations: 1 });
1408
+ depositId = extractDepositIdFromLogs(receipt.logs) || "";
1203
1409
  } catch {
1204
1410
  }
1205
- await new Promise((r) => setTimeout(r, delay));
1206
- delay = Math.min(INDEXER_MAX_DELAY_MS, Math.floor(delay * 1.7));
1207
1411
  }
1208
- }
1209
- if (!depositId) {
1210
- throw new OfframpError(
1211
- "Deposit created on-chain but could not confirm deposit ID. Your funds are safe. Call offramp() again to resume delegation.",
1212
- "CONFIRMATION_FAILED",
1213
- "confirming",
1214
- void 0,
1215
- { txHash: hash }
1216
- );
1217
- }
1218
- onProgress?.({ step: "delegating", txHash: hash, depositId });
1219
- try {
1220
- await client.setRateManager({
1221
- depositId: BigInt(depositId),
1222
- rateManagerAddress: RATE_MANAGER_REGISTRY_ADDRESS,
1223
- rateManagerId: DELEGATE_RATE_MANAGER_ID,
1224
- escrowAddress: ESCROW_ADDRESS,
1225
- txOverrides
1226
- });
1227
- } catch (delegationError) {
1228
- if (isUserCancellation(delegationError)) {
1412
+ if (!depositId) {
1413
+ let delay = INDEXER_INITIAL_DELAY_MS;
1414
+ for (let attempt = 0; attempt < INDEXER_MAX_ATTEMPTS && !depositId; attempt++) {
1415
+ try {
1416
+ const deps = await client.indexer.getDepositsWithRelations(
1417
+ { depositor: walletAddress },
1418
+ { limit: 25 }
1419
+ );
1420
+ const hit = deps.find((d) => (d?.txHash || "").toLowerCase() === hash.toLowerCase());
1421
+ if (hit) {
1422
+ depositId = String(hit.depositId);
1423
+ break;
1424
+ }
1425
+ } catch {
1426
+ }
1427
+ await new Promise((r) => setTimeout(r, delay));
1428
+ delay = Math.min(INDEXER_MAX_DELAY_MS, Math.floor(delay * 1.7));
1429
+ }
1430
+ }
1431
+ if (!depositId) {
1229
1432
  throw new OfframpError(
1230
- "User cancelled delegation",
1231
- "USER_CANCELLED",
1433
+ "Deposit created on-chain but could not confirm deposit ID. Your funds are safe. Call offramp() again to resume delegation.",
1434
+ "CONFIRMATION_FAILED",
1435
+ "confirming",
1436
+ void 0,
1437
+ { txHash: hash }
1438
+ );
1439
+ }
1440
+ emitProgress({ step: "delegating", txHash: hash, depositId });
1441
+ try {
1442
+ await client.setRateManager({
1443
+ depositId: BigInt(depositId),
1444
+ rateManagerAddress: RATE_MANAGER_REGISTRY_ADDRESS,
1445
+ rateManagerId: DELEGATE_RATE_MANAGER_ID,
1446
+ escrowAddress: ESCROW_ADDRESS,
1447
+ txOverrides
1448
+ });
1449
+ } catch (delegationError) {
1450
+ if (isUserCancellation(delegationError)) {
1451
+ throw new OfframpError(
1452
+ "User cancelled delegation",
1453
+ "USER_CANCELLED",
1454
+ "delegating",
1455
+ delegationError,
1456
+ { txHash: hash, depositId }
1457
+ );
1458
+ }
1459
+ throw new OfframpError(
1460
+ "Deposit created but delegation failed. Call offramp() again to resume delegation.",
1461
+ "DELEGATION_FAILED",
1232
1462
  "delegating",
1233
1463
  delegationError,
1234
1464
  { txHash: hash, depositId }
1235
1465
  );
1236
1466
  }
1237
- throw new OfframpError(
1238
- "Deposit created but delegation failed. Call offramp() again to resume delegation.",
1239
- "DELEGATION_FAILED",
1240
- "delegating",
1241
- delegationError,
1242
- { txHash: hash, depositId }
1243
- );
1244
- }
1245
- let otcLink;
1246
- if (params.otcTaker) {
1247
- onProgress?.({ step: "restricting", txHash: hash, depositId });
1248
- try {
1249
- const otcResult = await enableOtc(walletClient, depositId, params.otcTaker);
1250
- otcLink = otcResult.otcLink;
1251
- } catch (otcError) {
1252
- if (isUserCancellation(otcError)) {
1467
+ let otcLink;
1468
+ if (params.otcTaker) {
1469
+ emitProgress({ step: "restricting", txHash: hash, depositId });
1470
+ try {
1471
+ const otcResult = await enableOtc(walletClient, depositId, params.otcTaker);
1472
+ otcLink = otcResult.otcLink;
1473
+ } catch (otcError) {
1474
+ if (isUserCancellation(otcError)) {
1475
+ throw new OfframpError(
1476
+ "User cancelled OTC restriction",
1477
+ "USER_CANCELLED",
1478
+ "restricting",
1479
+ otcError,
1480
+ {
1481
+ txHash: hash,
1482
+ depositId
1483
+ }
1484
+ );
1485
+ }
1253
1486
  throw new OfframpError(
1254
- "User cancelled OTC restriction",
1255
- "USER_CANCELLED",
1487
+ "Deposit created and delegated but OTC restriction failed. Use enableOtc() to retry.",
1488
+ "DEPOSIT_FAILED",
1256
1489
  "restricting",
1257
1490
  otcError,
1258
- {
1259
- txHash: hash,
1260
- depositId
1261
- }
1491
+ { txHash: hash, depositId }
1262
1492
  );
1263
1493
  }
1494
+ }
1495
+ emitProgress({ step: "done", txHash: hash, depositId });
1496
+ const result = { depositId, txHash: hash, resumed: false, otcLink };
1497
+ telemetry.emit("sdk.createDeposit.success", {
1498
+ depositId: result.depositId,
1499
+ txHash: result.txHash,
1500
+ wallet: walletAddress,
1501
+ resumed: false,
1502
+ wallDurationMs: Date.now() - flowStartMs
1503
+ });
1504
+ return result;
1505
+ } catch (error) {
1506
+ const known = error instanceof OfframpError ? error : null;
1507
+ telemetry.emit("sdk.createDeposit.error", {
1508
+ code: known?.code ?? "DEPOSIT_FAILED",
1509
+ step: known?.step ?? "unknown"
1510
+ });
1511
+ throw error;
1512
+ }
1513
+ }
1514
+
1515
+ // src/offramp.ts
1516
+ var IDEMPOTENCY_TTL_MS = 10 * 60 * 1e3;
1517
+ var IDEMPOTENCY_STORAGE_PREFIX = "offramp:idempotency:";
1518
+ function getWalletAddress(walletClient) {
1519
+ const address = walletClient.account?.address;
1520
+ if (!address) {
1521
+ throw new OfframpError("Wallet client has no account. Connect a wallet first.", "VALIDATION");
1522
+ }
1523
+ return address.toLowerCase();
1524
+ }
1525
+ function getIdempotencyStorageKey(walletAddress, idempotencyKey) {
1526
+ return `${IDEMPOTENCY_STORAGE_PREFIX}${walletAddress}:${idempotencyKey}`;
1527
+ }
1528
+ function readIdempotencyResult(storageKey) {
1529
+ if (typeof sessionStorage === "undefined") return null;
1530
+ try {
1531
+ const raw = sessionStorage.getItem(storageKey);
1532
+ if (!raw) return null;
1533
+ const parsed = JSON.parse(raw);
1534
+ const expiresAt = typeof parsed.expiresAt === "number" && Number.isFinite(parsed.expiresAt) ? parsed.expiresAt : 0;
1535
+ if (expiresAt <= Date.now()) {
1536
+ sessionStorage.removeItem(storageKey);
1537
+ return null;
1538
+ }
1539
+ const result = parsed.result;
1540
+ if (!result?.depositId || !result.txHash) {
1541
+ sessionStorage.removeItem(storageKey);
1542
+ return null;
1543
+ }
1544
+ return result;
1545
+ } catch {
1546
+ return null;
1547
+ }
1548
+ }
1549
+ function writeIdempotencyResult(storageKey, result) {
1550
+ if (typeof sessionStorage === "undefined") return;
1551
+ try {
1552
+ const record = {
1553
+ result,
1554
+ expiresAt: Date.now() + IDEMPOTENCY_TTL_MS
1555
+ };
1556
+ sessionStorage.setItem(storageKey, JSON.stringify(record));
1557
+ } catch {
1558
+ }
1559
+ }
1560
+ function createPerFlowTelemetry(base4, integratorId, referralId) {
1561
+ return createTelemetryContext({
1562
+ sdkVersion: base4.sdkVersion,
1563
+ telemetry: base4.enabled,
1564
+ integratorId: sanitizeAttributionId(integratorId) ?? base4.integratorId,
1565
+ referralId: sanitizeAttributionId(referralId) ?? base4.referralId
1566
+ });
1567
+ }
1568
+ function normalizeCurrencyEntry(code) {
1569
+ const fromCatalog = CURRENCIES[code];
1570
+ if (fromCatalog) return fromCatalog;
1571
+ return {
1572
+ code,
1573
+ name: code,
1574
+ symbol: code,
1575
+ countryCode: ""
1576
+ };
1577
+ }
1578
+ function buildCurrencyGroups() {
1579
+ const grouped = {};
1580
+ for (const platform of Object.values(PLATFORMS)) {
1581
+ grouped[platform.id] = platform.currencies.map(normalizeCurrencyEntry);
1582
+ }
1583
+ return grouped;
1584
+ }
1585
+ var Offramp = class {
1586
+ walletClient;
1587
+ telemetry;
1588
+ constructor(options) {
1589
+ this.walletClient = options.walletClient;
1590
+ this.telemetry = createTelemetryContext({
1591
+ sdkVersion: SDK_VERSION,
1592
+ telemetry: options.telemetry,
1593
+ integratorId: options.integratorId,
1594
+ referralId: options.referralId
1595
+ });
1596
+ this.telemetry.emit("sdk.init", {
1597
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "unknown"
1598
+ });
1599
+ }
1600
+ async createDeposit(params, onProgress) {
1601
+ const flowTelemetry = createPerFlowTelemetry(
1602
+ this.telemetry,
1603
+ params.integratorId,
1604
+ params.referralId
1605
+ );
1606
+ const sanitizedParams = {
1607
+ ...params,
1608
+ integratorId: flowTelemetry.integratorId,
1609
+ referralId: flowTelemetry.referralId
1610
+ };
1611
+ const sanitizedKey = sanitizeAttributionId(params.idempotencyKey);
1612
+ const walletAddress = sanitizedKey ? getWalletAddress(this.walletClient) : null;
1613
+ const storageKey = walletAddress && sanitizedKey ? getIdempotencyStorageKey(walletAddress, sanitizedKey) : null;
1614
+ if (storageKey) {
1615
+ const existing = readIdempotencyResult(storageKey);
1616
+ if (existing) return existing;
1617
+ }
1618
+ const result = await offramp(
1619
+ this.walletClient,
1620
+ sanitizedParams,
1621
+ onProgress,
1622
+ flowTelemetry
1623
+ );
1624
+ if (storageKey) {
1625
+ writeIdempotencyResult(storageKey, result);
1626
+ }
1627
+ return result;
1628
+ }
1629
+ getQuote(input) {
1630
+ const amountUsdc = Number(input.amount);
1631
+ if (!Number.isFinite(amountUsdc) || amountUsdc <= 0) {
1632
+ throw new OfframpError("Amount must be a positive number", "VALIDATION");
1633
+ }
1634
+ if (!input.platform.currencies.includes(input.currency.code)) {
1264
1635
  throw new OfframpError(
1265
- "Deposit created and delegated but OTC restriction failed. Use enableOtc() to retry.",
1266
- "DEPOSIT_FAILED",
1267
- "restricting",
1268
- otcError,
1269
- { txHash: hash, depositId }
1636
+ `${input.currency.code} is not supported on ${input.platform.name}`,
1637
+ "UNSUPPORTED"
1270
1638
  );
1271
1639
  }
1640
+ const vaultSpreadBps = 0;
1641
+ const spreadFactor = Math.max(0, 1 - vaultSpreadBps / 1e4);
1642
+ const expectedFiat = amountUsdc * spreadFactor;
1643
+ const effectiveRate = amountUsdc === 0 ? 0 : expectedFiat / amountUsdc;
1644
+ return {
1645
+ amountUsdc,
1646
+ expectedFiat,
1647
+ effectiveRate,
1648
+ vaultSpreadBps
1649
+ };
1272
1650
  }
1273
- onProgress?.({ step: "done", txHash: hash, depositId });
1274
- return { depositId, txHash: hash, resumed: false, otcLink };
1651
+ getVaultStatus() {
1652
+ return {
1653
+ rateManagerId: DELEGATE_RATE_MANAGER_ID,
1654
+ rateManagerAddress: RATE_MANAGER_REGISTRY_ADDRESS,
1655
+ feeRateBps: DELEGATE_MANAGER_FEE_BPS,
1656
+ escrow: ESCROW_ADDRESS,
1657
+ isActive: true
1658
+ };
1659
+ }
1660
+ listCurrencies() {
1661
+ return buildCurrencyGroups();
1662
+ }
1663
+ };
1664
+ function createOfframp(options) {
1665
+ return new Offramp(options);
1275
1666
  }
1276
1667
 
1277
1668
  // src/hooks/useOfframp.ts
1278
- function useOfframp() {
1669
+ function mapStepToState(step) {
1670
+ if (step === "resuming" || step === "restricting") return "delegating";
1671
+ if (step === "done") return "done";
1672
+ return step;
1673
+ }
1674
+ function useOfframp(defaultOptions = {}) {
1675
+ const [state, setState] = (0, import_react.useState)("idle");
1279
1676
  const [step, setStep] = (0, import_react.useState)(null);
1280
1677
  const [txHash, setTxHash] = (0, import_react.useState)(null);
1281
1678
  const [depositId, setDepositId] = (0, import_react.useState)(null);
1282
- const [error, setError] = (0, import_react.useState)(null);
1679
+ const [lastError, setLastError] = (0, import_react.useState)(null);
1283
1680
  const [isLoading, setIsLoading] = (0, import_react.useState)(false);
1284
1681
  const lockRef = (0, import_react.useRef)(false);
1285
1682
  const reset = (0, import_react.useCallback)(() => {
1683
+ setState("idle");
1286
1684
  setStep(null);
1287
1685
  setTxHash(null);
1288
1686
  setDepositId(null);
1289
- setError(null);
1687
+ setLastError(null);
1290
1688
  setIsLoading(false);
1291
1689
  lockRef.current = false;
1292
1690
  }, []);
@@ -1295,16 +1693,25 @@ function useOfframp() {
1295
1693
  if (lockRef.current) throw new OfframpError("Deposit already in progress", "VALIDATION");
1296
1694
  lockRef.current = true;
1297
1695
  setIsLoading(true);
1298
- setError(null);
1696
+ setState("idle");
1697
+ setLastError(null);
1299
1698
  setStep(null);
1300
1699
  setTxHash(null);
1301
1700
  setDepositId(null);
1302
1701
  try {
1303
- const result = await offramp(walletClient, params, (progress) => {
1702
+ const sdk = createOfframp({
1703
+ walletClient,
1704
+ integratorId: params.integratorId ?? defaultOptions.integratorId,
1705
+ referralId: params.referralId ?? defaultOptions.referralId,
1706
+ telemetry: defaultOptions.telemetry
1707
+ });
1708
+ const result = await sdk.createDeposit(params, (progress) => {
1304
1709
  setStep(progress.step);
1710
+ setState(mapStepToState(progress.step));
1305
1711
  if (progress.txHash) setTxHash(progress.txHash);
1306
1712
  if (progress.depositId) setDepositId(progress.depositId);
1307
1713
  });
1714
+ setState("done");
1308
1715
  setDepositId(result.depositId);
1309
1716
  setTxHash(result.txHash);
1310
1717
  return result;
@@ -1313,20 +1720,23 @@ function useOfframp() {
1313
1720
  err instanceof Error ? err.message : "Offramp failed",
1314
1721
  "DEPOSIT_FAILED"
1315
1722
  );
1316
- setError(offrampError);
1317
- throw err;
1723
+ setLastError(offrampError);
1724
+ setState("error");
1725
+ throw offrampError;
1318
1726
  } finally {
1319
1727
  setIsLoading(false);
1320
1728
  lockRef.current = false;
1321
1729
  }
1322
1730
  },
1323
- []
1731
+ [defaultOptions.integratorId, defaultOptions.referralId, defaultOptions.telemetry]
1324
1732
  );
1325
1733
  return {
1734
+ state,
1326
1735
  step,
1327
1736
  txHash,
1328
1737
  depositId,
1329
- error,
1738
+ lastError,
1739
+ error: lastError,
1330
1740
  isLoading,
1331
1741
  offramp: offramp2,
1332
1742
  deposits,
@@ -1334,8 +1744,129 @@ function useOfframp() {
1334
1744
  reset
1335
1745
  };
1336
1746
  }
1747
+
1748
+ // src/hooks/usePeerExtensionRegistration.ts
1749
+ var import_react2 = require("react");
1750
+
1751
+ // src/extension.ts
1752
+ var import_sdk7 = require("@zkp2p/sdk");
1753
+
1754
+ // src/hooks/usePeerExtensionRegistration.ts
1755
+ function usePeerExtensionRegistration(platform) {
1756
+ const info = platform ? getPeerExtensionRegistrationInfo(platform.id) : null;
1757
+ const [phase, setPhase] = (0, import_react2.useState)(
1758
+ info ? "checking" : "unsupported"
1759
+ );
1760
+ const [busy, setBusy] = (0, import_react2.useState)(false);
1761
+ const [error, setError] = (0, import_react2.useState)(null);
1762
+ const mountedRef = (0, import_react2.useRef)(true);
1763
+ const providerId = info?.providerId ?? null;
1764
+ const refresh = (0, import_react2.useCallback)(async () => {
1765
+ if (!providerId) {
1766
+ setPhase("unsupported");
1767
+ return;
1768
+ }
1769
+ try {
1770
+ const state = await import_sdk7.peerExtensionSdk.getState();
1771
+ if (!mountedRef.current) return;
1772
+ setPhase(mapSdkState(state));
1773
+ } catch {
1774
+ if (!mountedRef.current) return;
1775
+ setPhase("needs_install");
1776
+ }
1777
+ }, [providerId]);
1778
+ (0, import_react2.useEffect)(() => {
1779
+ mountedRef.current = true;
1780
+ setError(null);
1781
+ setBusy(false);
1782
+ if (!providerId) {
1783
+ setPhase("unsupported");
1784
+ } else {
1785
+ setPhase("checking");
1786
+ refresh();
1787
+ }
1788
+ return () => {
1789
+ mountedRef.current = false;
1790
+ };
1791
+ }, [providerId, refresh]);
1792
+ const installExtension = (0, import_react2.useCallback)(() => {
1793
+ setError(null);
1794
+ try {
1795
+ import_sdk7.peerExtensionSdk.openInstallPage();
1796
+ } catch {
1797
+ if (typeof window !== "undefined") {
1798
+ window.open(import_sdk7.PEER_EXTENSION_CHROME_URL, "_blank", "noopener,noreferrer");
1799
+ }
1800
+ }
1801
+ }, []);
1802
+ const connectExtension = (0, import_react2.useCallback)(async () => {
1803
+ if (!providerId) return false;
1804
+ setError(null);
1805
+ setBusy(true);
1806
+ try {
1807
+ const approved = await import_sdk7.peerExtensionSdk.requestConnection();
1808
+ if (!mountedRef.current) return approved;
1809
+ if (approved) {
1810
+ setPhase("ready");
1811
+ import_sdk7.peerExtensionSdk.openSidebar(`/verify/${providerId}`);
1812
+ } else {
1813
+ setError("Connection was not approved. Try again once you've approved Peer.");
1814
+ }
1815
+ return approved;
1816
+ } catch (err) {
1817
+ if (!mountedRef.current) return false;
1818
+ setError(
1819
+ err instanceof Error ? err.message : "Couldn't request a Peer extension connection. Make sure Peer is installed and enabled."
1820
+ );
1821
+ refresh();
1822
+ return false;
1823
+ } finally {
1824
+ if (mountedRef.current) setBusy(false);
1825
+ }
1826
+ }, [providerId, refresh]);
1827
+ const openVerifySidebar = (0, import_react2.useCallback)(async () => {
1828
+ if (!providerId) return;
1829
+ setError(null);
1830
+ const state = await import_sdk7.peerExtensionSdk.getState().catch(() => "needs_install");
1831
+ if (!mountedRef.current) return;
1832
+ const nextPhase = mapSdkState(state);
1833
+ setPhase(nextPhase);
1834
+ if (nextPhase === "needs_install") {
1835
+ installExtension();
1836
+ return;
1837
+ }
1838
+ if (nextPhase === "needs_connection") {
1839
+ await connectExtension();
1840
+ return;
1841
+ }
1842
+ try {
1843
+ import_sdk7.peerExtensionSdk.openSidebar(`/verify/${providerId}`);
1844
+ } catch (err) {
1845
+ setError(
1846
+ err instanceof Error ? err.message : "Couldn't open the Peer extension. Make sure it's enabled."
1847
+ );
1848
+ refresh();
1849
+ }
1850
+ }, [connectExtension, installExtension, providerId, refresh]);
1851
+ return {
1852
+ phase,
1853
+ info,
1854
+ busy,
1855
+ error,
1856
+ refresh,
1857
+ installExtension,
1858
+ connectExtension,
1859
+ openVerifySidebar
1860
+ };
1861
+ }
1862
+ function mapSdkState(state) {
1863
+ if (state === "needs_install") return "needs_install";
1864
+ if (state === "needs_connection") return "needs_connection";
1865
+ return "ready";
1866
+ }
1337
1867
  // Annotate the CommonJS export names for ESM import in node:
1338
1868
  0 && (module.exports = {
1339
- useOfframp
1869
+ useOfframp,
1870
+ usePeerExtensionRegistration
1340
1871
  });
1341
1872
  //# sourceMappingURL=react.cjs.map