@usdctofiat/offramp 1.1.4 → 1.3.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
@@ -27,13 +27,9 @@ module.exports = __toCommonJS(react_exports);
27
27
  // src/hooks/useOfframp.ts
28
28
  var import_react = require("react");
29
29
 
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
30
  // src/config.ts
36
31
  var import_sdk = require("@zkp2p/sdk");
32
+ var SDK_VERSION = "1.2.0";
37
33
  var BASE_CHAIN_ID = 8453;
38
34
  var RUNTIME_ENV = "production";
39
35
  var API_BASE_URL = "https://api.zkp2p.xyz";
@@ -49,6 +45,7 @@ var GATING_SERVICE_ADDRESS = (0, import_sdk.getGatingServiceAddress)(
49
45
  );
50
46
  var DELEGATE_RATE_MANAGER_ID = "0x8666d6fb0f6797c56e95339fd7ca82fdd348b9db200e10a4c4aa0a0b879fc41c";
51
47
  var RATE_MANAGER_REGISTRY_ADDRESS = "0xeed7db23e724ac4590d6db6f78fda6db203535f3";
48
+ var DELEGATE_MANAGER_FEE_BPS = 10;
52
49
  var REFERRER = "galleonlabs";
53
50
  var MIN_DEPOSIT_USDC = 1;
54
51
  var MIN_ORDER_USDC = 1;
@@ -57,10 +54,33 @@ var INDEXER_INITIAL_DELAY_MS = 1e3;
57
54
  var INDEXER_MAX_DELAY_MS = 1e4;
58
55
  var PAYEE_REGISTRATION_TIMEOUT_MS = 8e3;
59
56
 
60
- // src/platforms.ts
57
+ // src/currencies.ts
61
58
  var import_sdk2 = require("@zkp2p/sdk");
59
+ function buildCurrencies() {
60
+ const entries = {};
61
+ for (const [code, info] of Object.entries(
62
+ import_sdk2.currencyInfo
63
+ )) {
64
+ entries[code] = {
65
+ code: info.currencyCode ?? code,
66
+ name: info.currencyName ?? code,
67
+ symbol: info.currencySymbol ?? code,
68
+ countryCode: info.countryCode ?? ""
69
+ };
70
+ }
71
+ return entries;
72
+ }
73
+ var CURRENCIES = buildCurrencies();
74
+
75
+ // src/deposit.ts
76
+ var import_viem3 = require("viem");
77
+ var import_chains3 = require("viem/chains");
78
+ var import_sdk6 = require("@zkp2p/sdk");
79
+
80
+ // src/platforms.ts
81
+ var import_sdk3 = require("@zkp2p/sdk");
62
82
  var import_zod = require("zod");
63
- var PAYMENT_CATALOG = (0, import_sdk2.getPaymentMethodsCatalog)(BASE_CHAIN_ID, RUNTIME_ENV);
83
+ var PAYMENT_CATALOG = (0, import_sdk3.getPaymentMethodsCatalog)(BASE_CHAIN_ID, RUNTIME_ENV);
64
84
  var ZELLE_HASH_LOOKUP_NAMES = ["zelle", "zelle-bofa", "zelle-chase", "zelle-citi"];
