@usdctofiat/offramp 1.1.3 → 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.
@@ -1,14 +1,6 @@
1
- // src/platforms.ts
2
- import {
3
- currencyInfo,
4
- getCurrencyInfoFromHash,
5
- getPaymentMethodsCatalog,
6
- resolvePaymentMethodHash
7
- } from "@zkp2p/sdk";
8
- import { z } from "zod";
9
-
10
1
  // src/config.ts
11
2
  import { getContracts, getGatingServiceAddress } from "@zkp2p/sdk";
3
+ var SDK_VERSION = "1.2.0";
12
4
  var BASE_CHAIN_ID = 8453;
13
5
  var RUNTIME_ENV = "production";
14
6
  var API_BASE_URL = "https://api.zkp2p.xyz";
@@ -24,6 +16,7 @@ var GATING_SERVICE_ADDRESS = getGatingServiceAddress(
24
16
  );
25
17
  var DELEGATE_RATE_MANAGER_ID = "0x8666d6fb0f6797c56e95339fd7ca82fdd348b9db200e10a4c4aa0a0b879fc41c";
26
18
  var RATE_MANAGER_REGISTRY_ADDRESS = "0xeed7db23e724ac4590d6db6f78fda6db203535f3";
19
+ var DELEGATE_MANAGER_FEE_BPS = 10;
27
20
  var REFERRER = "galleonlabs";
28
21
  var MIN_DEPOSIT_USDC = 1;
29
22
  var MIN_ORDER_USDC = 1;
@@ -33,6 +26,13 @@ var INDEXER_MAX_DELAY_MS = 1e4;
33
26
  var PAYEE_REGISTRATION_TIMEOUT_MS = 8e3;
34
27
 
35
28
  // src/platforms.ts
29
+ import {
30
+ currencyInfo,
31
+ getCurrencyInfoFromHash,
32
+ getPaymentMethodsCatalog,
33
+ resolvePaymentMethodHash
34
+ } from "@zkp2p/sdk";
35
+ import { z } from "zod";
36
36
  var PAYMENT_CATALOG = getPaymentMethodsCatalog(BASE_CHAIN_ID, RUNTIME_ENV);
37
37
  var ZELLE_HASH_LOOKUP_NAMES = ["zelle", "zelle-bofa", "zelle-chase", "zelle-citi"];
