@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/{chunk-OJY6DE3I.js → chunk-JQBKGGI2.js} +555 -213
- package/dist/chunk-JQBKGGI2.js.map +1 -0
- package/dist/{errors-Dtzrl98J.d.cts → errors-CfZrDly0.d.cts} +42 -2
- package/dist/{errors-Dtzrl98J.d.ts → errors-CfZrDly0.d.ts} +42 -2
- package/dist/index.cjs +556 -213
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -4
- package/dist/index.d.ts +37 -4
- package/dist/index.js +26 -18
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +591 -240
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +5 -2
- package/dist/react.d.ts +5 -2
- package/dist/react.js +33 -14
- package/dist/react.js.map +1 -1
- package/package.json +16 -6
- package/dist/chunk-OJY6DE3I.js.map +0 -1
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/
|
|
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,
|
|
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,
|
|
143
|
-
if (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,
|
|
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,
|
|
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
|
|
741
|
+
var import_sdk4 = require("@zkp2p/sdk");
|
|
722
742
|
function createSdkClient(walletClient) {
|
|
723
|
-
return new
|
|
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
|
|
832
|
+
var import_sdk5 = require("@zkp2p/sdk");
|
|
813
833
|
var indexerService = null;
|
|
814
834
|
function getIndexerService() {
|
|
815
835
|
if (!indexerService) {
|
|
816
|
-
const endpoint = (0,
|
|
817
|
-
const client = new
|
|
818
|
-
indexerService = new
|
|
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,
|
|
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,
|
|
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,
|
|
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) =>
|
|
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
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
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
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
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", "
|
|
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
|
-
|
|
1076
|
-
"
|
|
1077
|
-
"
|
|
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
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
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
|
-
|
|
1107
|
-
"
|
|
1296
|
+
"Payee registration failed",
|
|
1297
|
+
"REGISTRATION_FAILED",
|
|
1298
|
+
"registering",
|
|
1299
|
+
err
|
|
1108
1300
|
);
|
|
1109
1301
|
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
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
|
|
1186
|
-
|
|
1187
|
-
|
|
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
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1343
|
+
emitProgress({ step: "confirming", txHash: hash });
|
|
1344
|
+
let depositId = "";
|
|
1345
|
+
const receiptClient = client;
|
|
1346
|
+
if (typeof receiptClient.waitForTransactionReceipt === "function") {
|
|
1193
1347
|
try {
|
|
1194
|
-
const
|
|
1195
|
-
|
|
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
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
}
|
|
1227
|
-
|
|
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
|
-
"
|
|
1231
|
-
"
|
|
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
|
-
|
|
1238
|
-
|
|
1239
|
-
"
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
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
|
-
"
|
|
1255
|
-
"
|
|
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
|
-
|
|
1266
|
-
"
|
|
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
|
-
|
|
1274
|
-
|
|
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
|
|
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 [
|
|
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
|
-
|
|
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
|
-
|
|
1637
|
+
setState("idle");
|
|
1638
|
+
setLastError(null);
|
|
1299
1639
|
setStep(null);
|
|
1300
1640
|
setTxHash(null);
|
|
1301
1641
|
setDepositId(null);
|
|
1302
1642
|
try {
|
|
1303
|
-
const
|
|
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
|
-
|
|
1317
|
-
|
|
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
|
-
|
|
1679
|
+
lastError,
|
|
1680
|
+
error: lastError,
|
|
1330
1681
|
isLoading,
|
|
1331
1682
|
offramp: offramp2,
|
|
1332
1683
|
deposits,
|