@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.
- package/README.md +1 -1
- package/dist/{chunk-QELK2M73.js → chunk-JQBKGGI2.js} +738 -338
- 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 +657 -259
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +53 -4
- package/dist/index.d.ts +53 -4
- package/dist/index.js +34 -20
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +608 -271
- 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-QELK2M73.js.map +0 -1
|
@@ -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/
|
|
693
|
-
import {
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
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 !==
|
|
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
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
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
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
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", "
|
|
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
|
-
|
|
981
|
-
"
|
|
982
|
-
"
|
|
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
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
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
|
-
|
|
1012
|
-
"
|
|
1349
|
+
"Payee registration failed",
|
|
1350
|
+
"REGISTRATION_FAILED",
|
|
1351
|
+
"registering",
|
|
1352
|
+
err
|
|
1013
1353
|
);
|
|
1014
1354
|
}
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
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
|
|
1091
|
-
|
|
1092
|
-
|
|
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
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1396
|
+
emitProgress({ step: "confirming", txHash: hash });
|
|
1397
|
+
let depositId = "";
|
|
1398
|
+
const receiptClient = client;
|
|
1399
|
+
if (typeof receiptClient.waitForTransactionReceipt === "function") {
|
|
1098
1400
|
try {
|
|
1099
|
-
const
|
|
1100
|
-
|
|
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
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
}
|
|
1132
|
-
|
|
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
|
-
"
|
|
1136
|
-
"
|
|
1137
|
-
"
|
|
1138
|
-
|
|
1139
|
-
{ txHash: hash
|
|
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
|
-
|
|
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
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
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
|
|
1446
|
+
"User cancelled delegation",
|
|
1160
1447
|
"USER_CANCELLED",
|
|
1161
|
-
"
|
|
1162
|
-
|
|
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
|
|
1171
|
-
"
|
|
1172
|
-
"
|
|
1173
|
-
|
|
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/
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
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
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
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
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
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
|
-
|
|
1214
|
-
if (
|
|
1215
|
-
|
|
1216
|
-
|
|
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
|
-
|
|
1224
|
-
|
|
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
|
-
|
|
1239
|
-
|
|
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
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
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
|
|
1260
|
-
|
|
1261
|
-
|
|
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
|
-
|
|
1264
|
-
const
|
|
1265
|
-
if (
|
|
1266
|
-
|
|
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
|
-
|
|
1269
|
-
address: WHITELIST_HOOK_ADDRESS,
|
|
1270
|
-
abi: WhitelistPreIntentHook_default,
|
|
1271
|
-
functionName,
|
|
1272
|
-
args
|
|
1273
|
-
});
|
|
1595
|
+
return grouped;
|
|
1274
1596
|
}
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
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
|
-
|
|
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-
|
|
1700
|
+
//# sourceMappingURL=chunk-JQBKGGI2.js.map
|