38
38
  var FALLBACK_CURRENCIES = {
@@ -344,7 +344,8 @@ function isUserCancellation(error) {
344
344
  }
345
345
 
346
346
  // src/otc.ts
347
- import { isAddress, zeroAddress } from "viem";
347
+ import { createPublicClient, http, isAddress, zeroAddress } from "viem";
348
+ import { base } from "viem/chains";
348
349
 
349
350
  // ../../node_modules/@zkp2p/contracts-v2/abis/base/WhitelistPreIntentHook.json
350
351
  var WhitelistPreIntentHook_default = [
@@ -689,20 +690,123 @@ var WhitelistPreIntentHook_default = [
689
690
  }
690
691
  ];
691
692
 
692
- // src/deposit.ts
693
- import { createPublicClient, decodeEventLog, formatUnits as formatUnits2, http, parseUnits } from "viem";
694
- import { base } from "viem/chains";
695
- import {
696
- OfframpClient,
697
- getSpreadOracleConfig,
698
- mapConversionRatesToOnchainMinRate
699
- } from "@zkp2p/sdk";
693
+ // src/sdk-client.ts
694
+ import { OfframpClient } from "@zkp2p/sdk";
695
+ function createSdkClient(walletClient) {
696
+ return new OfframpClient({
697
+ walletClient,
698
+ chainId: BASE_CHAIN_ID,
699
+ runtimeEnv: RUNTIME_ENV,
700
+ rpcUrl: BASE_RPC_URL,
701
+ baseApiUrl: API_BASE_URL
702
+ });
703
+ }
704
+
705
+ // src/otc.ts
706
+ var WHITELIST_HOOK_ADDRESS = "0xda023Ea0d789A41BcF5866F7B6BBd2CaDF9b79B8";
707
+ var OTC_BASE_URL = "https://usdctofiat.xyz";
708
+ async function enableOtc(walletClient, depositId, takerAddress, escrowAddress) {
709
+ if (!isAddress(takerAddress)) {
710
+ throw new OfframpError("Invalid taker address", "VALIDATION");
711
+ }
712
+ const escrow = escrowAddress || ESCROW_ADDRESS;
713
+ const taker = takerAddress;
714
+ const depositIdBig = BigInt(depositId);
715
+ const client = createSdkClient(walletClient);
716
+ const txOverrides = { referrer: [REFERRER] };
717
+ try {
718
+ const currentHook = await client.getDepositWhitelistHook(depositIdBig, {
719
+ escrowAddress: escrow
720
+ });
721
+ if (currentHook !== zeroAddress) {
722
+ const isAlready = await readIsWhitelisted(escrow, depositIdBig, taker);
723
+ if (isAlready) {
724
+ return {
725
+ depositId,
726
+ takerAddress: taker,
727
+ otcLink: getOtcLink(depositId, escrow)
728
+ };
729
+ }
730
+ }
731
+ await writeContract(walletClient, "addToWhitelist", [escrow, depositIdBig, [taker]]);
732
+ if (currentHook === zeroAddress) {
733
+ await client.setDepositWhitelistHook({
734
+ depositId: depositIdBig,
735
+ whitelistHook: WHITELIST_HOOK_ADDRESS,
736
+ escrowAddress: escrow,
737
+ txOverrides
738
+ });
739
+ }
740
+ return {
741
+ depositId,
742
+ takerAddress: taker,
743
+ otcLink: getOtcLink(depositId, escrow)
744
+ };
745
+ } catch (err) {
746
+ if (err instanceof OfframpError) throw err;
747
+ if (isUserCancellation(err)) {
748
+ throw new OfframpError("User cancelled", "USER_CANCELLED", void 0, err, { depositId });
749
+ }
750
+ const detail = err instanceof Error ? err.message : String(err);
751
+ throw new OfframpError(`Failed to enable OTC: ${detail}`, "DEPOSIT_FAILED", void 0, err, {
752
+ depositId
753
+ });
754
+ }
755
+ }
756
+ async function disableOtc(walletClient, depositId, escrowAddress) {
757
+ const escrow = escrowAddress || ESCROW_ADDRESS;
758
+ const client = createSdkClient(walletClient);
759
+ const txOverrides = { referrer: [REFERRER] };
760
+ try {
761
+ await client.setDepositWhitelistHook({
762
+ depositId: BigInt(depositId),
763
+ whitelistHook: zeroAddress,
764
+ escrowAddress: escrow,
765
+ txOverrides
766
+ });
767
+ } catch (err) {
768
+ if (isUserCancellation(err)) {
769
+ throw new OfframpError("User cancelled", "USER_CANCELLED", void 0, err, { depositId });
770
+ }
771
+ const detail = err instanceof Error ? err.message : String(err);
772
+ throw new OfframpError(`Failed to disable OTC: ${detail}`, "DEPOSIT_FAILED", void 0, err, {
773
+ depositId
774
+ });
775
+ }
776
+ }
777
+ function getOtcLink(depositId, escrowAddress) {
778
+ const escrow = escrowAddress || ESCROW_ADDRESS;
779
+ return `${OTC_BASE_URL}/deposit/${escrow}/${depositId}`;
780
+ }
781
+ async function writeContract(walletClient, functionName, args) {
782
+ const wc = walletClient;
783
+ if (typeof wc.writeContract !== "function") {
784
+ throw new OfframpError("Wallet client does not support writeContract", "VALIDATION");
785
+ }
786
+ await wc.writeContract({
787
+ address: WHITELIST_HOOK_ADDRESS,
788
+ abi: WhitelistPreIntentHook_default,
789
+ functionName,
790
+ args
791
+ });
792
+ }
793
+ async function readIsWhitelisted(escrow, depositId, taker) {
794
+ const publicClient = createPublicClient({ chain: base, transport: http(BASE_RPC_URL) });
795
+ return publicClient.readContract({
796
+ address: WHITELIST_HOOK_ADDRESS,
797
+ abi: WhitelistPreIntentHook_default,
798
+ functionName: "isWhitelisted",
799
+ args: [escrow, depositId, taker]
800
+ });
801
+ }
700
802
 
701
803
  // src/queries.ts
702
- import { formatUnits } from "viem";
804
+ import { concatHex, createPublicClient as createPublicClient2, encodeFunctionData, formatUnits, http as http2 } from "viem";
805
+ import { base as base2 } from "viem/chains";
703
806
  import {
704
807
  classifyDelegationState,
705
808
  defaultIndexerEndpoint,
809
+ getAttributionDataSuffix,
706
810
  getCurrencyInfoFromHash as getCurrencyInfoFromHash2,
707
811
  IndexerClient,
708
812
  IndexerDepositService
@@ -779,6 +883,18 @@ function extractTxHash(result) {
779
883
  return result.transactionHash;
780
884
  throw new Error("Unexpected transaction result format");
781
885
  }
886
+ var ESCROW_V2_ABI = [
887
+ {
888
+ name: "clearRateManager",
889
+ type: "function",
890
+ stateMutability: "nonpayable",
891
+ inputs: [{ name: "depositId", type: "uint256" }],
892
+ outputs: []
893
+ }
894
+ ];
895
+ function appendReferrerAttribution(calldata) {
896
+ return concatHex([calldata, getAttributionDataSuffix([REFERRER])]);
897
+ }
782
898
  async function deposits(walletAddress) {
783
899
  const service = getIndexerService();
784
900
  const raw = await service.fetchDepositsWithRelations(
@@ -801,8 +917,167 @@ async function close(walletClient, depositId, escrowAddress) {
801
917
  });
802
918
  return extractTxHash(result);
803
919
  }
920
+ async function delegate(walletClient, depositId, escrowAddress) {
921
+ const client = createSdkClient(walletClient);
922
+ const result = await client.setRateManager({
923
+ depositId: BigInt(depositId),
924
+ rateManagerAddress: RATE_MANAGER_REGISTRY_ADDRESS,
925
+ rateManagerId: DELEGATE_RATE_MANAGER_ID,
926
+ escrowAddress: escrowAddress || ESCROW_ADDRESS,
927
+ txOverrides: { referrer: [REFERRER] }
928
+ });
929
+ return extractTxHash(result);
930
+ }
931
+ async function undelegate(walletClient, depositId, escrowAddress) {
932
+ const account = walletClient.account;
933
+ if (!account) {
934
+ throw new Error("Wallet client has no account. Connect a wallet first.");
935
+ }
936
+ const data = appendReferrerAttribution(
937
+ encodeFunctionData({
938
+ abi: ESCROW_V2_ABI,
939
+ functionName: "clearRateManager",
940
+ args: [BigInt(depositId)]
941
+ })
942
+ );
943
+ const hash = await walletClient.sendTransaction({
944
+ account,
945
+ chain: base2,
946
+ data,
947
+ to: escrowAddress || ESCROW_ADDRESS
948
+ });
949
+ const publicClient = createPublicClient2({
950
+ chain: base2,
951
+ transport: http2(BASE_RPC_URL)
952
+ });
953
+ try {
954
+ const receipt = await publicClient.waitForTransactionReceipt({
955
+ hash,
956
+ confirmations: 1,
957
+ timeout: 12e4,
958
+ pollingInterval: 4e3
959
+ });
960
+ if (receipt.status !== "success") {
961
+ throw new Error("Undelegation transaction failed");
962
+ }
963
+ } catch (err) {
964
+ const isTimeout = err instanceof Error && err.message.toLowerCase().includes("timed out");
965
+ if (!isTimeout) {
966
+ throw err;
967
+ }
968
+ }
969
+ return hash;
970
+ }
971
+
972
+ // src/telemetry.ts
973
+ var SDK_EVENTS_ENDPOINT = "https://usdctofiat.xyz/api/sdk-events";
974
+ var MAX_BATCH_SIZE = 50;
975
+ var FLUSH_DELAY_MS = 180;
976
+ var TELEMETRY_TIMEOUT_MS = 2e3;
977
+ var queuedEvents = [];
978
+ var flushTimer = null;
979
+ var flushInFlight = false;
980
+ function sanitizeAttributionId(raw) {
981
+ if (typeof raw !== "string") return void 0;
982
+ const stripped = raw.trim().replace(/[^a-zA-Z0-9_-]/g, "");
983
+ if (!stripped) return void 0;
984
+ if (stripped.length > 64) return void 0;
985
+ return stripped;
986
+ }
987
+ function scheduleFlush() {
988
+ if (flushTimer != null) return;
989
+ flushTimer = setTimeout(() => {
990
+ flushTimer = null;
991
+ void flushQueue();
992
+ }, FLUSH_DELAY_MS);
993
+ }
994
+ async function postBatch(batch) {
995
+ if (!batch.length) return;
996
+ const body = JSON.stringify(batch);
997
+ try {
998
+ if (typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function") {
999
+ const blob = new Blob([body], { type: "application/json" });
1000
+ const accepted = navigator.sendBeacon(SDK_EVENTS_ENDPOINT, blob);
1001
+ if (accepted) return;
1002
+ }
1003
+ } catch {
1004
+ }
1005
+ try {
1006
+ const controller = typeof AbortController !== "undefined" ? new AbortController() : null;
1007
+ const timeout = controller ? setTimeout(() => controller.abort(), TELEMETRY_TIMEOUT_MS) : null;
1008
+ await fetch(SDK_EVENTS_ENDPOINT, {
1009
+ method: "POST",
1010
+ headers: { "Content-Type": "application/json" },
1011
+ body,
1012
+ keepalive: true,
1013
+ signal: controller?.signal
1014
+ }).catch(() => {
1015
+ });
1016
+ if (timeout) clearTimeout(timeout);
1017
+ } catch {
1018
+ }
1019
+ }
1020
+ async function flushQueue() {
1021
+ if (flushInFlight) return;
1022
+ if (!queuedEvents.length) return;
1023
+ flushInFlight = true;
1024
+ try {
1025
+ while (queuedEvents.length > 0) {
1026
+ const batch = queuedEvents.slice(0, MAX_BATCH_SIZE);
1027
+ queuedEvents = queuedEvents.slice(batch.length);
1028
+ await postBatch(batch);
1029
+ }
1030
+ } finally {
1031
+ flushInFlight = false;
1032
+ }
1033
+ }
1034
+ function emitEvent(name, payload) {
1035
+ try {
1036
+ queuedEvents.push({
1037
+ name,
1038
+ payload,
1039
+ ts: Date.now()
1040
+ });
1041
+ if (queuedEvents.length >= MAX_BATCH_SIZE) {
1042
+ void flushQueue();
1043
+ return;
1044
+ }
1045
+ scheduleFlush();
1046
+ } catch {
1047
+ }
1048
+ }
1049
+ function sendTelemetryEvent(name, payload) {
1050
+ emitEvent(name, payload);
1051
+ }
1052
+ function createTelemetryContext(options) {
1053
+ const integratorId = sanitizeAttributionId(options.integratorId);
1054
+ const referralId = sanitizeAttributionId(options.referralId);
1055
+ const enabled = options.telemetry !== false;
1056
+ const sdkVersion = options.sdkVersion;
1057
+ return {
1058
+ enabled,
1059
+ sdkVersion,
1060
+ integratorId,
1061
+ referralId,
1062
+ emit(name, payload = {}) {
1063
+ if (!enabled) return;
1064
+ emitEvent(name, {
1065
+ sdkVersion,
1066
+ integratorId,
1067
+ referralId,
1068
+ ...payload
1069
+ });
1070
+ }
1071
+ };
1072
+ }
804
1073
 
805
1074
  // src/deposit.ts
1075
+ import { createPublicClient as createPublicClient3, decodeEventLog, formatUnits as formatUnits2, http as http3, parseUnits } from "viem";
1076
+ import { base as base3 } from "viem/chains";
1077
+ import {
1078
+ getSpreadOracleConfig,
1079
+ mapConversionRatesToOnchainMinRate
1080
+ } from "@zkp2p/sdk";
806
1081
  function usdcToUnits(amount) {
807
1082
  return parseUnits(amount, 6);
808
1083
  }
@@ -897,15 +1172,6 @@ function extractTxHash2(result) {
897
1172
  return result.transactionHash;
898
1173
  throw new Error("Unexpected transaction result format");
899
1174
  }
900
- function createSdkClient(walletClient) {
901
- return new OfframpClient({
902
- walletClient,
903
- chainId: BASE_CHAIN_ID,
904
- runtimeEnv: RUNTIME_ENV,
905
- rpcUrl: BASE_RPC_URL,
906
- baseApiUrl: API_BASE_URL
907
- });
908
- }
909
1175
  async function findUndelegatedDeposit(walletAddress) {
910
1176
  try {
911
1177
  const service = getIndexerService();
@@ -914,6 +1180,7 @@ async function findUndelegatedDeposit(walletAddress) {
914
1180
  { limit: 50 }
915
1181
  );
916
1182
  const escrowLower = ESCROW_ADDRESS.toLowerCase();
1183
+ const delegateIdLower = DELEGATE_RATE_MANAGER_ID.toLowerCase();
917
1184
  const undelegated = (raw || []).filter((d) => {
918
1185
  if (d.status === "CLOSED") return false;
919
1186
  if (d.escrowAddress.toLowerCase() !== escrowLower) return false;
@@ -925,369 +1192,493 @@ async function findUndelegatedDeposit(walletAddress) {
925
1192
  }
926
1193
  })();
927
1194
  if (remaining === 0n) return false;
928
- return d.rateManagerId !== DELEGATE_RATE_MANAGER_ID;
1195
+ return (d.rateManagerId ?? "").toLowerCase() !== delegateIdLower;
1196
+ });
1197
+ undelegated.sort((a, b) => {
1198
+ const left = BigInt(a.depositId);
1199
+ const right = BigInt(b.depositId);
1200
+ if (left === right) return 0;
1201
+ return left > right ? -1 : 1;
929
1202
  });
930
- undelegated.sort((a, b) => Number(BigInt(b.depositId) - BigInt(a.depositId)));
931
1203
  return undelegated[0] ?? null;
932
1204
  } catch {
933
1205
  return null;
934
1206
  }
935
1207
  }