65
85
  var FALLBACK_CURRENCIES = {
66
86
  venmo: ["USD"],
@@ -139,8 +159,8 @@ function gatherCatalogHashes(platform) {
139
159
  function resolveSupportedCurrencies(platform) {
140
160
  const codes = /* @__PURE__ */ new Set();
141
161
  for (const hash of gatherCatalogHashes(platform)) {
142
- const info = (0, import_sdk2.getCurrencyInfoFromHash)(hash);
143
- if (info?.currencyCode && import_sdk2.currencyInfo[info.currencyCode]) {
162
+ const info = (0, import_sdk3.getCurrencyInfoFromHash)(hash);
163
+ if (info?.currencyCode && import_sdk3.currencyInfo[info.currencyCode]) {
144
164
  codes.add(info.currencyCode);
145
165
  }
146
166
  }
@@ -300,7 +320,7 @@ function getPaymentMethodHash(platform) {
300
320
  const catalogEntry = PAYMENT_CATALOG[name];
301
321
  if (catalogEntry?.paymentMethodHash) return catalogEntry.paymentMethodHash;
302
322
  if (name === "zelle" || name.startsWith("zelle-")) return resolveCanonicalZelleHash();
303
- const sdkHash = (0, import_sdk2.resolvePaymentMethodHash)(name);
323
+ const sdkHash = (0, import_sdk3.resolvePaymentMethodHash)(name);
304
324
  return sdkHash ? sdkHash : null;
305
325
  }
306
326
  function getPaymentMethodHashes(platform) {
@@ -309,7 +329,7 @@ function getPaymentMethodHashes(platform) {
309
329
  return hash ? [hash] : [];
310
330
  }
311
331
  const hashes = /* @__PURE__ */ new Set();
312
- const generic = (0, import_sdk2.resolvePaymentMethodHash)("zelle");
332
+ const generic = (0, import_sdk3.resolvePaymentMethodHash)("zelle");
313
333
  if (generic) hashes.add(generic);
314
334
  const canonical = resolveCanonicalZelleHash();
315
335
  if (canonical) hashes.add(canonical);
@@ -718,9 +738,9 @@ var WhitelistPreIntentHook_default = [
718
738
  ];
719
739
 
720
740
  // src/sdk-client.ts
721
- var import_sdk3 = require("@zkp2p/sdk");
741
+ var import_sdk4 = require("@zkp2p/sdk");
722
742
  function createSdkClient(walletClient) {
723
- return new import_sdk3.OfframpClient({
743
+ return new import_sdk4.OfframpClient({
724
744
  walletClient,
725
745
  chainId: BASE_CHAIN_ID,
726
746
  runtimeEnv: RUNTIME_ENV,
@@ -809,13 +829,13 @@ async function readIsWhitelisted(escrow, depositId, taker) {
809
829
  // src/queries.ts
810
830
  var import_viem2 = require("viem");
811
831
  var import_chains2 = require("viem/chains");
812
- var import_sdk4 = require("@zkp2p/sdk");
832
+ var import_sdk5 = require("@zkp2p/sdk");
813
833
  var indexerService = null;
814
834
  function getIndexerService() {
815
835
  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);
836
+ const endpoint = (0, import_sdk5.defaultIndexerEndpoint)("PRODUCTION");
837
+ const client = new import_sdk5.IndexerClient(endpoint);
838
+ indexerService = new import_sdk5.IndexerDepositService(client);
819
839
  }
820
840
  return indexerService;
821
841
  }
@@ -849,7 +869,7 @@ function resolveMethodNames(hashes) {
849
869
  return Array.from(names);
850
870
  }
851
871
  function mapDeposit(d) {
852
- const delegationState = (0, import_sdk4.classifyDelegationState)(
872
+ const delegationState = (0, import_sdk5.classifyDelegationState)(
853
873
  d.rateManagerId ?? void 0,
854
874
  d.rateManagerAddress ?? void 0,
855
875
  DELEGATE_RATE_MANAGER_ID,
@@ -866,7 +886,7 @@ function mapDeposit(d) {
866
886
  fulfilledIntents: d.fulfilledIntents ?? 0,
867
887
  paymentMethods: resolveMethodNames(d.paymentMethods),
868
888
  currencies: d.currencies.map((c) => {
869
- const info = (0, import_sdk4.getCurrencyInfoFromHash)(c.currencyCode);
889
+ const info = (0, import_sdk5.getCurrencyInfoFromHash)(c.currencyCode);
870
890
  return info?.currencyCode ?? c.currencyCode;
871
891
  }),
872
892
  rateSource: d.currencies[0]?.rateSource || "unknown",
@@ -905,6 +925,105 @@ async function close(walletClient, depositId, escrowAddress) {
905
925
  return extractTxHash(result);
906
926
  }
907
927
 
928
+ // src/telemetry.ts
929
+ var SDK_EVENTS_ENDPOINT = "https://usdctofiat.xyz/api/sdk-events";
930
+ var MAX_BATCH_SIZE = 50;
931
+ var FLUSH_DELAY_MS = 180;
932
+ var TELEMETRY_TIMEOUT_MS = 2e3;
933
+ var queuedEvents = [];
934
+ var flushTimer = null;
935
+ var flushInFlight = false;
936
+ function sanitizeAttributionId(raw) {
937
+ if (typeof raw !== "string") return void 0;
938
+ const stripped = raw.trim().replace(/[^a-zA-Z0-9_-]/g, "");
939
+ if (!stripped) return void 0;
940
+ if (stripped.length > 64) return void 0;
941
+ return stripped;
942
+ }
943
+ function scheduleFlush() {
944
+ if (flushTimer != null) return;
945
+ flushTimer = setTimeout(() => {
946
+ flushTimer = null;
947
+ void flushQueue();
948
+ }, FLUSH_DELAY_MS);
949
+ }
950
+ async function postBatch(batch) {
951
+ if (!batch.length) return;
952
+ const body = JSON.stringify(batch);
953
+ try {
954
+ if (typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function") {
955
+ const blob = new Blob([body], { type: "application/json" });
956
+ const accepted = navigator.sendBeacon(SDK_EVENTS_ENDPOINT, blob);
957
+ if (accepted) return;
958
+ }
959
+ } catch {
960
+ }
961
+ try {
962
+ const controller = typeof AbortController !== "undefined" ? new AbortController() : null;
963
+ const timeout = controller ? setTimeout(() => controller.abort(), TELEMETRY_TIMEOUT_MS) : null;
964
+ await fetch(SDK_EVENTS_ENDPOINT, {
965
+ method: "POST",
966
+ headers: { "Content-Type": "application/json" },
967
+ body,
968
+ keepalive: true,
969
+ signal: controller?.signal
970
+ }).catch(() => {
971
+ });
972
+ if (timeout) clearTimeout(timeout);
973
+ } catch {
974
+ }
975
+ }
976
+ async function flushQueue() {
977
+ if (flushInFlight) return;
978
+ if (!queuedEvents.length) return;
979
+ flushInFlight = true;
980
+ try {
981
+ while (queuedEvents.length > 0) {
982
+ const batch = queuedEvents.slice(0, MAX_BATCH_SIZE);
983
+ queuedEvents = queuedEvents.slice(batch.length);
984
+ await postBatch(batch);
985
+ }
986
+ } finally {
987
+ flushInFlight = false;
988
+ }
989
+ }
990
+ function emitEvent(name, payload) {
991
+ try {
992
+ queuedEvents.push({
993
+ name,
994
+ payload,
995
+ ts: Date.now()
996
+ });
997
+ if (queuedEvents.length >= MAX_BATCH_SIZE) {
998
+ void flushQueue();
999
+ return;
1000
+ }
1001
+ scheduleFlush();
1002
+ } catch {
1003
+ }
1004
+ }
1005
+ function createTelemetryContext(options) {
1006
+ const integratorId = sanitizeAttributionId(options.integratorId);
1007
+ const referralId = sanitizeAttributionId(options.referralId);
1008
+ const enabled = options.telemetry !== false;
1009
+ const sdkVersion = options.sdkVersion;
1010
+ return {
1011
+ enabled,
1012
+ sdkVersion,
1013
+ integratorId,
1014
+ referralId,
1015
+ emit(name, payload = {}) {
1016
+ if (!enabled) return;
1017
+ emitEvent(name, {
1018
+ sdkVersion,
1019
+ integratorId,
1020
+ referralId,
1021
+ ...payload
1022
+ });
1023
+ }
1024
+ };
1025
+ }
1026
+
908
1027
  // src/deposit.ts
909
1028
  function usdcToUnits(amount) {
910
1029
  return (0, import_viem3.parseUnits)(amount, 6);
@@ -978,7 +1097,7 @@ function attachOracleConfig(entries, conversionRates) {
978
1097
  (group, gi) => group.map((entry, ci) => {
979
1098
  const currency = conversionRates[gi]?.[ci]?.currency;
980
1099
  if (!currency) return entry;
981
- const oracleConfig = (0, import_sdk5.getSpreadOracleConfig)(currency);
1100
+ const oracleConfig = (0, import_sdk6.getSpreadOracleConfig)(currency);
982
1101
  if (!oracleConfig) return entry;
983
1102
  return {
984
1103
  ...entry,
@@ -1022,271 +1141,491 @@ async function findUndelegatedDeposit(walletAddress) {
1022
1141
  if (remaining === 0n) return false;
1023
1142
  return (d.rateManagerId ?? "").toLowerCase() !== delegateIdLower;
1024
1143
  });
1025
- undelegated.sort((a, b) => Number(BigInt(b.depositId) - BigInt(a.depositId)));
1144
+ undelegated.sort((a, b) => {
1145
+ const left = BigInt(a.depositId);
1146
+ const right = BigInt(b.depositId);
1147
+ if (left === right) return 0;
1148
+ return left > right ? -1 : 1;
1149
+ });
1026
1150
  return undelegated[0] ?? null;
1027
1151
  } catch {
1028
1152
  return null;
1029
1153
  }
1030
1154
  }
1031
- async function offramp(walletClient, params, onProgress) {
1155
+ async function offramp(walletClient, params, onProgress, telemetryContext) {
1032
1156
  const { amount, platform, currency, identifier } = params;
1033
1157
  const platformId = platform.id;
1034
1158
  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] };
1159
+ const telemetry = telemetryContext ?? createTelemetryContext({
1160
+ sdkVersion: SDK_VERSION,
1161
+ telemetry: true,
1162
+ integratorId: params.integratorId,
1163
+ referralId: params.referralId
1164
+ });
1165
+ const flowStartMs = Date.now();
1166
+ const emitProgress = (progress) => {
1167
+ onProgress?.(progress);
1168
+ telemetry.emit("sdk.createDeposit.progress", {
1169
+ step: progress.step,
1170
+ txHash: progress.txHash
1171
+ });
1172
+ };
1173
+ try {
1174
+ if (!walletClient.account?.address) {
1175
+ throw new OfframpError("Wallet client has no account. Connect a wallet first.", "VALIDATION");
1176
+ }
1177
+ const walletAddress = walletClient.account.address.toLowerCase();
1178
+ telemetry.emit("sdk.createDeposit.start", {
1179
+ platform: platformId,
1180
+ currency: currencyCode,
1181
+ amount,
1182
+ wallet: walletAddress
1183
+ });
1184
+ const amt = parseFloat(amount);
1185
+ if (!Number.isFinite(amt) || amt < MIN_DEPOSIT_USDC) {
1186
+ throw new OfframpError(`Minimum deposit is ${MIN_DEPOSIT_USDC} USDC`, "VALIDATION");
1187
+ }
1188
+ if (!platform.currencies.includes(currencyCode)) {
1189
+ throw new OfframpError(`${currencyCode} is not supported on ${platform.name}`, "UNSUPPORTED");
1190
+ }
1191
+ const validation = platform.validate(identifier);
1192
+ if (!validation.valid) {
1193
+ throw new OfframpError(validation.error || "Invalid identifier", "VALIDATION");
1194
+ }
1195
+ const normalizedIdentifier = validation.normalized;
1196
+ const methodHash = getPaymentMethodHash(platformId);
1197
+ if (!methodHash) {
1198
+ throw new OfframpError(`${platform.name} is not currently supported`, "UNSUPPORTED");
1199
+ }
1200
+ const existing = await findUndelegatedDeposit(walletAddress);
1201
+ if (existing) {
1202
+ emitProgress({ step: "resuming", depositId: existing.depositId });
1203
+ const client2 = createSdkClient(walletClient);
1204
+ const txOverrides2 = { referrer: [REFERRER] };
1205
+ try {
1206
+ const result2 = await client2.setRateManager({
1207
+ depositId: BigInt(existing.depositId),
1208
+ rateManagerAddress: RATE_MANAGER_REGISTRY_ADDRESS,
1209
+ rateManagerId: DELEGATE_RATE_MANAGER_ID,
1210
+ escrowAddress: existing.escrowAddress,
1211
+ txOverrides: txOverrides2
1212
+ });
1213
+ const txHash = extractTxHash2(result2);
1214
+ emitProgress({ step: "done", txHash, depositId: existing.depositId });
1215
+ const resumedResult = { depositId: existing.depositId, txHash, resumed: true };
1216
+ telemetry.emit("sdk.createDeposit.success", {
1217
+ depositId: resumedResult.depositId,
1218
+ txHash: resumedResult.txHash,
1219
+ wallet: walletAddress,
1220
+ resumed: true,
1221
+ wallDurationMs: Date.now() - flowStartMs
1222
+ });
1223
+ return resumedResult;
1224
+ } catch (err) {
1225
+ if (isUserCancellation(err))
1226
+ throw new OfframpError("User cancelled", "USER_CANCELLED", "resuming", err);
1227
+ throw new OfframpError(
1228
+ "Found undelegated deposit but delegation failed. Try again.",
1229
+ "DELEGATION_FAILED",
1230
+ "resuming",
1231
+ err,
1232
+ { depositId: existing.depositId, txHash: existing.txHash }
1233
+ );
1234
+ }
1235
+ }
1236
+ const truncatedAmount = amt.toFixed(6).replace(/\.?0+$/, "");
1237
+ const amountUnits = usdcToUnits(truncatedAmount);
1238
+ const minUnits = usdcToUnits(String(MIN_ORDER_USDC));
1239
+ const maxUnits = usdcToUnits(String(Math.min(amt, 2500)));
1060
1240
  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
1241
+ const publicClient = (0, import_viem3.createPublicClient)({ chain: import_chains3.base, transport: (0, import_viem3.http)(BASE_RPC_URL) });
1242
+ const balance = await publicClient.readContract({
1243
+ address: USDC_ADDRESS,
1244
+ abi: [
1245
+ {
1246
+ name: "balanceOf",
1247
+ type: "function",
1248
+ stateMutability: "view",
1249
+ inputs: [{ name: "account", type: "address" }],
1250
+ outputs: [{ name: "", type: "uint256" }]
1251
+ }
1252
+ ],
1253
+ functionName: "balanceOf",
1254
+ args: [walletAddress]
1255
+ });
1256
+ if (balance < amountUnits) {
1257
+ const available = Number((0, import_viem3.formatUnits)(balance, 6));
1258
+ throw new OfframpError(
1259
+ `Insufficient USDC balance. Have ${available.toFixed(2)}, need ${truncatedAmount}.`,
1260
+ "VALIDATION"
1261
+ );
1262
+ }
1263
+ } catch (err) {
1264
+ if (err instanceof OfframpError) throw err;
1265
+ }
1266
+ const client = createSdkClient(walletClient);
1267
+ const txOverrides = { referrer: [REFERRER] };
1268
+ emitProgress({ step: "approving" });
1269
+ try {
1270
+ await client.ensureAllowance({
1271
+ token: USDC_ADDRESS,
1272
+ amount: amountUnits,
1273
+ escrowAddress: ESCROW_ADDRESS,
1274
+ maxApprove: false,
1275
+ txOverrides
1067
1276
  });
1068
- const txHash = extractTxHash2(result);
1069
- onProgress?.({ step: "done", txHash, depositId: existing.depositId });
1070
- return { depositId: existing.depositId, txHash, resumed: true };
1071
1277
  } catch (err) {
1072
1278
  if (isUserCancellation(err))
1073
- throw new OfframpError("User cancelled", "USER_CANCELLED", "resuming", err);
1279
+ throw new OfframpError("User cancelled", "USER_CANCELLED", "approving", err);
1280
+ const detail = err instanceof Error ? err.message : String(err);
1074
1281
  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 }
1282
+ `USDC approval failed: ${detail}`,
1283
+ "APPROVAL_FAILED",
1284
+ "approving",
1285
+ err
1080
1286
  );
1081
1287
  }
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));
1288
+ emitProgress({ step: "registering" });
1289
+ let hashedOnchainId;
1290
+ try {
1291
+ const canonicalName = platformId.startsWith("zelle") ? "zelle" : platformId;
1292
+ const depositData = buildDepositData(platformId, normalizedIdentifier);
1293
+ hashedOnchainId = await registerPayeeDetails(canonicalName, depositData);
1294
+ } catch (err) {
1105
1295
  throw new OfframpError(
1106
- `Insufficient USDC balance. Have ${available.toFixed(2)}, need ${truncatedAmount}.`,
1107
- "VALIDATION"
1296
+ "Payee registration failed",
1297
+ "REGISTRATION_FAILED",
1298
+ "registering",
1299
+ err
1108
1300
  );
1109
1301
  }
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") {
1302
+ emitProgress({ step: "depositing" });
1303
+ const conversionRates = [
1304
+ [{ currency: currencyCode, conversionRate: "1" }]
1305
+ ];
1306
+ const baseCurrenciesOverride = (0, import_sdk6.mapConversionRatesToOnchainMinRate)(conversionRates, 1);
1307
+ const currenciesOverride = attachOracleConfig(baseCurrenciesOverride, conversionRates);
1308
+ let hash;
1184
1309
  try {
1185
- const receipt = await receiptClient.waitForTransactionReceipt({ hash, confirmations: 1 });
1186
- depositId = extractDepositIdFromLogs(receipt.logs) || "";
1187
- } catch {
1310
+ const result2 = await client.createDeposit({
1311
+ token: USDC_ADDRESS,
1312
+ amount: amountUnits,
1313
+ retainOnEmpty: false,
1314
+ intentAmountRange: { min: minUnits, max: maxUnits },
1315
+ processorNames: [platformId],
1316
+ conversionRates,
1317
+ payeeDetailsHashes: [hashedOnchainId],
1318
+ paymentMethodsOverride: [methodHash],
1319
+ paymentMethodDataOverride: [
1320
+ {
1321
+ intentGatingService: GATING_SERVICE_ADDRESS,
1322
+ payeeDetails: hashedOnchainId,
1323
+ data: "0x"
1324
+ }
1325
+ ],
1326
+ currenciesOverride,
1327
+ escrowAddress: ESCROW_ADDRESS,
1328
+ txOverrides
1329
+ });
1330
+ if (!result2?.hash) throw new Error("No transaction hash returned");
1331
+ hash = result2.hash;
1332
+ } catch (err) {
1333
+ if (isUserCancellation(err))
1334
+ throw new OfframpError("User cancelled", "USER_CANCELLED", "depositing", err);
1335
+ const detail = err instanceof Error ? err.message : String(err);
1336
+ throw new OfframpError(
1337
+ `Deposit transaction failed: ${detail}`,
1338
+ "DEPOSIT_FAILED",
1339
+ "depositing",
1340
+ err
1341
+ );
1188
1342
  }
1189
- }
1190
- if (!depositId) {
1191
- let delay = INDEXER_INITIAL_DELAY_MS;
1192
- for (let attempt = 0; attempt < INDEXER_MAX_ATTEMPTS && !depositId; attempt++) {
1343
+ emitProgress({ step: "confirming", txHash: hash });
1344
+ let depositId = "";
1345
+ const receiptClient = client;
1346
+ if (typeof receiptClient.waitForTransactionReceipt === "function") {
1193
1347
  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
- }
1348
+ const receipt = await receiptClient.waitForTransactionReceipt({ hash, confirmations: 1 });
1349
+ depositId = extractDepositIdFromLogs(receipt.logs) || "";
1203
1350
  } catch {
1204
1351
  }
1205
- await new Promise((r) => setTimeout(r, delay));
1206
- delay = Math.min(INDEXER_MAX_DELAY_MS, Math.floor(delay * 1.7));
1207
1352
  }
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)) {
1353
+ if (!depositId) {
1354
+ let delay = INDEXER_INITIAL_DELAY_MS;
1355
+ for (let attempt = 0; attempt < INDEXER_MAX_ATTEMPTS && !depositId; attempt++) {
1356
+ try {
1357
+ const deps = await client.indexer.getDepositsWithRelations(
1358
+ { depositor: walletAddress },
1359
+ { limit: 25 }
1360
+ );
1361
+ const hit = deps.find((d) => (d?.txHash || "").toLowerCase() === hash.toLowerCase());
1362
+ if (hit) {
1363
+ depositId = String(hit.depositId);
1364
+ break;
1365
+ }
1366
+ } catch {
1367
+ }
1368
+ await new Promise((r) => setTimeout(r, delay));
1369
+ delay = Math.min(INDEXER_MAX_DELAY_MS, Math.floor(delay * 1.7));
1370
+ }
1371
+ }
1372
+ if (!depositId) {
1229
1373
  throw new OfframpError(
1230
- "User cancelled delegation",
1231
- "USER_CANCELLED",
1374
+ "Deposit created on-chain but could not confirm deposit ID. Your funds are safe. Call offramp() again to resume delegation.",
1375
+ "CONFIRMATION_FAILED",
1376
+ "confirming",
1377
+ void 0,
1378
+ { txHash: hash }
1379
+ );
1380
+ }
1381
+ emitProgress({ step: "delegating", txHash: hash, depositId });
1382
+ try {
1383
+ await client.setRateManager({
1384
+ depositId: BigInt(depositId),
1385
+ rateManagerAddress: RATE_MANAGER_REGISTRY_ADDRESS,
1386
+ rateManagerId: DELEGATE_RATE_MANAGER_ID,
1387
+ escrowAddress: ESCROW_ADDRESS,
1388
+ txOverrides
1389
+ });
1390
+ } catch (delegationError) {
1391
+ if (isUserCancellation(delegationError)) {
1392
+ throw new OfframpError(
1393
+ "User cancelled delegation",
1394
+ "USER_CANCELLED",
1395
+ "delegating",
1396
+ delegationError,
1397
+ { txHash: hash, depositId }
1398
+ );
1399
+ }
1400
+ throw new OfframpError(
1401
+ "Deposit created but delegation failed. Call offramp() again to resume delegation.",
1402
+ "DELEGATION_FAILED",
1232
1403
  "delegating",
1233
1404
  delegationError,
1234
1405
  { txHash: hash, depositId }
1235
1406
  );
1236
1407
  }
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)) {
1408
+ let otcLink;
1409
+ if (params.otcTaker) {
1410
+ emitProgress({ step: "restricting", txHash: hash, depositId });
1411
+ try {
1412
+ const otcResult = await enableOtc(walletClient, depositId, params.otcTaker);
1413
+ otcLink = otcResult.otcLink;
1414
+ } catch (otcError) {
1415
+ if (isUserCancellation(otcError)) {
1416
+ throw new OfframpError(
1417
+ "User cancelled OTC restriction",
1418
+ "USER_CANCELLED",
1419
+ "restricting",
1420
+ otcError,
1421
+ {
1422
+ txHash: hash,
1423
+ depositId
1424
+ }
1425
+ );
1426
+ }
1253
1427
  throw new OfframpError(
1254
- "User cancelled OTC restriction",
1255
- "USER_CANCELLED",
1428
+ "Deposit created and delegated but OTC restriction failed. Use enableOtc() to retry.",
1429
+ "DEPOSIT_FAILED",
1256
1430
  "restricting",
1257
1431
  otcError,
1258
- {
1259
- txHash: hash,
1260
- depositId
1261
- }
1432
+ { txHash: hash, depositId }
1262
1433
  );
1263
1434
  }
1435
+ }
1436
+ emitProgress({ step: "done", txHash: hash, depositId });
1437
+ const result = { depositId, txHash: hash, resumed: false, otcLink };
1438
+ telemetry.emit("sdk.createDeposit.success", {
1439
+ depositId: result.depositId,
1440
+ txHash: result.txHash,
1441
+ wallet: walletAddress,
1442
+ resumed: false,
1443
+ wallDurationMs: Date.now() - flowStartMs
1444
+ });
1445
+ return result;
1446
+ } catch (error) {
1447
+ const known = error instanceof OfframpError ? error : null;
1448
+ telemetry.emit("sdk.createDeposit.error", {
1449
+ code: known?.code ?? "DEPOSIT_FAILED",
1450
+ step: known?.step ?? "unknown"
1451
+ });
1452
+ throw error;
1453
+ }
1454
+ }
1455
+
1456
+ // src/offramp.ts
1457
+ var IDEMPOTENCY_TTL_MS = 10 * 60 * 1e3;
1458
+ var IDEMPOTENCY_STORAGE_PREFIX = "offramp:idempotency:";
1459
+ function getWalletAddress(walletClient) {
1460
+ const address = walletClient.account?.address;
1461
+ if (!address) {
1462
+ throw new OfframpError("Wallet client has no account. Connect a wallet first.", "VALIDATION");
1463
+ }
1464
+ return address.toLowerCase();
1465
+ }
1466
+ function getIdempotencyStorageKey(walletAddress, idempotencyKey) {
1467
+ return `${IDEMPOTENCY_STORAGE_PREFIX}${walletAddress}:${idempotencyKey}`;
1468
+ }
1469
+ function readIdempotencyResult(storageKey) {
1470
+ if (typeof sessionStorage === "undefined") return null;
1471
+ try {
1472
+ const raw = sessionStorage.getItem(storageKey);
1473
+ if (!raw) return null;
1474
+ const parsed = JSON.parse(raw);
1475
+ const expiresAt = typeof parsed.expiresAt === "number" && Number.isFinite(parsed.expiresAt) ? parsed.expiresAt : 0;
1476
+ if (expiresAt <= Date.now()) {
1477
+ sessionStorage.removeItem(storageKey);
1478
+ return null;
1479
+ }
1480
+ const result = parsed.result;
1481
+ if (!result?.depositId || !result.txHash) {
1482
+ sessionStorage.removeItem(storageKey);
1483
+ return null;
1484
+ }
1485
+ return result;
1486
+ } catch {
1487
+ return null;
1488
+ }
1489
+ }
1490
+ function writeIdempotencyResult(storageKey, result) {
1491
+ if (typeof sessionStorage === "undefined") return;
1492
+ try {
1493
+ const record = {
1494
+ result,
1495
+ expiresAt: Date.now() + IDEMPOTENCY_TTL_MS
1496
+ };
1497
+ sessionStorage.setItem(storageKey, JSON.stringify(record));
1498
+ } catch {
1499
+ }
1500
+ }
1501
+ function createPerFlowTelemetry(base4, integratorId, referralId) {
1502
+ return createTelemetryContext({
1503
+ sdkVersion: base4.sdkVersion,
1504
+ telemetry: base4.enabled,
1505
+ integratorId: sanitizeAttributionId(integratorId) ?? base4.integratorId,
1506
+ referralId: sanitizeAttributionId(referralId) ?? base4.referralId
1507
+ });
1508
+ }
1509
+ function normalizeCurrencyEntry(code) {
1510
+ const fromCatalog = CURRENCIES[code];
1511
+ if (fromCatalog) return fromCatalog;
1512
+ return {
1513
+ code,
1514
+ name: code,
1515
+ symbol: code,
1516
+ countryCode: ""
1517
+ };
1518
+ }
1519
+ function buildCurrencyGroups() {
1520
+ const grouped = {};
1521
+ for (const platform of Object.values(PLATFORMS)) {
1522
+ grouped[platform.id] = platform.currencies.map(normalizeCurrencyEntry);
1523
+ }
1524
+ return grouped;
1525
+ }
1526
+ var Offramp = class {
1527
+ walletClient;
1528
+ telemetry;
1529
+ constructor(options) {
1530
+ this.walletClient = options.walletClient;
1531
+ this.telemetry = createTelemetryContext({
1532
+ sdkVersion: SDK_VERSION,
1533
+ telemetry: options.telemetry,
1534
+ integratorId: options.integratorId,
1535
+ referralId: options.referralId
1536
+ });
1537
+ this.telemetry.emit("sdk.init", {
1538
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "unknown"
1539
+ });
1540
+ }
1541
+ async createDeposit(params, onProgress) {
1542
+ const flowTelemetry = createPerFlowTelemetry(
1543
+ this.telemetry,
1544
+ params.integratorId,
1545
+ params.referralId
1546
+ );
1547
+ const sanitizedParams = {
1548
+ ...params,
1549
+ integratorId: flowTelemetry.integratorId,
1550
+ referralId: flowTelemetry.referralId
1551
+ };
1552
+ const sanitizedKey = sanitizeAttributionId(params.idempotencyKey);
1553
+ const walletAddress = sanitizedKey ? getWalletAddress(this.walletClient) : null;
1554
+ const storageKey = walletAddress && sanitizedKey ? getIdempotencyStorageKey(walletAddress, sanitizedKey) : null;
1555
+ if (storageKey) {
1556
+ const existing = readIdempotencyResult(storageKey);
1557
+ if (existing) return existing;
1558
+ }
1559
+ const result = await offramp(
1560
+ this.walletClient,
1561
+ sanitizedParams,
1562
+ onProgress,
1563
+ flowTelemetry
1564
+ );
1565
+ if (storageKey) {
1566
+ writeIdempotencyResult(storageKey, result);
1567
+ }
1568
+ return result;
1569
+ }
1570
+ getQuote(input) {
1571
+ const amountUsdc = Number(input.amount);
1572
+ if (!Number.isFinite(amountUsdc) || amountUsdc <= 0) {
1573
+ throw new OfframpError("Amount must be a positive number", "VALIDATION");
1574
+ }
1575
+ if (!input.platform.currencies.includes(input.currency.code)) {
1264
1576
  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 }
1577
+ `${input.currency.code} is not supported on ${input.platform.name}`,
1578
+ "UNSUPPORTED"
1270
1579
  );
1271
1580
  }
1581
+ const vaultSpreadBps = 0;
1582
+ const spreadFactor = Math.max(0, 1 - vaultSpreadBps / 1e4);
1583
+ const expectedFiat = amountUsdc * spreadFactor;
1584
+ const effectiveRate = amountUsdc === 0 ? 0 : expectedFiat / amountUsdc;
1585
+ return {
1586
+ amountUsdc,
1587
+ expectedFiat,
1588
+ effectiveRate,
1589
+ vaultSpreadBps
1590
+ };
1272
1591
  }
1273
- onProgress?.({ step: "done", txHash: hash, depositId });
1274
- return { depositId, txHash: hash, resumed: false, otcLink };
1592
+ getVaultStatus() {
1593
+ return {
1594
+ rateManagerId: DELEGATE_RATE_MANAGER_ID,
1595
+ rateManagerAddress: RATE_MANAGER_REGISTRY_ADDRESS,
1596
+ feeRateBps: DELEGATE_MANAGER_FEE_BPS,
1597
+ escrow: ESCROW_ADDRESS,
1598
+ isActive: true
1599
+ };
1600
+ }
1601
+ listCurrencies() {
1602
+ return buildCurrencyGroups();
1603
+ }
1604
+ };
1605
+ function createOfframp(options) {
1606
+ return new Offramp(options);
1275
1607
  }
1276
1608
 
1277
1609
  // src/hooks/useOfframp.ts
1278
- function useOfframp() {
1610
+ function mapStepToState(step) {
1611
+ if (step === "resuming" || step === "restricting") return "delegating";
1612
+ if (step === "done") return "done";
1613
+ return step;
1614
+ }
1615
+ function useOfframp(defaultOptions = {}) {
1616
+ const [state, setState] = (0, import_react.useState)("idle");
1279
1617
  const [step, setStep] = (0, import_react.useState)(null);
1280
1618
  const [txHash, setTxHash] = (0, import_react.useState)(null);
1281
1619
  const [depositId, setDepositId] = (0, import_react.useState)(null);
1282
- const [error, setError] = (0, import_react.useState)(null);
1620
+ const [lastError, setLastError] = (0, import_react.useState)(null);
1283
1621
  const [isLoading, setIsLoading] = (0, import_react.useState)(false);
1284
1622
  const lockRef = (0, import_react.useRef)(false);
1285
1623
  const reset = (0, import_react.useCallback)(() => {
1624
+ setState("idle");
1286
1625
  setStep(null);
1287
1626
  setTxHash(null);
1288
1627
  setDepositId(null);
1289
- setError(null);
1628
+ setLastError(null);
1290
1629
  setIsLoading(false);
1291
1630
  lockRef.current = false;
1292
1631
  }, []);
@@ -1295,16 +1634,25 @@ function useOfframp() {
1295
1634
  if (lockRef.current) throw new OfframpError("Deposit already in progress", "VALIDATION");
1296
1635
  lockRef.current = true;
1297
1636
  setIsLoading(true);
1298
- setError(null);
1637
+ setState("idle");
1638
+ setLastError(null);
1299
1639
  setStep(null);
1300
1640
  setTxHash(null);
1301
1641
  setDepositId(null);
1302
1642
  try {
1303
- const result = await offramp(walletClient, params, (progress) => {
1643
+ const sdk = createOfframp({
1644
+ walletClient,
1645
+ integratorId: params.integratorId ?? defaultOptions.integratorId,
1646
+ referralId: params.referralId ?? defaultOptions.referralId,
1647
+ telemetry: defaultOptions.telemetry
1648
+ });
1649
+ const result = await sdk.createDeposit(params, (progress) => {
1304
1650
  setStep(progress.step);
1651
+ setState(mapStepToState(progress.step));
1305
1652
  if (progress.txHash) setTxHash(progress.txHash);
1306
1653
  if (progress.depositId) setDepositId(progress.depositId);
1307
1654
  });
1655
+ setState("done");
1308
1656
  setDepositId(result.depositId);
1309
1657
  setTxHash(result.txHash);
1310
1658
  return result;
@@ -1313,20 +1661,23 @@ function useOfframp() {
1313
1661
  err instanceof Error ? err.message : "Offramp failed",
1314
1662
  "DEPOSIT_FAILED"
1315
1663
  );
1316
- setError(offrampError);
1317
- throw err;
1664
+ setLastError(offrampError);
1665
+ setState("error");
1666
+ throw offrampError;
1318
1667
  } finally {
1319
1668
  setIsLoading(false);
1320
1669
  lockRef.current = false;
1321
1670
  }
1322
1671
  },
1323
- []
1672
+ [defaultOptions.integratorId, defaultOptions.referralId, defaultOptions.telemetry]
1324
1673
  );
1325
1674
  return {
1675
+ state,
1326
1676
  step,
1327
1677
  txHash,
1328
1678
  depositId,
1329
- error,
1679
+ lastError,
1680
+ error: lastError,
1330
1681
  isLoading,
1331
1682
  offramp: offramp2,
1332
1683
  deposits,