936
- async function offramp(walletClient, params, onProgress) {
1208
+ async function offramp(walletClient, params, onProgress, telemetryContext) {
937
1209
  const { amount, platform, currency, identifier } = params;
938
1210
  const platformId = platform.id;
939
1211
  const currencyCode = currency.code;
940
- if (!walletClient.account?.address) {
941
- throw new OfframpError("Wallet client has no account. Connect a wallet first.", "VALIDATION");
942
- }
943
- const walletAddress = walletClient.account.address;
944
- const amt = parseFloat(amount);
945
- if (!Number.isFinite(amt) || amt < MIN_DEPOSIT_USDC) {
946
- throw new OfframpError(`Minimum deposit is ${MIN_DEPOSIT_USDC} USDC`, "VALIDATION");
947
- }
948
- if (!platform.currencies.includes(currencyCode)) {
949
- throw new OfframpError(`${currencyCode} is not supported on ${platform.name}`, "UNSUPPORTED");
950
- }
951
- const validation = platform.validate(identifier);
952
- if (!validation.valid) {
953
- throw new OfframpError(validation.error || "Invalid identifier", "VALIDATION");
954
- }
955
- const normalizedIdentifier = validation.normalized;
956
- const methodHash = getPaymentMethodHash(platformId);
957
- if (!methodHash) {
958
- throw new OfframpError(`${platform.name} is not currently supported`, "UNSUPPORTED");
959
- }
960
- const existing = await findUndelegatedDeposit(walletAddress);
961
- if (existing) {
962
- onProgress?.({ step: "resuming", depositId: existing.depositId });
963
- const client2 = createSdkClient(walletClient);
964
- const txOverrides2 = { referrer: [REFERRER] };
1212
+ const telemetry = telemetryContext ?? createTelemetryContext({
1213
+ sdkVersion: SDK_VERSION,
1214
+ telemetry: true,
1215
+ integratorId: params.integratorId,
1216
+ referralId: params.referralId
1217
+ });
1218
+ const flowStartMs = Date.now();
1219
+ const emitProgress = (progress) => {
1220
+ onProgress?.(progress);
1221
+ telemetry.emit("sdk.createDeposit.progress", {
1222
+ step: progress.step,
1223
+ txHash: progress.txHash
1224
+ });
1225
+ };
1226
+ try {
1227
+ if (!walletClient.account?.address) {
1228
+ throw new OfframpError("Wallet client has no account. Connect a wallet first.", "VALIDATION");
1229
+ }
1230
+ const walletAddress = walletClient.account.address.toLowerCase();
1231
+ telemetry.emit("sdk.createDeposit.start", {
1232
+ platform: platformId,
1233
+ currency: currencyCode,
1234
+ amount,
1235
+ wallet: walletAddress
1236
+ });
1237
+ const amt = parseFloat(amount);
1238
+ if (!Number.isFinite(amt) || amt < MIN_DEPOSIT_USDC) {
1239
+ throw new OfframpError(`Minimum deposit is ${MIN_DEPOSIT_USDC} USDC`, "VALIDATION");
1240
+ }
1241
+ if (!platform.currencies.includes(currencyCode)) {
1242
+ throw new OfframpError(`${currencyCode} is not supported on ${platform.name}`, "UNSUPPORTED");
1243
+ }
1244
+ const validation = platform.validate(identifier);
1245
+ if (!validation.valid) {
1246
+ throw new OfframpError(validation.error || "Invalid identifier", "VALIDATION");
1247
+ }
1248
+ const normalizedIdentifier = validation.normalized;
1249
+ const methodHash = getPaymentMethodHash(platformId);
1250
+ if (!methodHash) {
1251
+ throw new OfframpError(`${platform.name} is not currently supported`, "UNSUPPORTED");
1252
+ }
1253
+ const existing = await findUndelegatedDeposit(walletAddress);
1254
+ if (existing) {
1255
+ emitProgress({ step: "resuming", depositId: existing.depositId });
1256
+ const client2 = createSdkClient(walletClient);
1257
+ const txOverrides2 = { referrer: [REFERRER] };
1258
+ try {
1259
+ const result2 = await client2.setRateManager({
1260
+ depositId: BigInt(existing.depositId),
1261
+ rateManagerAddress: RATE_MANAGER_REGISTRY_ADDRESS,
1262
+ rateManagerId: DELEGATE_RATE_MANAGER_ID,
1263
+ escrowAddress: existing.escrowAddress,
1264
+ txOverrides: txOverrides2
1265
+ });
1266
+ const txHash = extractTxHash2(result2);
1267
+ emitProgress({ step: "done", txHash, depositId: existing.depositId });
1268
+ const resumedResult = { depositId: existing.depositId, txHash, resumed: true };
1269
+ telemetry.emit("sdk.createDeposit.success", {
1270
+ depositId: resumedResult.depositId,
1271
+ txHash: resumedResult.txHash,
1272
+ wallet: walletAddress,
1273
+ resumed: true,
1274
+ wallDurationMs: Date.now() - flowStartMs
1275
+ });
1276
+ return resumedResult;
1277
+ } catch (err) {
1278
+ if (isUserCancellation(err))
1279
+ throw new OfframpError("User cancelled", "USER_CANCELLED", "resuming", err);
1280
+ throw new OfframpError(
1281
+ "Found undelegated deposit but delegation failed. Try again.",
1282
+ "DELEGATION_FAILED",
1283
+ "resuming",
1284
+ err,
1285
+ { depositId: existing.depositId, txHash: existing.txHash }
1286
+ );
1287
+ }
1288
+ }
1289
+ const truncatedAmount = amt.toFixed(6).replace(/\.?0+$/, "");
1290
+ const amountUnits = usdcToUnits(truncatedAmount);
1291
+ const minUnits = usdcToUnits(String(MIN_ORDER_USDC));
1292
+ const maxUnits = usdcToUnits(String(Math.min(amt, 2500)));
965
1293
  try {
966
- const result = await client2.setRateManager({
967
- depositId: BigInt(existing.depositId),
968
- rateManagerAddress: RATE_MANAGER_REGISTRY_ADDRESS,
969
- rateManagerId: DELEGATE_RATE_MANAGER_ID,
970
- escrowAddress: existing.escrowAddress,
971
- txOverrides: txOverrides2
1294
+ const publicClient = createPublicClient3({ chain: base3, transport: http3(BASE_RPC_URL) });
1295
+ const balance = await publicClient.readContract({
1296
+ address: USDC_ADDRESS,
1297
+ abi: [
1298
+ {
1299
+ name: "balanceOf",
1300
+ type: "function",
1301
+ stateMutability: "view",
1302
+ inputs: [{ name: "account", type: "address" }],
1303
+ outputs: [{ name: "", type: "uint256" }]
1304
+ }
1305
+ ],
1306
+ functionName: "balanceOf",
1307
+ args: [walletAddress]
1308
+ });
1309
+ if (balance < amountUnits) {
1310
+ const available = Number(formatUnits2(balance, 6));
1311
+ throw new OfframpError(
1312
+ `Insufficient USDC balance. Have ${available.toFixed(2)}, need ${truncatedAmount}.`,
1313
+ "VALIDATION"
1314
+ );
1315
+ }
1316
+ } catch (err) {
1317
+ if (err instanceof OfframpError) throw err;
1318
+ }
1319
+ const client = createSdkClient(walletClient);
1320
+ const txOverrides = { referrer: [REFERRER] };
1321
+ emitProgress({ step: "approving" });
1322
+ try {
1323
+ await client.ensureAllowance({
1324
+ token: USDC_ADDRESS,
1325
+ amount: amountUnits,
1326
+ escrowAddress: ESCROW_ADDRESS,
1327
+ maxApprove: false,
1328
+ txOverrides
972
1329
  });
973
- const txHash = extractTxHash2(result);
974
- onProgress?.({ step: "done", txHash, depositId: existing.depositId });
975
- return { depositId: existing.depositId, txHash, resumed: true };
976
1330
  } catch (err) {
977
1331
  if (isUserCancellation(err))
978
- throw new OfframpError("User cancelled", "USER_CANCELLED", "resuming", err);
1332
+ throw new OfframpError("User cancelled", "USER_CANCELLED", "approving", err);
1333
+ const detail = err instanceof Error ? err.message : String(err);
979
1334
  throw new OfframpError(
980
- "Found undelegated deposit but delegation failed. Try again.",
981
- "DELEGATION_FAILED",
982
- "resuming",
983
- err,
984
- { depositId: existing.depositId, txHash: existing.txHash }
1335
+ `USDC approval failed: ${detail}`,
1336
+ "APPROVAL_FAILED",
1337
+ "approving",
1338
+ err
985
1339
  );
986
1340
  }
987
- }
988
- const truncatedAmount = amt.toFixed(6).replace(/\.?0+$/, "");
989
- const amountUnits = usdcToUnits(truncatedAmount);
990
- const minUnits = usdcToUnits(String(MIN_ORDER_USDC));
991
- const maxUnits = usdcToUnits(String(Math.min(amt, 2500)));
992
- try {
993
- const publicClient = createPublicClient({ chain: base, transport: http(BASE_RPC_URL) });
994
- const balance = await publicClient.readContract({
995
- address: USDC_ADDRESS,
996
- abi: [
997
- {
998
- name: "balanceOf",
999
- type: "function",
1000
- stateMutability: "view",
1001
- inputs: [{ name: "account", type: "address" }],
1002
- outputs: [{ name: "", type: "uint256" }]
1003
- }
1004
- ],
1005
- functionName: "balanceOf",
1006
- args: [walletAddress]
1007
- });
1008
- if (balance < amountUnits) {
1009
- const available = Number(formatUnits2(balance, 6));
1341
+ emitProgress({ step: "registering" });
1342
+ let hashedOnchainId;
1343
+ try {
1344
+ const canonicalName = platformId.startsWith("zelle") ? "zelle" : platformId;
1345
+ const depositData = buildDepositData(platformId, normalizedIdentifier);
1346
+ hashedOnchainId = await registerPayeeDetails(canonicalName, depositData);
1347
+ } catch (err) {
1010
1348
  throw new OfframpError(
1011
- `Insufficient USDC balance. Have ${available.toFixed(2)}, need ${truncatedAmount}.`,
1012
- "VALIDATION"
1349
+ "Payee registration failed",
1350
+ "REGISTRATION_FAILED",
1351
+ "registering",
1352
+ err
1013
1353
  );
1014
1354
  }
1015
- } catch (err) {
1016
- if (err instanceof OfframpError) throw err;
1017
- }
1018
- const client = createSdkClient(walletClient);
1019
- const txOverrides = { referrer: [REFERRER] };
1020
- onProgress?.({ step: "approving" });
1021
- try {
1022
- await client.ensureAllowance({
1023
- token: USDC_ADDRESS,
1024
- amount: amountUnits,
1025
- escrowAddress: ESCROW_ADDRESS,
1026
- maxApprove: false,
1027
- txOverrides
1028
- });
1029
- } catch (err) {
1030
- if (isUserCancellation(err))
1031
- throw new OfframpError("User cancelled", "USER_CANCELLED", "approving", err);
1032
- const detail = err instanceof Error ? err.message : String(err);
1033
- throw new OfframpError(`USDC approval failed: ${detail}`, "APPROVAL_FAILED", "approving", err);
1034
- }
1035
- onProgress?.({ step: "registering" });
1036
- let hashedOnchainId;
1037
- try {
1038
- const canonicalName = platformId.startsWith("zelle") ? "zelle" : platformId;
1039
- const depositData = buildDepositData(platformId, normalizedIdentifier);
1040
- hashedOnchainId = await registerPayeeDetails(canonicalName, depositData);
1041
- } catch (err) {
1042
- throw new OfframpError("Payee registration failed", "REGISTRATION_FAILED", "registering", err);
1043
- }
1044
- onProgress?.({ step: "depositing" });
1045
- const conversionRates = [
1046
- [{ currency: currencyCode, conversionRate: "1" }]
1047
- ];
1048
- const baseCurrenciesOverride = mapConversionRatesToOnchainMinRate(conversionRates, 1);
1049
- const currenciesOverride = attachOracleConfig(baseCurrenciesOverride, conversionRates);
1050
- let hash;
1051
- try {
1052
- const result = await client.createDeposit({
1053
- token: USDC_ADDRESS,
1054
- amount: amountUnits,
1055
- retainOnEmpty: false,
1056
- intentAmountRange: { min: minUnits, max: maxUnits },
1057
- processorNames: [platformId],
1058
- conversionRates,
1059
- payeeDetailsHashes: [hashedOnchainId],
1060
- paymentMethodsOverride: [methodHash],
1061
- paymentMethodDataOverride: [
1062
- {
1063
- intentGatingService: GATING_SERVICE_ADDRESS,
1064
- payeeDetails: hashedOnchainId,
1065
- data: "0x"
1066
- }
1067
- ],
1068
- currenciesOverride,
1069
- escrowAddress: ESCROW_ADDRESS,
1070
- txOverrides
1071
- });
1072
- if (!result?.hash) throw new Error("No transaction hash returned");
1073
- hash = result.hash;
1074
- } catch (err) {
1075
- if (isUserCancellation(err))
1076
- throw new OfframpError("User cancelled", "USER_CANCELLED", "depositing", err);
1077
- const detail = err instanceof Error ? err.message : String(err);
1078
- throw new OfframpError(
1079
- `Deposit transaction failed: ${detail}`,
1080
- "DEPOSIT_FAILED",
1081
- "depositing",
1082
- err
1083
- );
1084
- }
1085
- onProgress?.({ step: "confirming", txHash: hash });
1086
- let depositId = "";
1087
- const receiptClient = client;
1088
- if (typeof receiptClient.waitForTransactionReceipt === "function") {
1355
+ emitProgress({ step: "depositing" });
1356
+ const conversionRates = [
1357
+ [{ currency: currencyCode, conversionRate: "1" }]
1358
+ ];
1359
+ const baseCurrenciesOverride = mapConversionRatesToOnchainMinRate(conversionRates, 1);
1360
+ const currenciesOverride = attachOracleConfig(baseCurrenciesOverride, conversionRates);
1361
+ let hash;
1089
1362
  try {
1090
- const receipt = await receiptClient.waitForTransactionReceipt({ hash, confirmations: 1 });
1091
- depositId = extractDepositIdFromLogs(receipt.logs) || "";
1092
- } catch {
1363
+ const result2 = await client.createDeposit({
1364
+ token: USDC_ADDRESS,
1365
+ amount: amountUnits,
1366
+ retainOnEmpty: false,
1367
+ intentAmountRange: { min: minUnits, max: maxUnits },
1368
+ processorNames: [platformId],
1369
+ conversionRates,
1370
+ payeeDetailsHashes: [hashedOnchainId],
1371
+ paymentMethodsOverride: [methodHash],
1372
+ paymentMethodDataOverride: [
1373
+ {
1374
+ intentGatingService: GATING_SERVICE_ADDRESS,
1375
+ payeeDetails: hashedOnchainId,
1376
+ data: "0x"
1377
+ }
1378
+ ],
1379
+ currenciesOverride,
1380
+ escrowAddress: ESCROW_ADDRESS,
1381
+ txOverrides
1382
+ });
1383
+ if (!result2?.hash) throw new Error("No transaction hash returned");
1384
+ hash = result2.hash;
1385
+ } catch (err) {
1386
+ if (isUserCancellation(err))
1387
+ throw new OfframpError("User cancelled", "USER_CANCELLED", "depositing", err);
1388
+ const detail = err instanceof Error ? err.message : String(err);
1389
+ throw new OfframpError(
1390
+ `Deposit transaction failed: ${detail}`,
1391
+ "DEPOSIT_FAILED",
1392
+ "depositing",
1393
+ err
1394
+ );
1093
1395
  }
1094
- }
1095
- if (!depositId) {
1096
- let delay = INDEXER_INITIAL_DELAY_MS;
1097
- for (let attempt = 0; attempt < INDEXER_MAX_ATTEMPTS && !depositId; attempt++) {
1396
+ emitProgress({ step: "confirming", txHash: hash });
1397
+ let depositId = "";
1398
+ const receiptClient = client;
1399
+ if (typeof receiptClient.waitForTransactionReceipt === "function") {
1098
1400
  try {
1099
- const deps = await client.indexer.getDepositsWithRelations(
1100
- { depositor: walletAddress },
1101
- { limit: 25 }
1102
- );
1103
- const hit = deps.find((d) => (d?.txHash || "").toLowerCase() === hash.toLowerCase());
1104
- if (hit) {
1105
- depositId = String(hit.depositId);
1106
- break;
1107
- }
1401
+ const receipt = await receiptClient.waitForTransactionReceipt({ hash, confirmations: 1 });
1402
+ depositId = extractDepositIdFromLogs(receipt.logs) || "";
1108
1403
  } catch {
1109
1404
  }
1110
- await new Promise((r) => setTimeout(r, delay));
1111
- delay = Math.min(INDEXER_MAX_DELAY_MS, Math.floor(delay * 1.7));
1112
1405
  }
1113
- }
1114
- if (!depositId) {
1115
- throw new OfframpError(
1116
- "Deposit created on-chain but could not confirm deposit ID. Your funds are safe. Call offramp() again to resume delegation.",
1117
- "CONFIRMATION_FAILED",
1118
- "confirming",
1119
- void 0,
1120
- { txHash: hash }
1121
- );
1122
- }
1123
- onProgress?.({ step: "delegating", txHash: hash, depositId });
1124
- try {
1125
- await client.setRateManager({
1126
- depositId: BigInt(depositId),
1127
- rateManagerAddress: RATE_MANAGER_REGISTRY_ADDRESS,
1128
- rateManagerId: DELEGATE_RATE_MANAGER_ID,
1129
- escrowAddress: ESCROW_ADDRESS,
1130
- txOverrides
1131
- });
1132
- } catch (delegationError) {
1133
- if (isUserCancellation(delegationError)) {
1406
+ if (!depositId) {
1407
+ let delay = INDEXER_INITIAL_DELAY_MS;
1408
+ for (let attempt = 0; attempt < INDEXER_MAX_ATTEMPTS && !depositId; attempt++) {
1409
+ try {
1410
+ const deps = await client.indexer.getDepositsWithRelations(
1411
+ { depositor: walletAddress },
1412
+ { limit: 25 }
1413
+ );
1414
+ const hit = deps.find((d) => (d?.txHash || "").toLowerCase() === hash.toLowerCase());
1415
+ if (hit) {
1416
+ depositId = String(hit.depositId);
1417
+ break;
1418
+ }
1419
+ } catch {
1420
+ }
1421
+ await new Promise((r) => setTimeout(r, delay));
1422
+ delay = Math.min(INDEXER_MAX_DELAY_MS, Math.floor(delay * 1.7));
1423
+ }
1424
+ }
1425
+ if (!depositId) {
1134
1426
  throw new OfframpError(
1135
- "User cancelled delegation",
1136
- "USER_CANCELLED",
1137
- "delegating",
1138
- delegationError,
1139
- { txHash: hash, depositId }
1427
+ "Deposit created on-chain but could not confirm deposit ID. Your funds are safe. Call offramp() again to resume delegation.",
1428
+ "CONFIRMATION_FAILED",
1429
+ "confirming",
1430
+ void 0,
1431
+ { txHash: hash }
1140
1432
  );
1141
1433
  }
1142
- throw new OfframpError(
1143
- "Deposit created but delegation failed. Call offramp() again to resume delegation.",
1144
- "DELEGATION_FAILED",
1145
- "delegating",
1146
- delegationError,
1147
- { txHash: hash, depositId }
1148
- );
1149
- }
1150
- let otcLink;
1151
- if (params.otcTaker) {
1152
- onProgress?.({ step: "restricting", txHash: hash, depositId });
1434
+ emitProgress({ step: "delegating", txHash: hash, depositId });
1153
1435
  try {
1154
- const otcResult = await enableOtc(walletClient, depositId, params.otcTaker);
1155
- otcLink = otcResult.otcLink;
1156
- } catch (otcError) {
1157
- if (isUserCancellation(otcError)) {
1436
+ await client.setRateManager({
1437
+ depositId: BigInt(depositId),
1438
+ rateManagerAddress: RATE_MANAGER_REGISTRY_ADDRESS,
1439
+ rateManagerId: DELEGATE_RATE_MANAGER_ID,
1440
+ escrowAddress: ESCROW_ADDRESS,
1441
+ txOverrides
1442
+ });
1443
+ } catch (delegationError) {
1444
+ if (isUserCancellation(delegationError)) {
1158
1445
  throw new OfframpError(
1159
- "User cancelled OTC restriction",
1446
+ "User cancelled delegation",
1160
1447
  "USER_CANCELLED",
1161
- "restricting",
1162
- otcError,
1163
- {
1164
- txHash: hash,
1165
- depositId
1166
- }
1448
+ "delegating",
1449
+ delegationError,
1450
+ { txHash: hash, depositId }
1167
1451
  );
1168
1452
  }
1169
1453
  throw new OfframpError(
1170
- "Deposit created and delegated but OTC restriction failed. Use enableOtc() to retry.",
1171
- "DEPOSIT_FAILED",
1172
- "restricting",
1173
- otcError,
1454
+ "Deposit created but delegation failed. Call offramp() again to resume delegation.",
1455
+ "DELEGATION_FAILED",
1456
+ "delegating",
1457
+ delegationError,
1174
1458
  { txHash: hash, depositId }
1175
1459
  );
1176
1460
  }
1461
+ let otcLink;
1462
+ if (params.otcTaker) {
1463
+ emitProgress({ step: "restricting", txHash: hash, depositId });
1464
+ try {
1465
+ const otcResult = await enableOtc(walletClient, depositId, params.otcTaker);
1466
+ otcLink = otcResult.otcLink;
1467
+ } catch (otcError) {
1468
+ if (isUserCancellation(otcError)) {
1469
+ throw new OfframpError(
1470
+ "User cancelled OTC restriction",
1471
+ "USER_CANCELLED",
1472
+ "restricting",
1473
+ otcError,
1474
+ {
1475
+ txHash: hash,
1476
+ depositId
1477
+ }
1478
+ );
1479
+ }
1480
+ throw new OfframpError(
1481
+ "Deposit created and delegated but OTC restriction failed. Use enableOtc() to retry.",
1482
+ "DEPOSIT_FAILED",
1483
+ "restricting",
1484
+ otcError,
1485
+ { txHash: hash, depositId }
1486
+ );
1487
+ }
1488
+ }
1489
+ emitProgress({ step: "done", txHash: hash, depositId });
1490
+ const result = { depositId, txHash: hash, resumed: false, otcLink };
1491
+ telemetry.emit("sdk.createDeposit.success", {
1492
+ depositId: result.depositId,
1493
+ txHash: result.txHash,
1494
+ wallet: walletAddress,
1495
+ resumed: false,
1496
+ wallDurationMs: Date.now() - flowStartMs
1497
+ });
1498
+ return result;
1499
+ } catch (error) {
1500
+ const known = error instanceof OfframpError ? error : null;
1501
+ telemetry.emit("sdk.createDeposit.error", {
1502
+ code: known?.code ?? "DEPOSIT_FAILED",
1503
+ step: known?.step ?? "unknown"
1504
+ });
1505
+ throw error;
1177
1506
  }
1178
- onProgress?.({ step: "done", txHash: hash, depositId });
1179
- return { depositId, txHash: hash, resumed: false, otcLink };
1180
1507
  }
1181
1508
 
1182
- // src/otc.ts
1183
- var WHITELIST_HOOK_ADDRESS = "0xda023Ea0d789A41BcF5866F7B6BBd2CaDF9b79B8";
1184
- var OTC_BASE_URL = "https://usdctofiat.xyz";
1185
- async function enableOtc(walletClient, depositId, takerAddress, escrowAddress) {
1186
- if (!isAddress(takerAddress)) {
1187
- throw new OfframpError("Invalid taker address", "VALIDATION");
1509
+ // src/currencies.ts
1510
+ import { currencyInfo as currencyInfo2 } from "@zkp2p/sdk";
1511
+ function buildCurrencies() {
1512
+ const entries = {};
1513
+ for (const [code, info] of Object.entries(
1514
+ currencyInfo2
1515
+ )) {
1516
+ entries[code] = {
1517
+ code: info.currencyCode ?? code,
1518
+ name: info.currencyName ?? code,
1519
+ symbol: info.currencySymbol ?? code,
1520
+ countryCode: info.countryCode ?? ""
1521
+ };
1188
1522
  }
1189
- const escrow = escrowAddress || ESCROW_ADDRESS;
1190
- const taker = takerAddress;
1191
- const depositIdBig = BigInt(depositId);
1192
- const client = createSdkClient(walletClient);
1193
- const txOverrides = { referrer: [REFERRER] };
1523
+ return entries;
1524
+ }
1525
+ var CURRENCIES = buildCurrencies();
1526
+
1527
+ // src/offramp.ts
1528
+ var IDEMPOTENCY_TTL_MS = 10 * 60 * 1e3;
1529
+ var IDEMPOTENCY_STORAGE_PREFIX = "offramp:idempotency:";
1530
+ function getWalletAddress(walletClient) {
1531
+ const address = walletClient.account?.address;
1532
+ if (!address) {
1533
+ throw new OfframpError("Wallet client has no account. Connect a wallet first.", "VALIDATION");
1534
+ }
1535
+ return address.toLowerCase();
1536
+ }
1537
+ function getIdempotencyStorageKey(walletAddress, idempotencyKey) {
1538
+ return `${IDEMPOTENCY_STORAGE_PREFIX}${walletAddress}:${idempotencyKey}`;
1539
+ }
1540
+ function readIdempotencyResult(storageKey) {
1541
+ if (typeof sessionStorage === "undefined") return null;
1194
1542
  try {
1195
- const currentHook = await client.getDepositWhitelistHook(depositIdBig, {
1196
- escrowAddress: escrow
1197
- });
1198
- if (currentHook !== zeroAddress) {
1199
- const isAlready = await readIsWhitelisted(
1200
- walletClient,
1201
- escrow,
1202
- depositIdBig,
1203
- taker
1204
- );
1205
- if (isAlready) {
1206
- return {
1207
- depositId,
1208
- takerAddress: taker,
1209
- otcLink: getOtcLink(depositId, escrow)
1210
- };
1211
- }
1543
+ const raw = sessionStorage.getItem(storageKey);
1544
+ if (!raw) return null;
1545
+ const parsed = JSON.parse(raw);
1546
+ const expiresAt = typeof parsed.expiresAt === "number" && Number.isFinite(parsed.expiresAt) ? parsed.expiresAt : 0;
1547
+ if (expiresAt <= Date.now()) {
1548
+ sessionStorage.removeItem(storageKey);
1549
+ return null;
1212
1550
  }
1213
- await writeContract(walletClient, "addToWhitelist", [escrow, depositIdBig, [taker]]);
1214
- if (currentHook === zeroAddress) {
1215
- await client.setDepositWhitelistHook({
1216
- depositId: depositIdBig,
1217
- whitelistHook: WHITELIST_HOOK_ADDRESS,
1218
- escrowAddress: escrow,
1219
- txOverrides
1220
- });
1551
+ const result = parsed.result;
1552
+ if (!result?.depositId || !result.txHash) {
1553
+ sessionStorage.removeItem(storageKey);
1554
+ return null;
1221
1555
  }
1222
- return {
1223
- depositId,
1224
- takerAddress: taker,
1225
- otcLink: getOtcLink(depositId, escrow)
1226
- };
1227
- } catch (err) {
1228
- if (err instanceof OfframpError) throw err;
1229
- if (isUserCancellation(err)) {
1230
- throw new OfframpError("User cancelled", "USER_CANCELLED", void 0, err, { depositId });
1231
- }
1232
- const detail = err instanceof Error ? err.message : String(err);
1233
- throw new OfframpError(`Failed to enable OTC: ${detail}`, "DEPOSIT_FAILED", void 0, err, {
1234
- depositId
1235
- });
1556
+ return result;
1557
+ } catch {
1558
+ return null;
1236
1559
  }
1237
1560
  }
1238
- async function disableOtc(walletClient, depositId, escrowAddress) {
1239
- const escrow = escrowAddress || ESCROW_ADDRESS;
1240
- const client = createSdkClient(walletClient);
1241
- const txOverrides = { referrer: [REFERRER] };
1561
+ function writeIdempotencyResult(storageKey, result) {
1562
+ if (typeof sessionStorage === "undefined") return;
1242
1563
  try {
1243
- await client.setDepositWhitelistHook({
1244
- depositId: BigInt(depositId),
1245
- whitelistHook: zeroAddress,
1246
- escrowAddress: escrow,
1247
- txOverrides
1248
- });
1249
- } catch (err) {
1250
- if (isUserCancellation(err)) {
1251
- throw new OfframpError("User cancelled", "USER_CANCELLED", void 0, err, { depositId });
1252
- }
1253
- const detail = err instanceof Error ? err.message : String(err);
1254
- throw new OfframpError(`Failed to disable OTC: ${detail}`, "DEPOSIT_FAILED", void 0, err, {
1255
- depositId
1256
- });
1564
+ const record = {
1565
+ result,
1566
+ expiresAt: Date.now() + IDEMPOTENCY_TTL_MS
1567
+ };
1568
+ sessionStorage.setItem(storageKey, JSON.stringify(record));
1569
+ } catch {
1257
1570
  }
1258
1571
  }
1259
- function getOtcLink(depositId, escrowAddress) {
1260
- const escrow = escrowAddress || ESCROW_ADDRESS;
1261
- return `${OTC_BASE_URL}/deposit/${escrow}/${depositId}`;
1572
+ function createPerFlowTelemetry(base4, integratorId, referralId) {
1573
+ return createTelemetryContext({
1574
+ sdkVersion: base4.sdkVersion,
1575
+ telemetry: base4.enabled,
1576
+ integratorId: sanitizeAttributionId(integratorId) ?? base4.integratorId,
1577
+ referralId: sanitizeAttributionId(referralId) ?? base4.referralId
1578
+ });
1262
1579
  }
1263
- async function writeContract(walletClient, functionName, args) {
1264
- const wc = walletClient;
1265
- if (typeof wc.writeContract !== "function") {
1266
- throw new OfframpError("Wallet client does not support writeContract", "VALIDATION");
1580
+ function normalizeCurrencyEntry(code) {
1581
+ const fromCatalog = CURRENCIES[code];
1582
+ if (fromCatalog) return fromCatalog;
1583
+ return {
1584
+ code,
1585
+ name: code,
1586
+ symbol: code,
1587
+ countryCode: ""
1588
+ };
1589
+ }
1590
+ function buildCurrencyGroups() {
1591
+ const grouped = {};
1592
+ for (const platform of Object.values(PLATFORMS)) {
1593
+ grouped[platform.id] = platform.currencies.map(normalizeCurrencyEntry);
1267
1594
  }
1268
- await wc.writeContract({
1269
- address: WHITELIST_HOOK_ADDRESS,
1270
- abi: WhitelistPreIntentHook_default,
1271
- functionName,
1272
- args
1273
- });
1595
+ return grouped;
1274
1596
  }
1275
- async function readIsWhitelisted(walletClient, escrow, depositId, taker) {
1276
- const { createPublicClient: createPublicClient2, http: http2 } = await import("viem");
1277
- const { base: base2 } = await import("viem/chains");
1278
- const publicClient = createPublicClient2({
1279
- chain: base2,
1280
- transport: http2("https://mainnet.base.org")
1281
- });
1282
- return publicClient.readContract({
1283
- address: WHITELIST_HOOK_ADDRESS,
1284
- abi: WhitelistPreIntentHook_default,
1285
- functionName: "isWhitelisted",
1286
- args: [escrow, depositId, taker]
1287
- });
1597
+ var Offramp = class {
1598
+ walletClient;
1599
+ telemetry;
1600
+ constructor(options) {
1601
+ this.walletClient = options.walletClient;
1602
+ this.telemetry = createTelemetryContext({
1603
+ sdkVersion: SDK_VERSION,
1604
+ telemetry: options.telemetry,
1605
+ integratorId: options.integratorId,
1606
+ referralId: options.referralId
1607
+ });
1608
+ this.telemetry.emit("sdk.init", {
1609
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "unknown"
1610
+ });
1611
+ }
1612
+ async createDeposit(params, onProgress) {
1613
+ const flowTelemetry = createPerFlowTelemetry(
1614
+ this.telemetry,
1615
+ params.integratorId,
1616
+ params.referralId
1617
+ );
1618
+ const sanitizedParams = {
1619
+ ...params,
1620
+ integratorId: flowTelemetry.integratorId,
1621
+ referralId: flowTelemetry.referralId
1622
+ };
1623
+ const sanitizedKey = sanitizeAttributionId(params.idempotencyKey);
1624
+ const walletAddress = sanitizedKey ? getWalletAddress(this.walletClient) : null;
1625
+ const storageKey = walletAddress && sanitizedKey ? getIdempotencyStorageKey(walletAddress, sanitizedKey) : null;
1626
+ if (storageKey) {
1627
+ const existing = readIdempotencyResult(storageKey);
1628
+ if (existing) return existing;
1629
+ }
1630
+ const result = await offramp(
1631
+ this.walletClient,
1632
+ sanitizedParams,
1633
+ onProgress,
1634
+ flowTelemetry
1635
+ );
1636
+ if (storageKey) {
1637
+ writeIdempotencyResult(storageKey, result);
1638
+ }
1639
+ return result;
1640
+ }
1641
+ getQuote(input) {
1642
+ const amountUsdc = Number(input.amount);
1643
+ if (!Number.isFinite(amountUsdc) || amountUsdc <= 0) {
1644
+ throw new OfframpError("Amount must be a positive number", "VALIDATION");
1645
+ }
1646
+ if (!input.platform.currencies.includes(input.currency.code)) {
1647
+ throw new OfframpError(
1648
+ `${input.currency.code} is not supported on ${input.platform.name}`,
1649
+ "UNSUPPORTED"
1650
+ );
1651
+ }
1652
+ const vaultSpreadBps = 0;
1653
+ const spreadFactor = Math.max(0, 1 - vaultSpreadBps / 1e4);
1654
+ const expectedFiat = amountUsdc * spreadFactor;
1655
+ const effectiveRate = amountUsdc === 0 ? 0 : expectedFiat / amountUsdc;
1656
+ return {
1657
+ amountUsdc,
1658
+ expectedFiat,
1659
+ effectiveRate,
1660
+ vaultSpreadBps
1661
+ };
1662
+ }
1663
+ getVaultStatus() {
1664
+ return {
1665
+ rateManagerId: DELEGATE_RATE_MANAGER_ID,
1666
+ rateManagerAddress: RATE_MANAGER_REGISTRY_ADDRESS,
1667
+ feeRateBps: DELEGATE_MANAGER_FEE_BPS,
1668
+ escrow: ESCROW_ADDRESS,
1669
+ isActive: true
1670
+ };
1671
+ }
1672
+ listCurrencies() {
1673
+ return buildCurrencyGroups();
1674
+ }
1675
+ };
1676
+ function createOfframp(options) {
1677
+ return new Offramp(options);
1288
1678
  }
1289
1679
 
1290
1680
  export {
1681
+ ESCROW_ADDRESS,
1291
1682
  PLATFORMS,
1292
1683
  OfframpError,
1293
1684
  enableOtc,
@@ -1295,6 +1686,15 @@ export {
1295
1686
  getOtcLink,
1296
1687
  deposits,
1297
1688
  close,
1298
- offramp
1689
+ delegate,
1690
+ undelegate,
1691
+ sanitizeAttributionId,
1692
+ emitEvent,
1693
+ sendTelemetryEvent,
1694
+ createTelemetryContext,
1695
+ offramp,
1696
+ CURRENCIES,
1697
+ Offramp,
1698
+ createOfframp
1299
1699
  };
1300
- //# sourceMappingURL=chunk-QELK2M73.js.map
1700
+ //# sourceMappingURL=chunk-JQBKGGI2.js.map