@oasisprotocol/privana-sdk 0.5.6 → 0.5.8
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 +88 -7
- package/dist/{chunk-TNR7AA52.cjs → chunk-EXFISDHQ.cjs} +1241 -101
- package/dist/chunk-EXFISDHQ.cjs.map +1 -0
- package/dist/{chunk-DLGIPF4N.js → chunk-ONXJGUOH.js} +1233 -103
- package/dist/chunk-ONXJGUOH.js.map +1 -0
- package/dist/index.cjs +781 -305
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +13 -5
- package/dist/index.d.ts +13 -5
- package/dist/index.js +545 -69
- package/dist/index.js.map +1 -1
- package/dist/on-ramp.cjs +11 -3
- package/dist/on-ramp.d.cts +61 -7
- package/dist/on-ramp.d.ts +61 -7
- package/dist/on-ramp.js +1 -1
- package/dist/{pending-lock-BIMYw4gd.d.cts → pending-lock-DIL2jCaT.d.cts} +33 -1
- package/dist/{pending-lock-BIMYw4gd.d.ts → pending-lock-DIL2jCaT.d.ts} +33 -1
- package/package.json +1 -1
- package/dist/chunk-DLGIPF4N.js.map +0 -1
- package/dist/chunk-TNR7AA52.cjs.map +0 -1
|
@@ -4,7 +4,7 @@ import { createContext, useContext, useRef, useCallback, useSyncExternalStore, u
|
|
|
4
4
|
import { WagmiContext, useConfig, useChainId, useSwitchChain, useAccount, useWalletClient } from 'wagmi';
|
|
5
5
|
import { watchAccount, getAccount, getWalletClient } from 'wagmi/actions';
|
|
6
6
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
7
|
-
import { parseAbiItem, zeroAddress, walletActions, hexToString, formatUnits,
|
|
7
|
+
import { parseAbiItem, zeroAddress, parseUnits, walletActions, hexToString, formatUnits, createClient, custom, decodeEventLog } from 'viem';
|
|
8
8
|
import { useQueryClient } from '@tanstack/react-query';
|
|
9
9
|
import { Slot } from '@radix-ui/react-slot';
|
|
10
10
|
import { cva } from 'class-variance-authority';
|
|
@@ -29,6 +29,24 @@ var config_default = {
|
|
|
29
29
|
name: "Ethereum Sepolia",
|
|
30
30
|
explorerUrl: "https://sepolia.etherscan.io",
|
|
31
31
|
explorerName: "Etherscan"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 1,
|
|
35
|
+
name: "Ethereum",
|
|
36
|
+
explorerUrl: "https://etherscan.io",
|
|
37
|
+
explorerName: "Etherscan"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 8453,
|
|
41
|
+
name: "Base",
|
|
42
|
+
explorerUrl: "https://basescan.org",
|
|
43
|
+
explorerName: "BaseScan"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 999,
|
|
47
|
+
name: "HyperEVM",
|
|
48
|
+
explorerUrl: "https://hyperevmscan.io",
|
|
49
|
+
explorerName: "HyperEVMScan"
|
|
32
50
|
}
|
|
33
51
|
],
|
|
34
52
|
networks: {
|
|
@@ -623,6 +641,12 @@ var PrivanaClient = class _PrivanaClient {
|
|
|
623
641
|
moonpay_currency_code: request.moonpay_currency_code
|
|
624
642
|
});
|
|
625
643
|
}
|
|
644
|
+
async createOnRampSession(request) {
|
|
645
|
+
return this.http.post("/v1/accounting/onramp/session", {
|
|
646
|
+
transaction_id: request.transaction_id,
|
|
647
|
+
ip_attestation: request.ip_attestation
|
|
648
|
+
});
|
|
649
|
+
}
|
|
626
650
|
async updateOnRamp(transactionId, request) {
|
|
627
651
|
return this.http.post(
|
|
628
652
|
`/v1/accounting/onramp/${encodeURIComponent(transactionId)}`,
|
|
@@ -1687,6 +1711,7 @@ var DEFAULT_NETWORK_CONFIG = NETWORK_CONFIG.testnet;
|
|
|
1687
1711
|
function PrivanaProvider({
|
|
1688
1712
|
children,
|
|
1689
1713
|
networkConfig: networkConfigOverride,
|
|
1714
|
+
onRamp,
|
|
1690
1715
|
tokens,
|
|
1691
1716
|
chains,
|
|
1692
1717
|
pollingInterval = 1e4,
|
|
@@ -1720,7 +1745,9 @@ function PrivanaProvider({
|
|
|
1720
1745
|
networkConfigOverride?.chainId,
|
|
1721
1746
|
networkConfigOverride?.name,
|
|
1722
1747
|
networkConfigOverride?.accountingContract,
|
|
1723
|
-
networkConfigOverride?.apiUrl
|
|
1748
|
+
networkConfigOverride?.apiUrl,
|
|
1749
|
+
networkConfigOverride?.moonpayApiUrl,
|
|
1750
|
+
networkConfigOverride?.moonpayApiKey
|
|
1724
1751
|
]);
|
|
1725
1752
|
const resolvedChains = useMemo(() => {
|
|
1726
1753
|
if (chains && chains.length > 0) return chains;
|
|
@@ -1903,6 +1930,7 @@ function PrivanaProvider({
|
|
|
1903
1930
|
() => ({
|
|
1904
1931
|
client,
|
|
1905
1932
|
networkConfig,
|
|
1933
|
+
onRamp,
|
|
1906
1934
|
enabledTokens,
|
|
1907
1935
|
defaultToken: enabledTokens[0],
|
|
1908
1936
|
getTokenById,
|
|
@@ -1923,6 +1951,7 @@ function PrivanaProvider({
|
|
|
1923
1951
|
[
|
|
1924
1952
|
client,
|
|
1925
1953
|
networkConfig,
|
|
1954
|
+
onRamp,
|
|
1926
1955
|
enabledTokens,
|
|
1927
1956
|
getTokenById,
|
|
1928
1957
|
getChainById2,
|
|
@@ -2674,6 +2703,176 @@ function creditedAmountFromResponse(response, requestedAmount) {
|
|
|
2674
2703
|
function isDefinitiveCandidateFailure(error) {
|
|
2675
2704
|
return error instanceof AccountingApiError && error.statusCode === 400;
|
|
2676
2705
|
}
|
|
2706
|
+
var BYTES32_PATTERN = /^0x[0-9a-fA-F]{64}$/;
|
|
2707
|
+
var PROVIDER_ASSET_CODE_PATTERN = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
2708
|
+
var TRANSAK_MINIMUM_TARGET_PERCENT = 105n;
|
|
2709
|
+
function resolveProductOnRamp({
|
|
2710
|
+
config,
|
|
2711
|
+
enabledTokens,
|
|
2712
|
+
legacyToken,
|
|
2713
|
+
moonpayApiKey
|
|
2714
|
+
}) {
|
|
2715
|
+
if (config === void 0) {
|
|
2716
|
+
if (!moonpayApiKey) {
|
|
2717
|
+
return unavailable("moonpay", legacyToken, false, "MoonPay is not configured.");
|
|
2718
|
+
}
|
|
2719
|
+
if (!legacyToken) {
|
|
2720
|
+
return available("moonpay", void 0, void 0, false);
|
|
2721
|
+
}
|
|
2722
|
+
if (!legacyToken.moonpayCurrencyCode) {
|
|
2723
|
+
return unavailable(
|
|
2724
|
+
"moonpay",
|
|
2725
|
+
legacyToken,
|
|
2726
|
+
false,
|
|
2727
|
+
`${legacyToken.symbol} isn\u2019t available for card purchases yet.`
|
|
2728
|
+
);
|
|
2729
|
+
}
|
|
2730
|
+
return available("moonpay", legacyToken, legacyToken.moonpayCurrencyCode, false);
|
|
2731
|
+
}
|
|
2732
|
+
if (!isValidConfig(config)) {
|
|
2733
|
+
return unavailable(null, void 0, true, "Card on-ramp configuration is invalid.");
|
|
2734
|
+
}
|
|
2735
|
+
const providerAssetCode = config.providerAssetCode.trim().toLowerCase();
|
|
2736
|
+
const token = enabledTokens.find(
|
|
2737
|
+
(candidate) => candidate.id.toLowerCase() === config.tokenId.toLowerCase()
|
|
2738
|
+
);
|
|
2739
|
+
if (!token) {
|
|
2740
|
+
return unavailable(
|
|
2741
|
+
config.provider,
|
|
2742
|
+
void 0,
|
|
2743
|
+
true,
|
|
2744
|
+
"The configured card-purchase token is not enabled."
|
|
2745
|
+
);
|
|
2746
|
+
}
|
|
2747
|
+
if (token.contract.toLowerCase() === zeroAddress) {
|
|
2748
|
+
return unavailable(config.provider, token, true, "Card purchases require an ERC20 token.");
|
|
2749
|
+
}
|
|
2750
|
+
if (config.provider === "moonpay") {
|
|
2751
|
+
if (!moonpayApiKey) {
|
|
2752
|
+
return unavailable("moonpay", token, true, "MoonPay is not configured.");
|
|
2753
|
+
}
|
|
2754
|
+
if (!token.moonpayCurrencyCode || token.moonpayCurrencyCode.toLowerCase() !== providerAssetCode) {
|
|
2755
|
+
return unavailable(
|
|
2756
|
+
"moonpay",
|
|
2757
|
+
token,
|
|
2758
|
+
true,
|
|
2759
|
+
"The configured MoonPay asset does not match the selected token."
|
|
2760
|
+
);
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
return available(config.provider, token, providerAssetCode, true);
|
|
2764
|
+
}
|
|
2765
|
+
function createProductOnRampFlowSnapshot({
|
|
2766
|
+
id,
|
|
2767
|
+
selection,
|
|
2768
|
+
amount,
|
|
2769
|
+
allowance,
|
|
2770
|
+
lockServiceAddress,
|
|
2771
|
+
moonpayApiKey,
|
|
2772
|
+
scope
|
|
2773
|
+
}) {
|
|
2774
|
+
if (selection.unavailableReason || !selection.provider || !selection.token || !selection.providerAssetCode) {
|
|
2775
|
+
throw new Error(selection.unavailableReason ?? "Card on-ramp configuration is unavailable.");
|
|
2776
|
+
}
|
|
2777
|
+
const amountBaseUnits = parseUnits(amount, selection.token.decimals);
|
|
2778
|
+
if (amountBaseUnits <= 0n) throw new Error("Card purchase amount must be positive.");
|
|
2779
|
+
if (!scope.beneficiaryAddress) {
|
|
2780
|
+
throw new Error("Sign in or connect a wallet before starting a card purchase.");
|
|
2781
|
+
}
|
|
2782
|
+
if (allowance && !lockServiceAddress) {
|
|
2783
|
+
throw new Error("The lock service address is not configured.");
|
|
2784
|
+
}
|
|
2785
|
+
return {
|
|
2786
|
+
id,
|
|
2787
|
+
selection: {
|
|
2788
|
+
...selection,
|
|
2789
|
+
provider: selection.provider,
|
|
2790
|
+
token: { ...selection.token },
|
|
2791
|
+
providerAssetCode: selection.providerAssetCode,
|
|
2792
|
+
unavailableReason: null
|
|
2793
|
+
},
|
|
2794
|
+
amount,
|
|
2795
|
+
amountBaseUnits,
|
|
2796
|
+
allowance: cloneAllowance(allowance),
|
|
2797
|
+
requiresLock: allowance !== void 0,
|
|
2798
|
+
lockServiceAddress: allowance ? lockServiceAddress : void 0,
|
|
2799
|
+
moonpayApiKey: selection.provider === "moonpay" ? moonpayApiKey : void 0,
|
|
2800
|
+
scope: { ...scope }
|
|
2801
|
+
};
|
|
2802
|
+
}
|
|
2803
|
+
function matchesProductOnRampScope(expected, current) {
|
|
2804
|
+
return expected.apiUrl === current.apiUrl && expected.accountingChainId === current.accountingChainId && expected.accountingContract.toLowerCase() === current.accountingContract.toLowerCase() && expected.beneficiaryAddress?.toLowerCase() === current.beneficiaryAddress?.toLowerCase();
|
|
2805
|
+
}
|
|
2806
|
+
function isMoonPayProductOnRamp(selection) {
|
|
2807
|
+
return selection.provider === "moonpay" && !selection.unavailableReason;
|
|
2808
|
+
}
|
|
2809
|
+
function getTransakMinimumTargetBaseUnits(minimum) {
|
|
2810
|
+
return (minimum * TRANSAK_MINIMUM_TARGET_PERCENT + 99n) / 100n;
|
|
2811
|
+
}
|
|
2812
|
+
function getOnRampTokenFingerprint(token) {
|
|
2813
|
+
if (!token) return "";
|
|
2814
|
+
return [token.id.toLowerCase(), token.contract.toLowerCase(), token.chainId, token.decimals].join(
|
|
2815
|
+
"\0"
|
|
2816
|
+
);
|
|
2817
|
+
}
|
|
2818
|
+
function matchesFrozenOnRampToken(frozen, live) {
|
|
2819
|
+
return live !== void 0 && getOnRampTokenFingerprint(live) === getOnRampTokenFingerprint(frozen);
|
|
2820
|
+
}
|
|
2821
|
+
function createProductOnRampOutcomeCallbacks({
|
|
2822
|
+
requiresLock,
|
|
2823
|
+
onComplete,
|
|
2824
|
+
onLockFailed
|
|
2825
|
+
}) {
|
|
2826
|
+
return {
|
|
2827
|
+
onCredited: requiresLock ? void 0 : onComplete,
|
|
2828
|
+
onLockSubmitted: onComplete,
|
|
2829
|
+
onLockFailed
|
|
2830
|
+
};
|
|
2831
|
+
}
|
|
2832
|
+
function isValidConfig(config) {
|
|
2833
|
+
const candidate = config;
|
|
2834
|
+
if (!isPlainRecord(candidate)) return false;
|
|
2835
|
+
if (candidate.provider !== "moonpay" && candidate.provider !== "transak") return false;
|
|
2836
|
+
if (typeof candidate.tokenId !== "string" || !BYTES32_PATTERN.test(candidate.tokenId)) {
|
|
2837
|
+
return false;
|
|
2838
|
+
}
|
|
2839
|
+
return typeof candidate.providerAssetCode === "string" && PROVIDER_ASSET_CODE_PATTERN.test(candidate.providerAssetCode.trim());
|
|
2840
|
+
}
|
|
2841
|
+
function available(provider, token, providerAssetCode, tokenSelectionLocked) {
|
|
2842
|
+
return {
|
|
2843
|
+
provider,
|
|
2844
|
+
token,
|
|
2845
|
+
providerAssetCode,
|
|
2846
|
+
tokenSelectionLocked,
|
|
2847
|
+
unavailableReason: null
|
|
2848
|
+
};
|
|
2849
|
+
}
|
|
2850
|
+
function unavailable(provider, token, tokenSelectionLocked, unavailableReason) {
|
|
2851
|
+
return {
|
|
2852
|
+
provider,
|
|
2853
|
+
token,
|
|
2854
|
+
providerAssetCode: void 0,
|
|
2855
|
+
tokenSelectionLocked,
|
|
2856
|
+
unavailableReason
|
|
2857
|
+
};
|
|
2858
|
+
}
|
|
2859
|
+
function isPlainRecord(value) {
|
|
2860
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
2861
|
+
const prototype = Object.getPrototypeOf(value);
|
|
2862
|
+
return prototype === Object.prototype || prototype === null;
|
|
2863
|
+
}
|
|
2864
|
+
function cloneAllowance(allowance) {
|
|
2865
|
+
if (!allowance) return void 0;
|
|
2866
|
+
return {
|
|
2867
|
+
value: allowance.value,
|
|
2868
|
+
minAmount: allowance.minAmount,
|
|
2869
|
+
lockDuration: allowance.lockDuration,
|
|
2870
|
+
terms: allowance.terms ? {
|
|
2871
|
+
permissions: allowance.terms.permissions?.map((term) => ({ ...term })),
|
|
2872
|
+
restrictions: allowance.terms.restrictions?.map((term) => ({ ...term }))
|
|
2873
|
+
} : void 0
|
|
2874
|
+
};
|
|
2875
|
+
}
|
|
2677
2876
|
function cn(...inputs) {
|
|
2678
2877
|
return twMerge(clsx(inputs));
|
|
2679
2878
|
}
|
|
@@ -2791,6 +2990,9 @@ var moonPayOnRampAdapter = {
|
|
|
2791
2990
|
token_id: tokenId,
|
|
2792
2991
|
chain_id: chainId,
|
|
2793
2992
|
moonpay_transaction_id: intentId === providerTransactionId ? void 0 : providerTransactionId
|
|
2993
|
+
}),
|
|
2994
|
+
recordDeposit: async ({ client, record, depositTxHash }) => client.updateOnRamp(record.transaction_id, {
|
|
2995
|
+
deposit_tx_hash: depositTxHash
|
|
2794
2996
|
})
|
|
2795
2997
|
};
|
|
2796
2998
|
function normalizeMoonPayProviderEvent(kind, event) {
|
|
@@ -3078,15 +3280,39 @@ function assertOnRampRecordProvider(record, configuredProvider) {
|
|
|
3078
3280
|
);
|
|
3079
3281
|
}
|
|
3080
3282
|
}
|
|
3283
|
+
function assertCreatedOnRampIntent(record, configuredProvider, expected) {
|
|
3284
|
+
assertOnRampRecordProvider(record, configuredProvider);
|
|
3285
|
+
if (typeof record.provider_asset_code !== "string" || record.provider_asset_code.toLowerCase() !== expected.providerAssetCode.toLowerCase()) {
|
|
3286
|
+
throw new Error(
|
|
3287
|
+
`On-ramp intent asset ${record.provider_asset_code} does not match requested asset ${expected.providerAssetCode}`
|
|
3288
|
+
);
|
|
3289
|
+
}
|
|
3290
|
+
if (typeof record.token_id !== "string" || record.token_id.toLowerCase() !== expected.tokenId.toLowerCase()) {
|
|
3291
|
+
throw new Error("On-ramp intent token does not match the requested token");
|
|
3292
|
+
}
|
|
3293
|
+
if (record.chain_id !== expected.chainId) {
|
|
3294
|
+
throw new Error("On-ramp intent chain does not match the requested chain");
|
|
3295
|
+
}
|
|
3296
|
+
if (typeof record.wallet_address !== "string" || record.wallet_address.toLowerCase() !== expected.walletAddress.toLowerCase()) {
|
|
3297
|
+
throw new Error("On-ramp intent wallet does not match the Privana deposit address");
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3081
3300
|
function matchesOnRampTransaction(record, transactionId) {
|
|
3082
3301
|
return record.transaction_id === transactionId || record.external_transaction_id === transactionId || record.provider_transaction_id === transactionId || record.moonpay_transaction_id === transactionId;
|
|
3083
3302
|
}
|
|
3084
|
-
function getOnRampVerificationKey(record) {
|
|
3085
|
-
return record.on_chain_tx_hash ?? record.transaction_id;
|
|
3086
|
-
}
|
|
3087
3303
|
function getOnRampIntentId(record) {
|
|
3088
3304
|
return record.external_transaction_id ?? record.transaction_id;
|
|
3089
3305
|
}
|
|
3306
|
+
function getOnRampVerificationKey(record) {
|
|
3307
|
+
return getOnRampIntentId(record);
|
|
3308
|
+
}
|
|
3309
|
+
function canRetryOnRampVerification(record, activeVerificationId) {
|
|
3310
|
+
return Boolean(record.on_chain_tx_hash) && activeVerificationId === null;
|
|
3311
|
+
}
|
|
3312
|
+
async function recordOnRampProviderDeposit(adapter, context) {
|
|
3313
|
+
if (context.record.provider !== adapter.provider) return void 0;
|
|
3314
|
+
return adapter.recordDeposit?.(context);
|
|
3315
|
+
}
|
|
3090
3316
|
async function verifyPendingOnRampsSequentially({
|
|
3091
3317
|
records,
|
|
3092
3318
|
shouldStop,
|
|
@@ -3110,13 +3336,19 @@ async function verifyPendingOnRampsSequentially({
|
|
|
3110
3336
|
|
|
3111
3337
|
// src/sdk/on-ramp/recovery.ts
|
|
3112
3338
|
var MAX_UNRESOLVED_ONRAMP_INTENTS = 10;
|
|
3339
|
+
var MAX_CREDITED_ONRAMP_VERIFICATIONS = 1e3;
|
|
3113
3340
|
var MIN_ONRAMP_PENDING_REQUEST_INTERVAL_MS = 1e4;
|
|
3114
3341
|
var ONRAMP_INTENT_RETENTION_MS = 365 * 24 * 60 * 60 * 1e3;
|
|
3115
3342
|
var ONRAMP_RECOVERY_VERSION = 1;
|
|
3343
|
+
var ONRAMP_CREDIT_RECOVERY_VERSION = 2;
|
|
3116
3344
|
function recoveryKey(scope) {
|
|
3117
3345
|
const api = encodeURIComponent(scope.apiUrl.replace(/\/$/, ""));
|
|
3118
3346
|
return `privana:onramp-intents:${api}:${scope.chainId}:${scope.userAddress.toLowerCase()}`;
|
|
3119
3347
|
}
|
|
3348
|
+
function creditedRecoveryKey(scope) {
|
|
3349
|
+
const api = encodeURIComponent(scope.apiUrl.replace(/\/$/, ""));
|
|
3350
|
+
return `privana:onramp-credited:${api}:${scope.chainId}:${scope.userAddress.toLowerCase()}`;
|
|
3351
|
+
}
|
|
3120
3352
|
function isIntent(value) {
|
|
3121
3353
|
if (!value || typeof value !== "object") return false;
|
|
3122
3354
|
const intent = value;
|
|
@@ -3135,6 +3367,25 @@ function writeIntents(scope, intents) {
|
|
|
3135
3367
|
})
|
|
3136
3368
|
);
|
|
3137
3369
|
}
|
|
3370
|
+
function isCreditedVerification(value) {
|
|
3371
|
+
if (!value || typeof value !== "object") return false;
|
|
3372
|
+
const verification = value;
|
|
3373
|
+
return typeof verification.verificationKey === "string" && verification.verificationKey.length > 0 && verification.verificationKey.length <= 512 && Number.isFinite(verification.savedAt) && (verification.savedAt ?? 0) > 0;
|
|
3374
|
+
}
|
|
3375
|
+
function writeCreditedVerifications(scope, verifications) {
|
|
3376
|
+
const key = creditedRecoveryKey(scope);
|
|
3377
|
+
if (verifications.length === 0) {
|
|
3378
|
+
removeBrowserStorageItem(key);
|
|
3379
|
+
return true;
|
|
3380
|
+
}
|
|
3381
|
+
return setBrowserStorageItem(
|
|
3382
|
+
key,
|
|
3383
|
+
JSON.stringify({
|
|
3384
|
+
version: ONRAMP_CREDIT_RECOVERY_VERSION,
|
|
3385
|
+
verifications
|
|
3386
|
+
})
|
|
3387
|
+
);
|
|
3388
|
+
}
|
|
3138
3389
|
function loadUnresolvedOnRampIntents(scope, now = Date.now()) {
|
|
3139
3390
|
const key = recoveryKey(scope);
|
|
3140
3391
|
try {
|
|
@@ -3166,6 +3417,48 @@ function forgetUnresolvedOnRampIntent(scope, transactionId, now = Date.now()) {
|
|
|
3166
3417
|
);
|
|
3167
3418
|
writeIntents(scope, intents);
|
|
3168
3419
|
}
|
|
3420
|
+
function loadCreditedOnRampVerifications(scope, now = Date.now()) {
|
|
3421
|
+
const key = creditedRecoveryKey(scope);
|
|
3422
|
+
try {
|
|
3423
|
+
const raw = getBrowserStorageItem(key);
|
|
3424
|
+
if (!raw) return [];
|
|
3425
|
+
const parsed = JSON.parse(raw);
|
|
3426
|
+
if (parsed.version !== ONRAMP_CREDIT_RECOVERY_VERSION || !Array.isArray(parsed.verifications)) {
|
|
3427
|
+
removeBrowserStorageItem(key);
|
|
3428
|
+
return [];
|
|
3429
|
+
}
|
|
3430
|
+
const retained = parsed.verifications.filter(isCreditedVerification).filter((verification) => now - verification.savedAt <= ONRAMP_INTENT_RETENTION_MS).sort((left, right) => left.savedAt - right.savedAt).slice(-MAX_CREDITED_ONRAMP_VERIFICATIONS);
|
|
3431
|
+
if (retained.length !== parsed.verifications.length) {
|
|
3432
|
+
writeCreditedVerifications(scope, retained);
|
|
3433
|
+
}
|
|
3434
|
+
return retained;
|
|
3435
|
+
} catch {
|
|
3436
|
+
removeBrowserStorageItem(key);
|
|
3437
|
+
return [];
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
3440
|
+
function rememberCreditedOnRampVerification(scope, verificationKey, now = Date.now()) {
|
|
3441
|
+
if (!verificationKey || verificationKey.length > 512) return false;
|
|
3442
|
+
const verifications = loadCreditedOnRampVerifications(scope, now).filter(
|
|
3443
|
+
(verification) => verification.verificationKey !== verificationKey
|
|
3444
|
+
);
|
|
3445
|
+
verifications.push({ verificationKey, savedAt: now });
|
|
3446
|
+
return writeCreditedVerifications(scope, verifications.slice(-MAX_CREDITED_ONRAMP_VERIFICATIONS));
|
|
3447
|
+
}
|
|
3448
|
+
function finalizeCreditedOnRampIntent(scope, record, now = Date.now()) {
|
|
3449
|
+
const remembered = rememberCreditedOnRampVerification(
|
|
3450
|
+
scope,
|
|
3451
|
+
getOnRampVerificationKey(record),
|
|
3452
|
+
now
|
|
3453
|
+
);
|
|
3454
|
+
if (remembered) forgetUnresolvedOnRampIntent(scope, getOnRampIntentId(record), now);
|
|
3455
|
+
return remembered;
|
|
3456
|
+
}
|
|
3457
|
+
function filterCreditedOnRampRecords(records, creditedVerificationKeys) {
|
|
3458
|
+
const credited = new Set(creditedVerificationKeys);
|
|
3459
|
+
if (credited.size === 0) return [...records];
|
|
3460
|
+
return records.filter((record) => !credited.has(getOnRampVerificationKey(record)));
|
|
3461
|
+
}
|
|
3169
3462
|
function discardInvalidOnRampIntent(scope, invalidIntentId, activeIntentId) {
|
|
3170
3463
|
forgetUnresolvedOnRampIntent(scope, invalidIntentId);
|
|
3171
3464
|
const invalidatedActiveIntent = activeIntentId === invalidIntentId;
|
|
@@ -3216,17 +3509,31 @@ async function getPendingOnRampsWithRecovery({
|
|
|
3216
3509
|
} catch (error) {
|
|
3217
3510
|
if (!isBadRequest(error) || bounded.length === 0) throw error;
|
|
3218
3511
|
}
|
|
3219
|
-
const
|
|
3512
|
+
const validResponses = [];
|
|
3220
3513
|
for (const intentId of bounded) {
|
|
3221
3514
|
try {
|
|
3222
|
-
await client.getPendingOnRamps([intentId]);
|
|
3223
|
-
valid.push(intentId);
|
|
3515
|
+
validResponses.push(await client.getPendingOnRamps([intentId]));
|
|
3224
3516
|
} catch (error) {
|
|
3225
3517
|
if (!isBadRequest(error)) throw error;
|
|
3226
3518
|
onInvalidIntent(intentId);
|
|
3227
3519
|
}
|
|
3228
3520
|
}
|
|
3229
|
-
return client.getPendingOnRamps(
|
|
3521
|
+
if (validResponses.length === 0) return client.getPendingOnRamps([]);
|
|
3522
|
+
return { pending: mergePendingOnRampRows(validResponses.flatMap((response) => response.pending)) };
|
|
3523
|
+
}
|
|
3524
|
+
function mergePendingOnRampRows(rows) {
|
|
3525
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3526
|
+
return rows.filter((record) => {
|
|
3527
|
+
const identifier = record.provider_transaction_id || record.transaction_id;
|
|
3528
|
+
const key = `${record.provider}\0${identifier}`;
|
|
3529
|
+
if (!identifier || seen.has(key)) return false;
|
|
3530
|
+
seen.add(key);
|
|
3531
|
+
return true;
|
|
3532
|
+
}).sort(
|
|
3533
|
+
(left, right) => right.updated_at - left.updated_at || right.created_at - left.created_at || String(right.provider_transaction_id ?? "").localeCompare(
|
|
3534
|
+
String(left.provider_transaction_id ?? "")
|
|
3535
|
+
)
|
|
3536
|
+
);
|
|
3230
3537
|
}
|
|
3231
3538
|
function isBadRequest(error) {
|
|
3232
3539
|
return error instanceof AccountingApiError && error.statusCode === 400;
|
|
@@ -3234,6 +3541,17 @@ function isBadRequest(error) {
|
|
|
3234
3541
|
var ERC20_TRANSFER_EVENT = parseAbiItem(
|
|
3235
3542
|
"event Transfer(address indexed from, address indexed to, uint256 value)"
|
|
3236
3543
|
);
|
|
3544
|
+
function decodeErc20TransferLog(log) {
|
|
3545
|
+
try {
|
|
3546
|
+
return decodeEventLog({
|
|
3547
|
+
abi: [ERC20_TRANSFER_EVENT],
|
|
3548
|
+
data: log.data,
|
|
3549
|
+
topics: log.topics
|
|
3550
|
+
});
|
|
3551
|
+
} catch {
|
|
3552
|
+
return void 0;
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3237
3555
|
function assertErc20OnRampToken(tokenAddress) {
|
|
3238
3556
|
if (tokenAddress.toLowerCase() === zeroAddress) {
|
|
3239
3557
|
throw new Error("On-ramp verification supports ERC-20 tokens only");
|
|
@@ -3243,23 +3561,31 @@ function erc20MinDepositBaseUnits(minDepositByChain, chainId) {
|
|
|
3243
3561
|
const minimum = minDepositByChain?.[String(chainId)]?.erc20;
|
|
3244
3562
|
return minimum !== void 0 && /^\d+$/.test(minimum) ? BigInt(minimum) : void 0;
|
|
3245
3563
|
}
|
|
3246
|
-
function
|
|
3247
|
-
let
|
|
3564
|
+
function resolveErc20OnRampTransfer(logs, tokenAddress, depositAddress) {
|
|
3565
|
+
let match;
|
|
3248
3566
|
for (const log of logs) {
|
|
3249
3567
|
if (log.address.toLowerCase() !== tokenAddress.toLowerCase()) continue;
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
topics: log.topics
|
|
3255
|
-
});
|
|
3256
|
-
if (decoded.eventName === "Transfer" && decoded.args.to.toLowerCase() === depositAddress.toLowerCase()) {
|
|
3257
|
-
delivered += decoded.args.value;
|
|
3258
|
-
}
|
|
3259
|
-
} catch {
|
|
3568
|
+
const decoded = decodeErc20TransferLog(log);
|
|
3569
|
+
if (!decoded) continue;
|
|
3570
|
+
if (decoded.eventName !== "Transfer" || decoded.args.to.toLowerCase() !== depositAddress.toLowerCase()) {
|
|
3571
|
+
continue;
|
|
3260
3572
|
}
|
|
3573
|
+
if (match) {
|
|
3574
|
+
throw new Error("On-ramp receipt contains multiple matching ERC-20 Transfer logs");
|
|
3575
|
+
}
|
|
3576
|
+
const logIndex = log.logIndex;
|
|
3577
|
+
if (typeof logIndex !== "number" || !Number.isSafeInteger(logIndex) || logIndex < 0) {
|
|
3578
|
+
throw new Error("Matching on-ramp ERC-20 Transfer does not have a valid log index");
|
|
3579
|
+
}
|
|
3580
|
+
match = { amount: decoded.args.value, logIndex };
|
|
3581
|
+
}
|
|
3582
|
+
if (!match) {
|
|
3583
|
+
throw new Error("On-ramp receipt does not contain a matching ERC-20 Transfer");
|
|
3261
3584
|
}
|
|
3262
|
-
|
|
3585
|
+
if (match.amount <= 0n) {
|
|
3586
|
+
throw new Error("Matching on-ramp ERC-20 Transfer must have a positive amount");
|
|
3587
|
+
}
|
|
3588
|
+
return match;
|
|
3263
3589
|
}
|
|
3264
3590
|
|
|
3265
3591
|
// src/sdk/on-ramp/settlement.ts
|
|
@@ -3341,6 +3667,12 @@ function useEnsureCorrectChain() {
|
|
|
3341
3667
|
var DEFAULT_DELIVERY_TIMEOUT_MS = 12e4;
|
|
3342
3668
|
var DEFAULT_VERIFICATION_TIMEOUT_MS = 10 * 6e4;
|
|
3343
3669
|
var DEFAULT_FINALITY_RETRY_INTERVAL_MS = 15e3;
|
|
3670
|
+
function bindOnRampFlowSession(ref, flowSession) {
|
|
3671
|
+
ref.current = flowSession;
|
|
3672
|
+
return () => {
|
|
3673
|
+
if (ref.current === flowSession) ref.current = null;
|
|
3674
|
+
};
|
|
3675
|
+
}
|
|
3344
3676
|
function useOnRamp(options) {
|
|
3345
3677
|
const {
|
|
3346
3678
|
adapter,
|
|
@@ -3382,6 +3714,7 @@ function useOnRamp(options) {
|
|
|
3382
3714
|
const flowSession = useMemo(() => Symbol(flowIdentity), [flowIdentity]);
|
|
3383
3715
|
const flowSessionRef = useRef(flowSession);
|
|
3384
3716
|
flowSessionRef.current = flowSession;
|
|
3717
|
+
useEffect(() => bindOnRampFlowSession(flowSessionRef, flowSession), [flowSession]);
|
|
3385
3718
|
const [status, setStatus] = useState("idle");
|
|
3386
3719
|
const [pending, setPending] = useState([]);
|
|
3387
3720
|
const [error, setError] = useState(null);
|
|
@@ -3404,6 +3737,7 @@ function useOnRamp(options) {
|
|
|
3404
3737
|
const activeVerificationSurfacesFailureRef = useRef(false);
|
|
3405
3738
|
const lockOwnerRef = useRef(null);
|
|
3406
3739
|
const triggeredVerificationKeysRef = useRef(/* @__PURE__ */ new Set());
|
|
3740
|
+
const creditedVerificationKeysRef = useRef(/* @__PURE__ */ new Set());
|
|
3407
3741
|
const activeVerificationDoneRef = useRef(null);
|
|
3408
3742
|
const closeReconcilePromiseRef = useRef(null);
|
|
3409
3743
|
const deliveryWaitPromiseRef = useRef(null);
|
|
@@ -3511,7 +3845,13 @@ function useOnRamp(options) {
|
|
|
3511
3845
|
}
|
|
3512
3846
|
})
|
|
3513
3847
|
);
|
|
3514
|
-
|
|
3848
|
+
const creditedKeys = recoveryScope ? loadCreditedOnRampVerifications(recoveryScope).map(
|
|
3849
|
+
(verification) => verification.verificationKey
|
|
3850
|
+
) : [];
|
|
3851
|
+
return filterCreditedOnRampRecords(rows, [
|
|
3852
|
+
...creditedKeys,
|
|
3853
|
+
...creditedVerificationKeysRef.current
|
|
3854
|
+
]);
|
|
3515
3855
|
}, [emitDebug, executeOnRampPrivateRead, flowSession, recoveryScope]);
|
|
3516
3856
|
const readPendingRows = useMemo(
|
|
3517
3857
|
() => createPendingOnRampReadCoordinator({
|
|
@@ -3554,7 +3894,8 @@ function useOnRamp(options) {
|
|
|
3554
3894
|
activeVerificationDoneRef.current = null;
|
|
3555
3895
|
}, []);
|
|
3556
3896
|
const submitPendingLockAfterCredit = useCallback(
|
|
3557
|
-
async (
|
|
3897
|
+
async (record, userAddress, creditedAmount) => {
|
|
3898
|
+
const transactionId = record.transaction_id;
|
|
3558
3899
|
try {
|
|
3559
3900
|
const settlement = await settlePendingOnRampLock({
|
|
3560
3901
|
client,
|
|
@@ -3569,7 +3910,8 @@ function useOnRamp(options) {
|
|
|
3569
3910
|
"not-found"
|
|
3570
3911
|
);
|
|
3571
3912
|
emitDebug("lock:not-found");
|
|
3572
|
-
(onLockFailedRef.current
|
|
3913
|
+
if (onLockFailedRef.current) onLockFailedRef.current(lockError, record);
|
|
3914
|
+
else onErrorRef.current?.(lockError);
|
|
3573
3915
|
return;
|
|
3574
3916
|
}
|
|
3575
3917
|
queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
|
|
@@ -3579,7 +3921,7 @@ function useOnRamp(options) {
|
|
|
3579
3921
|
emitDebug("lock:submitted", {
|
|
3580
3922
|
submissionIdPresent: Boolean(settlement.response.submission_id)
|
|
3581
3923
|
});
|
|
3582
|
-
onLockSubmittedRef.current?.(settlement.response);
|
|
3924
|
+
onLockSubmittedRef.current?.(settlement.response, record);
|
|
3583
3925
|
} catch (err) {
|
|
3584
3926
|
const error2 = err instanceof PostDepositLockError ? err : new PostDepositLockError(
|
|
3585
3927
|
err instanceof Error ? err.message : "Lock submission failed",
|
|
@@ -3592,7 +3934,8 @@ function useOnRamp(options) {
|
|
|
3592
3934
|
reason: error2.reason,
|
|
3593
3935
|
message: error2.message
|
|
3594
3936
|
});
|
|
3595
|
-
(onLockFailedRef.current
|
|
3937
|
+
if (onLockFailedRef.current) onLockFailedRef.current(error2, record);
|
|
3938
|
+
else onErrorRef.current?.(error2);
|
|
3596
3939
|
}
|
|
3597
3940
|
},
|
|
3598
3941
|
[client, emitDebug, postDepositLock, queryClient]
|
|
@@ -3622,6 +3965,13 @@ function useOnRamp(options) {
|
|
|
3622
3965
|
setStatus("credited");
|
|
3623
3966
|
}
|
|
3624
3967
|
if (record) {
|
|
3968
|
+
creditedVerificationKeysRef.current.add(getOnRampVerificationKey(record));
|
|
3969
|
+
setPending((rows) => filterCreditedOnRampRecords(rows, creditedVerificationKeysRef.current));
|
|
3970
|
+
const finalizeRecovery = () => {
|
|
3971
|
+
if (recoveryScopeAtCredit && !finalizeCreditedOnRampIntent(recoveryScopeAtCredit, record)) {
|
|
3972
|
+
emitDebug("verification:credit-recovery-storage-unavailable");
|
|
3973
|
+
}
|
|
3974
|
+
};
|
|
3625
3975
|
setFinalityProgress((prev) => {
|
|
3626
3976
|
if (!(record.transaction_id in prev)) return prev;
|
|
3627
3977
|
const next = { ...prev };
|
|
@@ -3630,39 +3980,48 @@ function useOnRamp(options) {
|
|
|
3630
3980
|
});
|
|
3631
3981
|
const lockOwner = lockOwnerRef.current ?? privateReadAddress;
|
|
3632
3982
|
if (lockOwner) {
|
|
3633
|
-
void submitPendingLockAfterCredit(record
|
|
3983
|
+
void submitPendingLockAfterCredit(record, lockOwner, creditedAmount).finally(
|
|
3984
|
+
finalizeRecovery
|
|
3985
|
+
);
|
|
3634
3986
|
} else if (postDepositLock) {
|
|
3635
3987
|
emitDebug("lock:owner-unavailable");
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
"not-found"
|
|
3640
|
-
)
|
|
3988
|
+
const lockError = new PostDepositLockError(
|
|
3989
|
+
"No wallet address available to look up the signed lock for this on-ramp",
|
|
3990
|
+
"not-found"
|
|
3641
3991
|
);
|
|
3992
|
+
if (onLockFailedRef.current) onLockFailedRef.current(lockError, record);
|
|
3993
|
+
else onErrorRef.current?.(lockError);
|
|
3994
|
+
finalizeRecovery();
|
|
3995
|
+
} else {
|
|
3996
|
+
finalizeRecovery();
|
|
3642
3997
|
}
|
|
3643
3998
|
}
|
|
3644
3999
|
void (async () => {
|
|
3645
4000
|
try {
|
|
3646
|
-
if (record && depositTxHash.startsWith("0x")) {
|
|
3647
|
-
emitDebug("
|
|
4001
|
+
if (record && adapter.recordDeposit && record.provider === adapter.provider && depositTxHash.startsWith("0x")) {
|
|
4002
|
+
emitDebug("provider-deposit:record-request", {
|
|
4003
|
+
provider: adapter.provider,
|
|
3648
4004
|
depositTxHash
|
|
3649
4005
|
});
|
|
3650
4006
|
const updated = await executeOnRampPrivateRead(
|
|
3651
|
-
(readClient) =>
|
|
3652
|
-
|
|
4007
|
+
(readClient) => recordOnRampProviderDeposit(adapter, {
|
|
4008
|
+
client: readClient,
|
|
4009
|
+
record,
|
|
4010
|
+
depositTxHash
|
|
3653
4011
|
})
|
|
3654
4012
|
);
|
|
3655
|
-
emitDebug("
|
|
3656
|
-
|
|
4013
|
+
emitDebug("provider-deposit:record-success", {
|
|
4014
|
+
provider: adapter.provider,
|
|
4015
|
+
record: updated ? summariseOnRampRecord(updated) : null
|
|
3657
4016
|
});
|
|
3658
4017
|
}
|
|
3659
4018
|
} catch (err) {
|
|
3660
|
-
emitDebug("
|
|
3661
|
-
|
|
4019
|
+
emitDebug("provider-deposit:record-error", {
|
|
4020
|
+
provider: adapter.provider,
|
|
4021
|
+
...errorPayload(err)
|
|
4022
|
+
});
|
|
4023
|
+
console.warn("Failed to record provider deposit:", err);
|
|
3662
4024
|
} finally {
|
|
3663
|
-
if (record && recoveryScopeAtCredit) {
|
|
3664
|
-
forgetUnresolvedOnRampIntent(recoveryScopeAtCredit, getOnRampIntentId(record));
|
|
3665
|
-
}
|
|
3666
4025
|
await refreshPending();
|
|
3667
4026
|
clearActiveVerification(verificationKey);
|
|
3668
4027
|
if (record && activeIntentIdRef.current && matchesOnRampTransaction(record, activeIntentIdRef.current)) {
|
|
@@ -3674,7 +4033,7 @@ function useOnRamp(options) {
|
|
|
3674
4033
|
}
|
|
3675
4034
|
}
|
|
3676
4035
|
})();
|
|
3677
|
-
onCreditedRef.current?.(depositTxHash);
|
|
4036
|
+
if (record) onCreditedRef.current?.(depositTxHash, record);
|
|
3678
4037
|
},
|
|
3679
4038
|
onCheckTimeout: (depositTxHash) => {
|
|
3680
4039
|
const record = activeVerificationRecordRef.current;
|
|
@@ -3712,6 +4071,7 @@ function useOnRamp(options) {
|
|
|
3712
4071
|
resetDepositVerification();
|
|
3713
4072
|
clearActiveVerification();
|
|
3714
4073
|
triggeredVerificationKeysRef.current.clear();
|
|
4074
|
+
creditedVerificationKeysRef.current.clear();
|
|
3715
4075
|
setPending([]);
|
|
3716
4076
|
setFinalityProgress({});
|
|
3717
4077
|
statusRef.current = "idle";
|
|
@@ -3767,28 +4127,19 @@ function useOnRamp(options) {
|
|
|
3767
4127
|
quoteCurrencyAmountPresent: quoteCurrencyAmount !== void 0,
|
|
3768
4128
|
depositAddressReady: true
|
|
3769
4129
|
});
|
|
4130
|
+
const intentInput = {
|
|
4131
|
+
walletAddress: scopedDepositAddress,
|
|
4132
|
+
tokenId,
|
|
4133
|
+
chainId: token.chainId,
|
|
4134
|
+
providerAssetCode
|
|
4135
|
+
};
|
|
3770
4136
|
const record = await executeOnRampPrivateRead(
|
|
3771
|
-
(readClient) => readClient.createOnRampIntent(
|
|
3772
|
-
adapter.buildIntentRequest({
|
|
3773
|
-
walletAddress: scopedDepositAddress,
|
|
3774
|
-
tokenId,
|
|
3775
|
-
chainId: token.chainId,
|
|
3776
|
-
providerAssetCode
|
|
3777
|
-
})
|
|
3778
|
-
)
|
|
4137
|
+
(readClient) => readClient.createOnRampIntent(adapter.buildIntentRequest(intentInput))
|
|
3779
4138
|
);
|
|
3780
4139
|
if (flowSessionRef.current !== flowSession) {
|
|
3781
4140
|
throw new Error("On-ramp account or network changed while creating the intent");
|
|
3782
4141
|
}
|
|
3783
|
-
|
|
3784
|
-
if (!record.provider_asset_code) {
|
|
3785
|
-
throw new Error("On-ramp intent response is missing provider_asset_code");
|
|
3786
|
-
}
|
|
3787
|
-
if (record.provider_asset_code.toLowerCase() !== providerAssetCode.toLowerCase()) {
|
|
3788
|
-
throw new Error(
|
|
3789
|
-
`On-ramp intent asset ${record.provider_asset_code} does not match requested asset ${providerAssetCode}`
|
|
3790
|
-
);
|
|
3791
|
-
}
|
|
4142
|
+
assertCreatedOnRampIntent(record, adapter.provider, intentInput);
|
|
3792
4143
|
if (postDepositLock && lockOwner && lockAmount !== void 0) {
|
|
3793
4144
|
const signingWalletClient = await getWalletClient3(wagmiConfig, {
|
|
3794
4145
|
chainId: networkConfig.chainId
|
|
@@ -4029,7 +4380,7 @@ function useOnRamp(options) {
|
|
|
4029
4380
|
`Token ${recordTokenId} is on chain ${token.chainId} but record is on chain ${record.chain_id}`
|
|
4030
4381
|
);
|
|
4031
4382
|
}
|
|
4032
|
-
const
|
|
4383
|
+
const delivered = await resolveDeliveredTransfer({
|
|
4033
4384
|
onChainTxHash: record.on_chain_tx_hash,
|
|
4034
4385
|
chainId: record.chain_id,
|
|
4035
4386
|
walletAddress: record.wallet_address,
|
|
@@ -4042,12 +4393,14 @@ function useOnRamp(options) {
|
|
|
4042
4393
|
scopedMinDepositByChain,
|
|
4043
4394
|
record.chain_id
|
|
4044
4395
|
);
|
|
4045
|
-
if (recordMinDepositBaseUnits !== void 0 && amount < recordMinDepositBaseUnits) {
|
|
4396
|
+
if (recordMinDepositBaseUnits !== void 0 && delivered.amount < recordMinDepositBaseUnits) {
|
|
4046
4397
|
emitDebug("verification:below-minimum", {
|
|
4047
|
-
deliveredAmount: amount.toString(),
|
|
4398
|
+
deliveredAmount: delivered.amount.toString(),
|
|
4048
4399
|
minDepositBaseUnits: String(recordMinDepositBaseUnits)
|
|
4049
4400
|
});
|
|
4050
|
-
throw new Error(
|
|
4401
|
+
throw new Error(
|
|
4402
|
+
`Delivered amount (${delivered.amount} base units) is below the minimum deposit.`
|
|
4403
|
+
);
|
|
4051
4404
|
}
|
|
4052
4405
|
if (activeIntentIdRef.current && matchesOnRampTransaction(record, activeIntentIdRef.current)) {
|
|
4053
4406
|
setStatus("verifying");
|
|
@@ -4055,12 +4408,14 @@ function useOnRamp(options) {
|
|
|
4055
4408
|
emitDebug("verification:check-deposit-request", {
|
|
4056
4409
|
hash: record.on_chain_tx_hash,
|
|
4057
4410
|
chainId: record.chain_id,
|
|
4058
|
-
amount: amount.toString()
|
|
4411
|
+
amount: delivered.amount.toString(),
|
|
4412
|
+
logIndex: delivered.logIndex
|
|
4059
4413
|
});
|
|
4060
4414
|
await verify({
|
|
4061
4415
|
hash: record.on_chain_tx_hash,
|
|
4062
4416
|
chainId: record.chain_id,
|
|
4063
|
-
amount
|
|
4417
|
+
amount: delivered.amount,
|
|
4418
|
+
logIndex: delivered.logIndex
|
|
4064
4419
|
});
|
|
4065
4420
|
} catch (err) {
|
|
4066
4421
|
if (flowSessionRef.current !== flowSession) return;
|
|
@@ -4318,7 +4673,7 @@ function summariseOnRampRecord(record) {
|
|
|
4318
4673
|
credited_at: record.credited_at ?? null
|
|
4319
4674
|
};
|
|
4320
4675
|
}
|
|
4321
|
-
async function
|
|
4676
|
+
async function resolveDeliveredTransfer({
|
|
4322
4677
|
onChainTxHash,
|
|
4323
4678
|
chainId,
|
|
4324
4679
|
walletAddress,
|
|
@@ -4327,7 +4682,6 @@ async function resolveDeliveredAmount({
|
|
|
4327
4682
|
emitDebug
|
|
4328
4683
|
}) {
|
|
4329
4684
|
assertErc20OnRampToken(token.contract);
|
|
4330
|
-
let receiptError;
|
|
4331
4685
|
try {
|
|
4332
4686
|
const receipt = await waitForTransactionReceipt(wagmiConfig, {
|
|
4333
4687
|
hash: onChainTxHash,
|
|
@@ -4335,27 +4689,21 @@ async function resolveDeliveredAmount({
|
|
|
4335
4689
|
timeout: 6e4,
|
|
4336
4690
|
pollingInterval: 4e3
|
|
4337
4691
|
});
|
|
4338
|
-
const delivered =
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
tokenAddress: token.contract,
|
|
4343
|
-
depositAddressMatched: true
|
|
4344
|
-
});
|
|
4345
|
-
return delivered;
|
|
4346
|
-
}
|
|
4347
|
-
emitDebug("verification:amount-from-receipt-missing", {
|
|
4692
|
+
const delivered = resolveErc20OnRampTransfer(receipt.logs, token.contract, walletAddress);
|
|
4693
|
+
emitDebug("verification:amount-from-receipt", {
|
|
4694
|
+
amount: delivered.amount.toString(),
|
|
4695
|
+
logIndex: delivered.logIndex,
|
|
4348
4696
|
tokenAddress: token.contract,
|
|
4349
|
-
depositAddressMatched:
|
|
4697
|
+
depositAddressMatched: true
|
|
4350
4698
|
});
|
|
4699
|
+
return delivered;
|
|
4351
4700
|
} catch (err) {
|
|
4352
4701
|
emitDebug("verification:amount-from-receipt-error", errorPayload(err));
|
|
4353
|
-
|
|
4702
|
+
const errorDetail = err instanceof Error ? err.message : String(err);
|
|
4703
|
+
throw new Error(
|
|
4704
|
+
`Unable to derive delivered ${token.symbol} transfer from its receipt: ${errorDetail}`
|
|
4705
|
+
);
|
|
4354
4706
|
}
|
|
4355
|
-
const errorDetail = receiptError instanceof Error ? receiptError.message : receiptError === void 0 ? `no ${token.symbol} Transfer to the derived deposit address found` : String(receiptError);
|
|
4356
|
-
throw new Error(
|
|
4357
|
-
`Unable to derive delivered ${token.symbol} amount from its receipt: ${errorDetail}`
|
|
4358
|
-
);
|
|
4359
4707
|
}
|
|
4360
4708
|
function errorPayload(err) {
|
|
4361
4709
|
if (err instanceof Error) {
|
|
@@ -4635,7 +4983,12 @@ function FiatOnRampForm({
|
|
|
4635
4983
|
onLockSubmitted,
|
|
4636
4984
|
onLockFailed,
|
|
4637
4985
|
onError,
|
|
4638
|
-
|
|
4986
|
+
onLeaveFlow,
|
|
4987
|
+
onDebugEvent,
|
|
4988
|
+
onlyRouteActiveIntentCallbacks = false,
|
|
4989
|
+
frozenToken,
|
|
4990
|
+
onUnsafeToCloseChange,
|
|
4991
|
+
onActiveFlowChange
|
|
4639
4992
|
}) {
|
|
4640
4993
|
const { address } = useAccount();
|
|
4641
4994
|
const [visible, setVisible] = useState(false);
|
|
@@ -4643,6 +4996,14 @@ function FiatOnRampForm({
|
|
|
4643
4996
|
const [rowError, setRowError] = useState(null);
|
|
4644
4997
|
const [lockError, setLockError] = useState(null);
|
|
4645
4998
|
const [lockSettled, setLockSettled] = useState(false);
|
|
4999
|
+
const [ownedTerminal, setOwnedTerminal] = useState(false);
|
|
5000
|
+
const ownedIntentIdRef = useRef(null);
|
|
5001
|
+
const ownsRecord = useCallback(
|
|
5002
|
+
(record) => Boolean(
|
|
5003
|
+
ownedIntentIdRef.current && matchesOnRampTransaction(record, ownedIntentIdRef.current)
|
|
5004
|
+
),
|
|
5005
|
+
[]
|
|
5006
|
+
);
|
|
4646
5007
|
const {
|
|
4647
5008
|
status,
|
|
4648
5009
|
activeIntentId,
|
|
@@ -4663,18 +5024,37 @@ function FiatOnRampForm({
|
|
|
4663
5024
|
} = useFiatOnRamp({
|
|
4664
5025
|
tokenId,
|
|
4665
5026
|
postDepositLock,
|
|
4666
|
-
onCredited,
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
5027
|
+
onCredited: (depositTxHash, record) => {
|
|
5028
|
+
if (!onlyRouteActiveIntentCallbacks || ownsRecord(record)) {
|
|
5029
|
+
onCredited?.(depositTxHash);
|
|
5030
|
+
}
|
|
5031
|
+
if (ownsRecord(record) && !postDepositLock) {
|
|
5032
|
+
setOwnedTerminal(true);
|
|
5033
|
+
onActiveFlowChange?.(false);
|
|
5034
|
+
}
|
|
5035
|
+
},
|
|
5036
|
+
// Product modals route only the snapshotted intent. Standalone consumers
|
|
5037
|
+
// retain the legacy behavior of receiving recovered-row callbacks.
|
|
5038
|
+
onLockSubmitted: (response, record) => {
|
|
5039
|
+
const routesToCurrentFlow = !onlyRouteActiveIntentCallbacks || ownsRecord(record);
|
|
5040
|
+
if (!routesToCurrentFlow) return;
|
|
4671
5041
|
setLockError(null);
|
|
4672
5042
|
setLockSettled(true);
|
|
4673
5043
|
onLockSubmitted?.(response);
|
|
5044
|
+
if (ownsRecord(record)) {
|
|
5045
|
+
setOwnedTerminal(true);
|
|
5046
|
+
onActiveFlowChange?.(false);
|
|
5047
|
+
}
|
|
4674
5048
|
},
|
|
4675
|
-
onLockFailed: (err) => {
|
|
5049
|
+
onLockFailed: (err, record) => {
|
|
5050
|
+
const routesToCurrentFlow = !onlyRouteActiveIntentCallbacks || ownsRecord(record);
|
|
5051
|
+
if (!routesToCurrentFlow) return;
|
|
4676
5052
|
setLockError(err.message);
|
|
4677
5053
|
onLockFailed?.(err);
|
|
5054
|
+
if (ownsRecord(record)) {
|
|
5055
|
+
setOwnedTerminal(true);
|
|
5056
|
+
onActiveFlowChange?.(false);
|
|
5057
|
+
}
|
|
4678
5058
|
},
|
|
4679
5059
|
onError,
|
|
4680
5060
|
onDebugEvent
|
|
@@ -4703,6 +5083,8 @@ function FiatOnRampForm({
|
|
|
4703
5083
|
}
|
|
4704
5084
|
})();
|
|
4705
5085
|
const isBelowMin = quoteBaseUnits !== void 0 && minDepositBaseUnits !== void 0 ? quoteBaseUnits < minDepositBaseUnits : minFiatGate !== void 0 && Number(defaultBaseCurrencyAmount) < minFiatGate;
|
|
5086
|
+
const minimumUnknown = minDepositBaseUnits === void 0;
|
|
5087
|
+
const tokenMismatch = frozenToken !== void 0 && !matchesFrozenOnRampToken(frozenToken, selectedToken);
|
|
4706
5088
|
const isBusy = isPreparing || status === "awaiting-purchase";
|
|
4707
5089
|
const lockPending = !!postDepositLock && status === "credited" && !lockSettled && !lockError;
|
|
4708
5090
|
const isInitializing = !!address && !depositAddress;
|
|
@@ -4714,10 +5096,23 @@ function FiatOnRampForm({
|
|
|
4714
5096
|
!depositAddress ? "deposit-address-not-loaded" : null,
|
|
4715
5097
|
isBusy ? `busy:${isPreparing ? "preparing" : status}` : null,
|
|
4716
5098
|
visible ? "widget-open" : null,
|
|
5099
|
+
minimumUnknown ? "minimum-unknown" : null,
|
|
4717
5100
|
isBelowMin ? "below-minimum" : null,
|
|
5101
|
+
tokenMismatch ? "token-drift" : null,
|
|
4718
5102
|
quoteParseFailed ? "invalid-quote-amount" : null
|
|
4719
5103
|
].filter((reason) => Boolean(reason)),
|
|
4720
|
-
[
|
|
5104
|
+
[
|
|
5105
|
+
address,
|
|
5106
|
+
depositAddress,
|
|
5107
|
+
isBelowMin,
|
|
5108
|
+
isBusy,
|
|
5109
|
+
isPreparing,
|
|
5110
|
+
minimumUnknown,
|
|
5111
|
+
quoteParseFailed,
|
|
5112
|
+
status,
|
|
5113
|
+
tokenMismatch,
|
|
5114
|
+
visible
|
|
5115
|
+
]
|
|
4721
5116
|
);
|
|
4722
5117
|
const canBuy = blockReasons.length === 0;
|
|
4723
5118
|
const handleOpen = useCallback(async () => {
|
|
@@ -4736,6 +5131,8 @@ function FiatOnRampForm({
|
|
|
4736
5131
|
return;
|
|
4737
5132
|
}
|
|
4738
5133
|
setIsPreparing(true);
|
|
5134
|
+
setOwnedTerminal(false);
|
|
5135
|
+
ownedIntentIdRef.current = null;
|
|
4739
5136
|
setLockError(null);
|
|
4740
5137
|
setLockSettled(false);
|
|
4741
5138
|
emitFormDebug("form:open-click", {
|
|
@@ -4754,6 +5151,8 @@ function FiatOnRampForm({
|
|
|
4754
5151
|
baseCurrencyAmount: defaultBaseCurrencyAmount,
|
|
4755
5152
|
quoteCurrencyAmount
|
|
4756
5153
|
});
|
|
5154
|
+
ownedIntentIdRef.current = intent.transaction_id;
|
|
5155
|
+
onActiveFlowChange?.(true);
|
|
4757
5156
|
emitFormDebug("form:intent-ready", {
|
|
4758
5157
|
transactionIdPresent: Boolean(intent.transaction_id),
|
|
4759
5158
|
externalTransactionIdPresent: Boolean(intent.external_transaction_id)
|
|
@@ -4781,7 +5180,8 @@ function FiatOnRampForm({
|
|
|
4781
5180
|
depositAddress,
|
|
4782
5181
|
emitFormDebug,
|
|
4783
5182
|
prepareOnRampIntent,
|
|
4784
|
-
status
|
|
5183
|
+
status,
|
|
5184
|
+
onActiveFlowChange
|
|
4785
5185
|
]);
|
|
4786
5186
|
const handleClose = useCallback(async () => {
|
|
4787
5187
|
emitFormDebug("moonpay:onClose");
|
|
@@ -4796,6 +5196,11 @@ function FiatOnRampForm({
|
|
|
4796
5196
|
const handleReady = useCallback(async () => {
|
|
4797
5197
|
emitFormDebug("moonpay:onReady");
|
|
4798
5198
|
}, [emitFormDebug]);
|
|
5199
|
+
const handleLeaveFlow = useCallback(() => {
|
|
5200
|
+
setVisible(false);
|
|
5201
|
+
onActiveFlowChange?.(false);
|
|
5202
|
+
onLeaveFlow?.();
|
|
5203
|
+
}, [onActiveFlowChange, onLeaveFlow]);
|
|
4799
5204
|
const widgetElement = useMoonPayOnRampAdapter({
|
|
4800
5205
|
variant,
|
|
4801
5206
|
visible,
|
|
@@ -4823,6 +5228,21 @@ function FiatOnRampForm({
|
|
|
4823
5228
|
onTransactionCreated: handleTransactionCreated,
|
|
4824
5229
|
onTransactionCompleted: handleTransactionCompleted
|
|
4825
5230
|
});
|
|
5231
|
+
const ownsActiveIntent = Boolean(
|
|
5232
|
+
activeIntentId && ownedIntentIdRef.current && activeIntentId === ownedIntentIdRef.current
|
|
5233
|
+
);
|
|
5234
|
+
const ownsPending = Boolean(
|
|
5235
|
+
ownedIntentIdRef.current && pending.some((record) => matchesOnRampTransaction(record, ownedIntentIdRef.current))
|
|
5236
|
+
);
|
|
5237
|
+
const ownedFlowActive = !ownedTerminal && (ownsActiveIntent || ownsPending || lockPending);
|
|
5238
|
+
useEffect(() => {
|
|
5239
|
+
onActiveFlowChange?.(ownedFlowActive);
|
|
5240
|
+
}, [onActiveFlowChange, ownedFlowActive]);
|
|
5241
|
+
const unsafeToClose = isPreparing || lockPending;
|
|
5242
|
+
useEffect(() => {
|
|
5243
|
+
onUnsafeToCloseChange?.(unsafeToClose);
|
|
5244
|
+
return () => onUnsafeToCloseChange?.(false);
|
|
5245
|
+
}, [onUnsafeToCloseChange, unsafeToClose]);
|
|
4826
5246
|
useEffect(() => {
|
|
4827
5247
|
if (visible && !activeIntentId && status === "idle") setVisible(false);
|
|
4828
5248
|
}, [activeIntentId, status, visible]);
|
|
@@ -4838,10 +5258,8 @@ function FiatOnRampForm({
|
|
|
4838
5258
|
/* @__PURE__ */ jsx("p", { className: "text-foreground text-sm font-medium", children: "Validating purchases" }),
|
|
4839
5259
|
pending.map((record) => {
|
|
4840
5260
|
const progress = parseFinalityProgress(finalityProgress[record.transaction_id]);
|
|
4841
|
-
const hasProgress = !!finalityProgress[record.transaction_id];
|
|
4842
|
-
const isStalled = !hasProgress && Date.now() / 1e3 - (record.updated_at ?? 0) > 60;
|
|
4843
5261
|
const isActivelyVerifying = record.transaction_id === activeVerificationId;
|
|
4844
|
-
const showRetry =
|
|
5262
|
+
const showRetry = canRetryOnRampVerification(record, activeVerificationId);
|
|
4845
5263
|
return /* @__PURE__ */ jsxs(
|
|
4846
5264
|
"div",
|
|
4847
5265
|
{
|
|
@@ -4850,7 +5268,7 @@ function FiatOnRampForm({
|
|
|
4850
5268
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
|
|
4851
5269
|
/* @__PURE__ */ jsxs("p", { className: "text-muted-foreground flex items-center gap-1 text-xs", children: [
|
|
4852
5270
|
/* @__PURE__ */ jsx(Loader2, { className: "size-3 animate-spin", "aria-hidden": true }),
|
|
4853
|
-
progress ?? "Verifying\u2026"
|
|
5271
|
+
record.on_chain_tx_hash ? progress ?? (isActivelyVerifying ? "Verifying\u2026" : "Ready to verify") : "Waiting for provider delivery\u2026"
|
|
4854
5272
|
] }),
|
|
4855
5273
|
/* @__PURE__ */ jsxs("p", { className: "text-muted-foreground text-xs", children: [
|
|
4856
5274
|
record.quote_currency_amount ?? "?",
|
|
@@ -4876,7 +5294,17 @@ function FiatOnRampForm({
|
|
|
4876
5294
|
});
|
|
4877
5295
|
}
|
|
4878
5296
|
},
|
|
4879
|
-
children: "Retry"
|
|
5297
|
+
children: "Retry verification"
|
|
5298
|
+
}
|
|
5299
|
+
),
|
|
5300
|
+
!record.on_chain_tx_hash && /* @__PURE__ */ jsx(
|
|
5301
|
+
Button,
|
|
5302
|
+
{
|
|
5303
|
+
type: "button",
|
|
5304
|
+
variant: "outline",
|
|
5305
|
+
size: "sm",
|
|
5306
|
+
onClick: () => void refreshPending(),
|
|
5307
|
+
children: "Refresh delivery"
|
|
4880
5308
|
}
|
|
4881
5309
|
)
|
|
4882
5310
|
]
|
|
@@ -4903,6 +5331,10 @@ function FiatOnRampForm({
|
|
|
4903
5331
|
"Buy"
|
|
4904
5332
|
] }),
|
|
4905
5333
|
widgetElement,
|
|
5334
|
+
onLeaveFlow && ownedFlowActive && !unsafeToClose && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
5335
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: handleLeaveFlow, children: "Leave checkout" }),
|
|
5336
|
+
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: "Privana will keep this signed purchase available for exact recovery." })
|
|
5337
|
+
] }),
|
|
4906
5338
|
error && /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm", role: "alert", children: error.message }),
|
|
4907
5339
|
lockError && /* @__PURE__ */ jsxs("p", { className: "text-destructive text-sm", role: "alert", children: [
|
|
4908
5340
|
"Purchase credited to your account, but locking the funds to a service failed: ",
|
|
@@ -4913,6 +5345,8 @@ function FiatOnRampForm({
|
|
|
4913
5345
|
minFiatGate.toFixed(2),
|
|
4914
5346
|
"."
|
|
4915
5347
|
] }),
|
|
5348
|
+
tokenMismatch && /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm", role: "alert", children: "The token configuration changed. Close this purchase and start again." }),
|
|
5349
|
+
!tokenMismatch && depositAddress && minimumUnknown && !error && /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm", role: "alert", children: "The minimum purchase amount is unavailable. Close this purchase and try again." }),
|
|
4916
5350
|
isVerifying && pending.length === 0 && /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground flex items-center gap-2 text-sm", children: [
|
|
4917
5351
|
/* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin", "aria-hidden": true }),
|
|
4918
5352
|
"Verifying your purchase\u2026"
|
|
@@ -4925,6 +5359,702 @@ function parseFinalityProgress(message) {
|
|
|
4925
5359
|
return match ? `${match[1]} confirmations` : null;
|
|
4926
5360
|
}
|
|
4927
5361
|
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
5362
|
+
// src/sdk/on-ramp/transak-adapter.ts
|
|
5363
|
+
var MAX_INTENT_ID_LENGTH = 512;
|
|
5364
|
+
var MAX_WIDGET_URL_LENGTH = 8192;
|
|
5365
|
+
var TRANSAK_IP_ATTESTATION_PATH = "/__onramp-ip-attest";
|
|
5366
|
+
var TRANSAK_IP_ATTESTATION_TIMEOUT_MS = 1e4;
|
|
5367
|
+
var TRANSAK_WIDGET_ORIGINS = /* @__PURE__ */ new Set([
|
|
5368
|
+
"https://global-stg.transak.com",
|
|
5369
|
+
"https://global.transak.com"
|
|
5370
|
+
]);
|
|
5371
|
+
var transakOnRampAdapter = {
|
|
5372
|
+
provider: "transak",
|
|
5373
|
+
pollPendingWhileOpen: true,
|
|
5374
|
+
buildIntentRequest: ({ walletAddress, tokenId, chainId }) => ({
|
|
5375
|
+
wallet_address: walletAddress,
|
|
5376
|
+
token_id: tokenId,
|
|
5377
|
+
chain_id: chainId
|
|
5378
|
+
})
|
|
5379
|
+
};
|
|
5380
|
+
async function requestTransakWidgetSession({
|
|
5381
|
+
client,
|
|
5382
|
+
intentId,
|
|
5383
|
+
generation,
|
|
5384
|
+
now = Date.now,
|
|
5385
|
+
fetcher = globalThis.fetch
|
|
5386
|
+
}) {
|
|
5387
|
+
if (!intentId || intentId.length > MAX_INTENT_ID_LENGTH) {
|
|
5388
|
+
throw new Error("Transak session requires a valid on-ramp intent");
|
|
5389
|
+
}
|
|
5390
|
+
const ipAttestation = await fetchTransakIpAttestation({
|
|
5391
|
+
intentId,
|
|
5392
|
+
fetcher
|
|
5393
|
+
});
|
|
5394
|
+
const response = await client.createOnRampSession({
|
|
5395
|
+
transaction_id: intentId,
|
|
5396
|
+
ip_attestation: ipAttestation
|
|
5397
|
+
});
|
|
5398
|
+
return validateTransakWidgetSession(response, intentId, generation, now());
|
|
5399
|
+
}
|
|
5400
|
+
async function fetchTransakIpAttestation({
|
|
5401
|
+
intentId,
|
|
5402
|
+
fetcher
|
|
5403
|
+
}) {
|
|
5404
|
+
const subtleCrypto = globalThis.crypto?.subtle;
|
|
5405
|
+
const origin = globalThis.location?.origin;
|
|
5406
|
+
if (typeof fetcher !== "function" || !subtleCrypto || !origin || origin === "null") {
|
|
5407
|
+
throw new Error("Secure client-IP attestation is unavailable");
|
|
5408
|
+
}
|
|
5409
|
+
const digest = await subtleCrypto.digest("SHA-256", new TextEncoder().encode(intentId));
|
|
5410
|
+
const intentHash = Array.from(
|
|
5411
|
+
new Uint8Array(digest),
|
|
5412
|
+
(byte) => byte.toString(16).padStart(2, "0")
|
|
5413
|
+
).join("");
|
|
5414
|
+
const controller = new AbortController();
|
|
5415
|
+
const timeoutId = setTimeout(() => controller.abort(), TRANSAK_IP_ATTESTATION_TIMEOUT_MS);
|
|
5416
|
+
try {
|
|
5417
|
+
let response;
|
|
5418
|
+
try {
|
|
5419
|
+
response = await fetcher(`${origin}${TRANSAK_IP_ATTESTATION_PATH}`, {
|
|
5420
|
+
method: "POST",
|
|
5421
|
+
headers: { "Content-Type": "application/json" },
|
|
5422
|
+
body: JSON.stringify({ intentHash }),
|
|
5423
|
+
cache: "no-store",
|
|
5424
|
+
credentials: "omit",
|
|
5425
|
+
redirect: "error",
|
|
5426
|
+
signal: controller.signal
|
|
5427
|
+
});
|
|
5428
|
+
} catch (error) {
|
|
5429
|
+
throw new Error("Client-IP attestation request failed", { cause: error });
|
|
5430
|
+
}
|
|
5431
|
+
if (!response.ok) {
|
|
5432
|
+
throw new Error(`Client-IP attestation request failed with HTTP ${response.status}`);
|
|
5433
|
+
}
|
|
5434
|
+
try {
|
|
5435
|
+
return await response.json();
|
|
5436
|
+
} catch (error) {
|
|
5437
|
+
throw new Error("Client-IP attestation response is malformed", { cause: error });
|
|
5438
|
+
}
|
|
5439
|
+
} finally {
|
|
5440
|
+
clearTimeout(timeoutId);
|
|
5441
|
+
}
|
|
5442
|
+
}
|
|
5443
|
+
function validateTransakWidgetSession(response, intentId, generation, now = Date.now()) {
|
|
5444
|
+
if (!isPlainRecord2(response) || response.provider !== "transak") {
|
|
5445
|
+
throw new Error("On-ramp session provider does not match Transak");
|
|
5446
|
+
}
|
|
5447
|
+
if (!Number.isSafeInteger(response.expires_at) || response.expires_at * 1e3 <= now) {
|
|
5448
|
+
throw new Error("Transak session is expired or malformed");
|
|
5449
|
+
}
|
|
5450
|
+
const origin = validateTransakWidgetUrl(response.url);
|
|
5451
|
+
return {
|
|
5452
|
+
provider: "transak",
|
|
5453
|
+
url: response.url,
|
|
5454
|
+
origin,
|
|
5455
|
+
expiresAt: response.expires_at,
|
|
5456
|
+
intentId,
|
|
5457
|
+
generation
|
|
5458
|
+
};
|
|
5459
|
+
}
|
|
5460
|
+
function isTransakWidgetSessionLoadable(session, now = Date.now()) {
|
|
5461
|
+
return session.expiresAt * 1e3 > now;
|
|
5462
|
+
}
|
|
5463
|
+
function resolveTransakWidgetMessage({
|
|
5464
|
+
message,
|
|
5465
|
+
iframeWindow,
|
|
5466
|
+
session,
|
|
5467
|
+
currentGeneration
|
|
5468
|
+
}) {
|
|
5469
|
+
if (session.generation !== currentGeneration) return null;
|
|
5470
|
+
if (!iframeWindow || message.source !== iframeWindow) return null;
|
|
5471
|
+
if (message.origin !== session.origin) return null;
|
|
5472
|
+
return normalizeTransakWidgetMessage(message.data, session.intentId);
|
|
5473
|
+
}
|
|
5474
|
+
function normalizeTransakWidgetMessage(data, intentId) {
|
|
5475
|
+
if (!isPlainRecord2(data) || typeof data.event_id !== "string") return null;
|
|
5476
|
+
switch (data.event_id) {
|
|
5477
|
+
case "TRANSAK_WIDGET_INITIALISED":
|
|
5478
|
+
case "TRANSAK_WIDGET_OPEN":
|
|
5479
|
+
return { type: "ready" };
|
|
5480
|
+
case "TRANSAK_ORDER_CREATED":
|
|
5481
|
+
return {
|
|
5482
|
+
type: "provider-event",
|
|
5483
|
+
event: {
|
|
5484
|
+
provider: "transak",
|
|
5485
|
+
kind: "transaction-created",
|
|
5486
|
+
providerTransactionId: intentId,
|
|
5487
|
+
intentId
|
|
5488
|
+
}
|
|
5489
|
+
};
|
|
5490
|
+
case "TRANSAK_ORDER_SUCCESSFUL":
|
|
5491
|
+
return {
|
|
5492
|
+
type: "provider-event",
|
|
5493
|
+
event: {
|
|
5494
|
+
provider: "transak",
|
|
5495
|
+
kind: "transaction-completed",
|
|
5496
|
+
providerTransactionId: intentId,
|
|
5497
|
+
intentId
|
|
5498
|
+
}
|
|
5499
|
+
};
|
|
5500
|
+
case "TRANSAK_ORDER_CANCELLED":
|
|
5501
|
+
case "TRANSAK_ORDER_FAILED":
|
|
5502
|
+
return { type: "refresh" };
|
|
5503
|
+
case "TRANSAK_WIDGET_CLOSE":
|
|
5504
|
+
return { type: "close" };
|
|
5505
|
+
default:
|
|
5506
|
+
return null;
|
|
5507
|
+
}
|
|
5508
|
+
}
|
|
5509
|
+
function validateTransakWidgetUrl(url) {
|
|
5510
|
+
if (typeof url !== "string" || url.length === 0 || url.length > MAX_WIDGET_URL_LENGTH || !/^[\x21-\x7e]+$/.test(url) || url.includes("\\")) {
|
|
5511
|
+
throw new Error("Transak widget URL is malformed");
|
|
5512
|
+
}
|
|
5513
|
+
let parsed;
|
|
5514
|
+
try {
|
|
5515
|
+
parsed = new URL(url);
|
|
5516
|
+
} catch {
|
|
5517
|
+
throw new Error("Transak widget URL is malformed");
|
|
5518
|
+
}
|
|
5519
|
+
if (parsed.protocol !== "https:" || parsed.username || parsed.password) {
|
|
5520
|
+
throw new Error("Transak widget URL is malformed");
|
|
5521
|
+
}
|
|
5522
|
+
if (!TRANSAK_WIDGET_ORIGINS.has(parsed.origin)) {
|
|
5523
|
+
throw new Error("Transak widget URL origin is not allowed");
|
|
5524
|
+
}
|
|
5525
|
+
return parsed.origin;
|
|
5526
|
+
}
|
|
5527
|
+
function isPlainRecord2(value) {
|
|
5528
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
5529
|
+
const prototype = Object.getPrototypeOf(value);
|
|
5530
|
+
return prototype === Object.prototype || prototype === null;
|
|
5531
|
+
}
|
|
5532
|
+
function isTransakWidgetFrameRenderable(session, loadedGeneration, now = Date.now()) {
|
|
5533
|
+
return loadedGeneration === session.generation || isTransakWidgetSessionLoadable(session, now);
|
|
5534
|
+
}
|
|
5535
|
+
function TransakWidgetFrame({
|
|
5536
|
+
session,
|
|
5537
|
+
getCurrentGeneration,
|
|
5538
|
+
shouldPollPending,
|
|
5539
|
+
refreshPending,
|
|
5540
|
+
onReady,
|
|
5541
|
+
onAction,
|
|
5542
|
+
onExpired,
|
|
5543
|
+
title = "Transak secure checkout",
|
|
5544
|
+
className
|
|
5545
|
+
}) {
|
|
5546
|
+
const iframeRef = useRef(null);
|
|
5547
|
+
const loadedGenerationRef = useRef(null);
|
|
5548
|
+
const renderable = isTransakWidgetFrameRenderable(session, loadedGenerationRef.current);
|
|
5549
|
+
const readyRef = useRef(false);
|
|
5550
|
+
const expiryTimerRef = useRef(null);
|
|
5551
|
+
const callbacksRef = useRef({
|
|
5552
|
+
getCurrentGeneration,
|
|
5553
|
+
refreshPending,
|
|
5554
|
+
onReady,
|
|
5555
|
+
onAction,
|
|
5556
|
+
onExpired
|
|
5557
|
+
});
|
|
5558
|
+
callbacksRef.current = {
|
|
5559
|
+
getCurrentGeneration,
|
|
5560
|
+
refreshPending,
|
|
5561
|
+
onReady,
|
|
5562
|
+
onAction,
|
|
5563
|
+
onExpired
|
|
5564
|
+
};
|
|
5565
|
+
const expireUnloadedSession = useCallback(() => {
|
|
5566
|
+
if (loadedGenerationRef.current === session.generation) return;
|
|
5567
|
+
callbacksRef.current.onExpired(session.generation);
|
|
5568
|
+
}, [session.generation]);
|
|
5569
|
+
useEffect(() => {
|
|
5570
|
+
loadedGenerationRef.current = null;
|
|
5571
|
+
readyRef.current = false;
|
|
5572
|
+
const remaining = session.expiresAt * 1e3 - Date.now();
|
|
5573
|
+
if (remaining <= 0) {
|
|
5574
|
+
expireUnloadedSession();
|
|
5575
|
+
return;
|
|
5576
|
+
}
|
|
5577
|
+
expiryTimerRef.current = setTimeout(expireUnloadedSession, remaining);
|
|
5578
|
+
return () => {
|
|
5579
|
+
if (expiryTimerRef.current) clearTimeout(expiryTimerRef.current);
|
|
5580
|
+
expiryTimerRef.current = null;
|
|
5581
|
+
};
|
|
5582
|
+
}, [expireUnloadedSession, session.expiresAt]);
|
|
5583
|
+
useEffect(() => {
|
|
5584
|
+
const handleMessage = (message) => {
|
|
5585
|
+
const action = resolveTransakWidgetMessage({
|
|
5586
|
+
message,
|
|
5587
|
+
iframeWindow: iframeRef.current?.contentWindow ?? null,
|
|
5588
|
+
session,
|
|
5589
|
+
currentGeneration: callbacksRef.current.getCurrentGeneration()
|
|
5590
|
+
});
|
|
5591
|
+
if (!action) return;
|
|
5592
|
+
loadedGenerationRef.current = session.generation;
|
|
5593
|
+
if (expiryTimerRef.current) clearTimeout(expiryTimerRef.current);
|
|
5594
|
+
expiryTimerRef.current = null;
|
|
5595
|
+
void callbacksRef.current.onAction(session.generation, action);
|
|
5596
|
+
};
|
|
5597
|
+
window.addEventListener("message", handleMessage);
|
|
5598
|
+
return () => window.removeEventListener("message", handleMessage);
|
|
5599
|
+
}, [session]);
|
|
5600
|
+
useEffect(() => {
|
|
5601
|
+
if (!renderable || !transakOnRampAdapter.pollPendingWhileOpen || !shouldPollPending) return;
|
|
5602
|
+
const id = setInterval(
|
|
5603
|
+
() => void callbacksRef.current.refreshPending(),
|
|
5604
|
+
MIN_ONRAMP_PENDING_REQUEST_INTERVAL_MS
|
|
5605
|
+
);
|
|
5606
|
+
return () => clearInterval(id);
|
|
5607
|
+
}, [renderable, shouldPollPending]);
|
|
5608
|
+
const handleLoad = useCallback(() => {
|
|
5609
|
+
if (!isTransakWidgetSessionLoadable(session)) {
|
|
5610
|
+
expireUnloadedSession();
|
|
5611
|
+
return;
|
|
5612
|
+
}
|
|
5613
|
+
loadedGenerationRef.current = session.generation;
|
|
5614
|
+
if (expiryTimerRef.current) clearTimeout(expiryTimerRef.current);
|
|
5615
|
+
expiryTimerRef.current = null;
|
|
5616
|
+
if (readyRef.current) return;
|
|
5617
|
+
readyRef.current = true;
|
|
5618
|
+
callbacksRef.current.onReady(session.generation);
|
|
5619
|
+
}, [expireUnloadedSession, session]);
|
|
5620
|
+
if (!renderable) return null;
|
|
5621
|
+
return /* @__PURE__ */ jsx(
|
|
5622
|
+
"iframe",
|
|
5623
|
+
{
|
|
5624
|
+
ref: iframeRef,
|
|
5625
|
+
src: session.url,
|
|
5626
|
+
title,
|
|
5627
|
+
className,
|
|
5628
|
+
allow: "camera; microphone; payment",
|
|
5629
|
+
referrerPolicy: "strict-origin-when-cross-origin",
|
|
5630
|
+
onLoad: handleLoad,
|
|
5631
|
+
"data-privana-transak-widget": true
|
|
5632
|
+
}
|
|
5633
|
+
);
|
|
5634
|
+
}
|
|
5635
|
+
function transitionTransakSessionRecreation(state, event) {
|
|
5636
|
+
if (event === "reset") return "none";
|
|
5637
|
+
if (event === "intent-created") return "preload-only";
|
|
5638
|
+
if (state === "none") return "none";
|
|
5639
|
+
return "blocked";
|
|
5640
|
+
}
|
|
5641
|
+
function getMountedTransakSessionError(action, hasMountedSession) {
|
|
5642
|
+
if (!hasMountedSession) return null;
|
|
5643
|
+
return action === "launch" ? new Error("Close the current Transak checkout before launching another") : new Error("Close or expire the current Transak checkout before reopening");
|
|
5644
|
+
}
|
|
5645
|
+
function getTransakRecreateSessionError({
|
|
5646
|
+
hasMountedSession,
|
|
5647
|
+
activeIntentId,
|
|
5648
|
+
canRecreateSession
|
|
5649
|
+
}) {
|
|
5650
|
+
const mountedError = getMountedTransakSessionError("reopen", hasMountedSession);
|
|
5651
|
+
if (mountedError) return mountedError;
|
|
5652
|
+
if (!activeIntentId) {
|
|
5653
|
+
return new Error("No unresolved Transak intent is available to reopen");
|
|
5654
|
+
}
|
|
5655
|
+
if (!canRecreateSession) {
|
|
5656
|
+
return new Error(
|
|
5657
|
+
"This Transak purchase may already be in progress; continue recovery instead of reopening checkout"
|
|
5658
|
+
);
|
|
5659
|
+
}
|
|
5660
|
+
return null;
|
|
5661
|
+
}
|
|
5662
|
+
function getTransakSessionContextError({
|
|
5663
|
+
currentGeneration,
|
|
5664
|
+
expectedGeneration,
|
|
5665
|
+
scopeChanged
|
|
5666
|
+
}) {
|
|
5667
|
+
if (currentGeneration !== expectedGeneration) {
|
|
5668
|
+
return new Error("Transak checkout request was cancelled");
|
|
5669
|
+
}
|
|
5670
|
+
if (scopeChanged) {
|
|
5671
|
+
return new Error("On-ramp account or network changed while creating the session");
|
|
5672
|
+
}
|
|
5673
|
+
return null;
|
|
5674
|
+
}
|
|
5675
|
+
function getTransakSessionRequestError({
|
|
5676
|
+
currentGeneration,
|
|
5677
|
+
expectedGeneration,
|
|
5678
|
+
scopeChanged,
|
|
5679
|
+
activeIntentId,
|
|
5680
|
+
expectedIntentId
|
|
5681
|
+
}) {
|
|
5682
|
+
const contextError = getTransakSessionContextError({
|
|
5683
|
+
currentGeneration,
|
|
5684
|
+
expectedGeneration,
|
|
5685
|
+
scopeChanged
|
|
5686
|
+
});
|
|
5687
|
+
if (contextError) return contextError;
|
|
5688
|
+
if (activeIntentId !== expectedIntentId) {
|
|
5689
|
+
return new Error("The Transak purchase intent is no longer active");
|
|
5690
|
+
}
|
|
5691
|
+
return null;
|
|
5692
|
+
}
|
|
5693
|
+
function shouldSurfaceTransakSessionFailure({
|
|
5694
|
+
status,
|
|
5695
|
+
activeIntentId,
|
|
5696
|
+
activeVerificationId,
|
|
5697
|
+
activeVerificationRecord,
|
|
5698
|
+
expectedIntentId
|
|
5699
|
+
}) {
|
|
5700
|
+
if (!expectedIntentId || activeIntentId !== expectedIntentId) return false;
|
|
5701
|
+
if (status === "awaiting-delivery") return false;
|
|
5702
|
+
const ownsActiveVerification = activeVerificationRecord ? matchesOnRampTransaction(activeVerificationRecord, expectedIntentId) : activeVerificationId === expectedIntentId;
|
|
5703
|
+
return !(ownsActiveVerification && (status === "verifying" || status === "credited"));
|
|
5704
|
+
}
|
|
5705
|
+
function hasTransakProviderEvidence({
|
|
5706
|
+
status,
|
|
5707
|
+
activeIntentId,
|
|
5708
|
+
pending,
|
|
5709
|
+
activeVerificationRecord
|
|
5710
|
+
}) {
|
|
5711
|
+
if (!activeIntentId) return false;
|
|
5712
|
+
const ownsRecord = (record) => matchesOnRampTransaction(record, activeIntentId);
|
|
5713
|
+
return pending.some(ownsRecord) || activeVerificationRecord !== null && ownsRecord(activeVerificationRecord) || status === "awaiting-delivery" || status === "credited";
|
|
5714
|
+
}
|
|
5715
|
+
function useTransakOnRamp(options) {
|
|
5716
|
+
const { iframeTitle, iframeClassName, ...coreOptions } = options;
|
|
5717
|
+
const { executePrivateRead, privateReadQueryScope } = usePrivateReadRequest();
|
|
5718
|
+
const core = useOnRamp({ ...coreOptions, adapter: transakOnRampAdapter });
|
|
5719
|
+
const {
|
|
5720
|
+
prepareOnRampIntent,
|
|
5721
|
+
handleProviderLaunchReady,
|
|
5722
|
+
handleProviderLaunchFailed,
|
|
5723
|
+
handleProviderEvent,
|
|
5724
|
+
handleProviderClosed,
|
|
5725
|
+
refreshPending
|
|
5726
|
+
} = core;
|
|
5727
|
+
const [session, setSessionState] = useState(null);
|
|
5728
|
+
const [isLaunching, setIsLaunching] = useState(false);
|
|
5729
|
+
const [sessionRecreationState, setSessionRecreationState] = useState("none");
|
|
5730
|
+
const sessionRecreationStateRef = useRef("none");
|
|
5731
|
+
const sessionRef = useRef(null);
|
|
5732
|
+
const mountedSessionScopeRef = useRef(null);
|
|
5733
|
+
const generationRef = useRef(0);
|
|
5734
|
+
const sessionRequestRef = useRef(null);
|
|
5735
|
+
const activeIntentIdRef = useRef(core.activeIntentId);
|
|
5736
|
+
activeIntentIdRef.current = core.activeIntentId;
|
|
5737
|
+
const activeVerificationIdRef = useRef(core.activeVerificationId);
|
|
5738
|
+
activeVerificationIdRef.current = core.activeVerificationId;
|
|
5739
|
+
const activeVerificationRecord = useMemo(
|
|
5740
|
+
() => core.activeVerificationId ? core.pending.find((record) => record.transaction_id === core.activeVerificationId) ?? null : null,
|
|
5741
|
+
[core.activeVerificationId, core.pending]
|
|
5742
|
+
);
|
|
5743
|
+
const activeVerificationRecordRef = useRef(activeVerificationRecord);
|
|
5744
|
+
activeVerificationRecordRef.current = activeVerificationRecord;
|
|
5745
|
+
const pendingRef = useRef(core.pending);
|
|
5746
|
+
pendingRef.current = core.pending;
|
|
5747
|
+
const coreStatusRef = useRef(core.status);
|
|
5748
|
+
coreStatusRef.current = core.status;
|
|
5749
|
+
const [apiUrl, chainId, privateReadAddress] = privateReadQueryScope;
|
|
5750
|
+
const scopeIdentity = [
|
|
5751
|
+
apiUrl,
|
|
5752
|
+
chainId,
|
|
5753
|
+
privateReadAddress?.toLowerCase() ?? "",
|
|
5754
|
+
coreOptions.tokenId.toLowerCase(),
|
|
5755
|
+
getOnRampTokenFingerprint(core.selectedToken)
|
|
5756
|
+
].join("\0");
|
|
5757
|
+
const scopeSession = useMemo(() => Symbol(scopeIdentity), [scopeIdentity]);
|
|
5758
|
+
const scopeSessionRef = useRef(scopeSession);
|
|
5759
|
+
scopeSessionRef.current = scopeSession;
|
|
5760
|
+
const setSession = useCallback((next, nextScope) => {
|
|
5761
|
+
sessionRef.current = next;
|
|
5762
|
+
mountedSessionScopeRef.current = nextScope;
|
|
5763
|
+
setSessionState(next);
|
|
5764
|
+
}, []);
|
|
5765
|
+
const updateSessionRecreation = useCallback((event) => {
|
|
5766
|
+
const next = transitionTransakSessionRecreation(sessionRecreationStateRef.current, event);
|
|
5767
|
+
sessionRecreationStateRef.current = next;
|
|
5768
|
+
setSessionRecreationState(next);
|
|
5769
|
+
}, []);
|
|
5770
|
+
const hasProviderEvidence = hasTransakProviderEvidence({
|
|
5771
|
+
status: core.status,
|
|
5772
|
+
activeIntentId: core.activeIntentId,
|
|
5773
|
+
pending: core.pending,
|
|
5774
|
+
activeVerificationRecord
|
|
5775
|
+
});
|
|
5776
|
+
const canRecreateSession = sessionRecreationState === "preload-only" && !hasProviderEvidence;
|
|
5777
|
+
const requestSession = useCallback(
|
|
5778
|
+
(intentId, generation) => executePrivateRead(
|
|
5779
|
+
(readClient) => requestTransakWidgetSession({
|
|
5780
|
+
client: readClient,
|
|
5781
|
+
intentId,
|
|
5782
|
+
generation
|
|
5783
|
+
})
|
|
5784
|
+
),
|
|
5785
|
+
[executePrivateRead]
|
|
5786
|
+
);
|
|
5787
|
+
const launch = useCallback(
|
|
5788
|
+
(request) => {
|
|
5789
|
+
const currentRequest = sessionRequestRef.current;
|
|
5790
|
+
if (currentRequest?.generation === generationRef.current) return currentRequest.promise;
|
|
5791
|
+
const mountedSessionError = getMountedTransakSessionError(
|
|
5792
|
+
"launch",
|
|
5793
|
+
sessionRef.current !== null
|
|
5794
|
+
);
|
|
5795
|
+
if (mountedSessionError) return Promise.reject(mountedSessionError);
|
|
5796
|
+
const generation = ++generationRef.current;
|
|
5797
|
+
setIsLaunching(true);
|
|
5798
|
+
updateSessionRecreation("reset");
|
|
5799
|
+
let launchedIntentId = null;
|
|
5800
|
+
const pendingRequest = (async () => {
|
|
5801
|
+
try {
|
|
5802
|
+
const intent = await prepareOnRampIntent({
|
|
5803
|
+
providerAssetCode: request.providerAssetCode,
|
|
5804
|
+
quoteCurrencyAmount: request.quoteCurrencyAmount
|
|
5805
|
+
});
|
|
5806
|
+
launchedIntentId = intent.transaction_id;
|
|
5807
|
+
updateSessionRecreation("intent-created");
|
|
5808
|
+
const beforeSessionError = getTransakSessionContextError({
|
|
5809
|
+
currentGeneration: generationRef.current,
|
|
5810
|
+
expectedGeneration: generation,
|
|
5811
|
+
scopeChanged: scopeSessionRef.current !== scopeSession
|
|
5812
|
+
});
|
|
5813
|
+
if (beforeSessionError) throw beforeSessionError;
|
|
5814
|
+
const next = await requestSession(intent.transaction_id, generation);
|
|
5815
|
+
const afterSessionError = getTransakSessionRequestError({
|
|
5816
|
+
currentGeneration: generationRef.current,
|
|
5817
|
+
expectedGeneration: generation,
|
|
5818
|
+
scopeChanged: scopeSessionRef.current !== scopeSession,
|
|
5819
|
+
activeIntentId: activeIntentIdRef.current,
|
|
5820
|
+
expectedIntentId: intent.transaction_id
|
|
5821
|
+
});
|
|
5822
|
+
if (afterSessionError) throw afterSessionError;
|
|
5823
|
+
setSession(next, scopeSession);
|
|
5824
|
+
} catch (error) {
|
|
5825
|
+
const launchError = error instanceof Error ? error : new Error("Failed to launch Transak checkout");
|
|
5826
|
+
if (generationRef.current === generation && shouldSurfaceTransakSessionFailure({
|
|
5827
|
+
status: coreStatusRef.current,
|
|
5828
|
+
activeIntentId: activeIntentIdRef.current,
|
|
5829
|
+
activeVerificationId: activeVerificationIdRef.current,
|
|
5830
|
+
activeVerificationRecord: activeVerificationRecordRef.current,
|
|
5831
|
+
expectedIntentId: launchedIntentId
|
|
5832
|
+
})) {
|
|
5833
|
+
handleProviderLaunchFailed(launchError);
|
|
5834
|
+
}
|
|
5835
|
+
throw launchError;
|
|
5836
|
+
} finally {
|
|
5837
|
+
if (generationRef.current === generation) setIsLaunching(false);
|
|
5838
|
+
if (sessionRequestRef.current?.generation === generation) {
|
|
5839
|
+
sessionRequestRef.current = null;
|
|
5840
|
+
}
|
|
5841
|
+
}
|
|
5842
|
+
})();
|
|
5843
|
+
sessionRequestRef.current = { generation, promise: pendingRequest };
|
|
5844
|
+
return pendingRequest;
|
|
5845
|
+
},
|
|
5846
|
+
[
|
|
5847
|
+
handleProviderLaunchFailed,
|
|
5848
|
+
prepareOnRampIntent,
|
|
5849
|
+
requestSession,
|
|
5850
|
+
scopeSession,
|
|
5851
|
+
setSession,
|
|
5852
|
+
updateSessionRecreation
|
|
5853
|
+
]
|
|
5854
|
+
);
|
|
5855
|
+
const recreateSession = useCallback(() => {
|
|
5856
|
+
const currentRequest = sessionRequestRef.current;
|
|
5857
|
+
if (currentRequest?.generation === generationRef.current) return currentRequest.promise;
|
|
5858
|
+
const intentId = activeIntentIdRef.current;
|
|
5859
|
+
const hasProviderEvidence2 = hasTransakProviderEvidence({
|
|
5860
|
+
status: coreStatusRef.current,
|
|
5861
|
+
activeIntentId: intentId,
|
|
5862
|
+
pending: pendingRef.current,
|
|
5863
|
+
activeVerificationRecord: activeVerificationRecordRef.current
|
|
5864
|
+
});
|
|
5865
|
+
const recreateError = getTransakRecreateSessionError({
|
|
5866
|
+
hasMountedSession: sessionRef.current !== null,
|
|
5867
|
+
activeIntentId: intentId,
|
|
5868
|
+
canRecreateSession: sessionRecreationStateRef.current === "preload-only" && !hasProviderEvidence2
|
|
5869
|
+
});
|
|
5870
|
+
if (recreateError) return Promise.reject(recreateError);
|
|
5871
|
+
if (!intentId) return Promise.reject(new Error("No unresolved Transak intent is available"));
|
|
5872
|
+
const generation = ++generationRef.current;
|
|
5873
|
+
setIsLaunching(true);
|
|
5874
|
+
const pendingRequest = (async () => {
|
|
5875
|
+
try {
|
|
5876
|
+
const next = await requestSession(intentId, generation);
|
|
5877
|
+
const requestError = getTransakSessionRequestError({
|
|
5878
|
+
currentGeneration: generationRef.current,
|
|
5879
|
+
expectedGeneration: generation,
|
|
5880
|
+
scopeChanged: scopeSessionRef.current !== scopeSession,
|
|
5881
|
+
activeIntentId: activeIntentIdRef.current,
|
|
5882
|
+
expectedIntentId: intentId
|
|
5883
|
+
});
|
|
5884
|
+
if (requestError) throw requestError;
|
|
5885
|
+
setSession(next, scopeSession);
|
|
5886
|
+
} catch (error) {
|
|
5887
|
+
const launchError = error instanceof Error ? error : new Error("Failed to recreate Transak checkout");
|
|
5888
|
+
if (generationRef.current === generation && shouldSurfaceTransakSessionFailure({
|
|
5889
|
+
status: coreStatusRef.current,
|
|
5890
|
+
activeIntentId: activeIntentIdRef.current,
|
|
5891
|
+
activeVerificationId: activeVerificationIdRef.current,
|
|
5892
|
+
activeVerificationRecord: activeVerificationRecordRef.current,
|
|
5893
|
+
expectedIntentId: intentId
|
|
5894
|
+
})) {
|
|
5895
|
+
handleProviderLaunchFailed(launchError);
|
|
5896
|
+
}
|
|
5897
|
+
throw launchError;
|
|
5898
|
+
} finally {
|
|
5899
|
+
if (generationRef.current === generation) setIsLaunching(false);
|
|
5900
|
+
if (sessionRequestRef.current?.generation === generation) {
|
|
5901
|
+
sessionRequestRef.current = null;
|
|
5902
|
+
}
|
|
5903
|
+
}
|
|
5904
|
+
})();
|
|
5905
|
+
sessionRequestRef.current = { generation, promise: pendingRequest };
|
|
5906
|
+
return pendingRequest;
|
|
5907
|
+
}, [handleProviderLaunchFailed, requestSession, scopeSession, setSession]);
|
|
5908
|
+
const closeWidget = useCallback(async () => {
|
|
5909
|
+
generationRef.current++;
|
|
5910
|
+
const shouldReconcile = Boolean(sessionRef.current || activeIntentIdRef.current);
|
|
5911
|
+
setSession(null, null);
|
|
5912
|
+
setIsLaunching(false);
|
|
5913
|
+
if (shouldReconcile) await handleProviderClosed();
|
|
5914
|
+
}, [handleProviderClosed, setSession]);
|
|
5915
|
+
const getCurrentGeneration = useCallback(() => generationRef.current, []);
|
|
5916
|
+
const handleFrameReady = useCallback(
|
|
5917
|
+
(generation) => {
|
|
5918
|
+
const current = sessionRef.current;
|
|
5919
|
+
if (!current || current.generation !== generation || generationRef.current !== generation)
|
|
5920
|
+
return;
|
|
5921
|
+
updateSessionRecreation("provider-ui-activated");
|
|
5922
|
+
handleProviderLaunchReady();
|
|
5923
|
+
},
|
|
5924
|
+
[handleProviderLaunchReady, updateSessionRecreation]
|
|
5925
|
+
);
|
|
5926
|
+
const handleFrameExpired = useCallback(
|
|
5927
|
+
(generation) => {
|
|
5928
|
+
const current = sessionRef.current;
|
|
5929
|
+
if (!current || current.generation !== generation || generationRef.current !== generation)
|
|
5930
|
+
return;
|
|
5931
|
+
const shouldSurfaceFailure = shouldSurfaceTransakSessionFailure({
|
|
5932
|
+
status: coreStatusRef.current,
|
|
5933
|
+
activeIntentId: activeIntentIdRef.current,
|
|
5934
|
+
activeVerificationId: activeVerificationIdRef.current,
|
|
5935
|
+
activeVerificationRecord: activeVerificationRecordRef.current,
|
|
5936
|
+
expectedIntentId: current.intentId
|
|
5937
|
+
});
|
|
5938
|
+
generationRef.current++;
|
|
5939
|
+
setSession(null, null);
|
|
5940
|
+
if (shouldSurfaceFailure) {
|
|
5941
|
+
handleProviderLaunchFailed(
|
|
5942
|
+
new Error(
|
|
5943
|
+
"Transak checkout session expired before it could be loaded; reopen to continue"
|
|
5944
|
+
)
|
|
5945
|
+
);
|
|
5946
|
+
}
|
|
5947
|
+
},
|
|
5948
|
+
[handleProviderLaunchFailed, setSession]
|
|
5949
|
+
);
|
|
5950
|
+
const handleFrameAction = useCallback(
|
|
5951
|
+
async (generation, action) => {
|
|
5952
|
+
const current = sessionRef.current;
|
|
5953
|
+
if (!current || current.generation !== generation || generationRef.current !== generation)
|
|
5954
|
+
return;
|
|
5955
|
+
updateSessionRecreation("provider-ui-activated");
|
|
5956
|
+
switch (action.type) {
|
|
5957
|
+
case "ready":
|
|
5958
|
+
handleProviderLaunchReady();
|
|
5959
|
+
return;
|
|
5960
|
+
case "refresh":
|
|
5961
|
+
await refreshPending();
|
|
5962
|
+
return;
|
|
5963
|
+
case "provider-event":
|
|
5964
|
+
await handleProviderEvent(action.event);
|
|
5965
|
+
return;
|
|
5966
|
+
case "close":
|
|
5967
|
+
await closeWidget();
|
|
5968
|
+
}
|
|
5969
|
+
},
|
|
5970
|
+
[
|
|
5971
|
+
closeWidget,
|
|
5972
|
+
handleProviderEvent,
|
|
5973
|
+
handleProviderLaunchReady,
|
|
5974
|
+
refreshPending,
|
|
5975
|
+
updateSessionRecreation
|
|
5976
|
+
]
|
|
5977
|
+
);
|
|
5978
|
+
useEffect(() => {
|
|
5979
|
+
const current = sessionRef.current;
|
|
5980
|
+
if (!current || core.activeIntentId === current.intentId) return;
|
|
5981
|
+
generationRef.current++;
|
|
5982
|
+
setSession(null, null);
|
|
5983
|
+
}, [core.activeIntentId, setSession]);
|
|
5984
|
+
useEffect(() => {
|
|
5985
|
+
if (!core.activeIntentId) {
|
|
5986
|
+
updateSessionRecreation("reset");
|
|
5987
|
+
return;
|
|
5988
|
+
}
|
|
5989
|
+
if (hasProviderEvidence) {
|
|
5990
|
+
updateSessionRecreation("provider-evidence");
|
|
5991
|
+
}
|
|
5992
|
+
}, [core.activeIntentId, hasProviderEvidence, updateSessionRecreation]);
|
|
5993
|
+
useEffect(() => {
|
|
5994
|
+
generationRef.current++;
|
|
5995
|
+
setSession(null, null);
|
|
5996
|
+
setIsLaunching(false);
|
|
5997
|
+
updateSessionRecreation("reset");
|
|
5998
|
+
}, [scopeSession, setSession, updateSessionRecreation]);
|
|
5999
|
+
useEffect(
|
|
6000
|
+
() => () => {
|
|
6001
|
+
generationRef.current++;
|
|
6002
|
+
sessionRef.current = null;
|
|
6003
|
+
mountedSessionScopeRef.current = null;
|
|
6004
|
+
},
|
|
6005
|
+
[]
|
|
6006
|
+
);
|
|
6007
|
+
const scopedSession = mountedSessionScopeRef.current === scopeSession ? session : null;
|
|
6008
|
+
const widget = useMemo(
|
|
6009
|
+
() => scopedSession ? /* @__PURE__ */ jsx(
|
|
6010
|
+
TransakWidgetFrame,
|
|
6011
|
+
{
|
|
6012
|
+
session: scopedSession,
|
|
6013
|
+
getCurrentGeneration,
|
|
6014
|
+
shouldPollPending: core.status === "awaiting-purchase",
|
|
6015
|
+
refreshPending,
|
|
6016
|
+
onReady: handleFrameReady,
|
|
6017
|
+
onAction: handleFrameAction,
|
|
6018
|
+
onExpired: handleFrameExpired,
|
|
6019
|
+
title: iframeTitle,
|
|
6020
|
+
className: iframeClassName
|
|
6021
|
+
},
|
|
6022
|
+
scopedSession.generation
|
|
6023
|
+
) : null,
|
|
6024
|
+
[
|
|
6025
|
+
core.status,
|
|
6026
|
+
getCurrentGeneration,
|
|
6027
|
+
handleFrameAction,
|
|
6028
|
+
handleFrameExpired,
|
|
6029
|
+
handleFrameReady,
|
|
6030
|
+
iframeClassName,
|
|
6031
|
+
iframeTitle,
|
|
6032
|
+
refreshPending,
|
|
6033
|
+
scopedSession
|
|
6034
|
+
]
|
|
6035
|
+
);
|
|
6036
|
+
return {
|
|
6037
|
+
status: core.status,
|
|
6038
|
+
activeIntentId: core.activeIntentId,
|
|
6039
|
+
pending: core.pending,
|
|
6040
|
+
activeVerificationId: core.activeVerificationId,
|
|
6041
|
+
error: core.error,
|
|
6042
|
+
finalityProgress: core.finalityProgress,
|
|
6043
|
+
depositAddress: core.depositAddress,
|
|
6044
|
+
minDepositBaseUnits: core.minDepositBaseUnits,
|
|
6045
|
+
selectedToken: core.selectedToken,
|
|
6046
|
+
finishPendingVerification: core.finishPendingVerification,
|
|
6047
|
+
refreshPending,
|
|
6048
|
+
isLaunching,
|
|
6049
|
+
isWidgetOpen: scopedSession !== null,
|
|
6050
|
+
canRecreateSession,
|
|
6051
|
+
widget,
|
|
6052
|
+
launch,
|
|
6053
|
+
recreateSession,
|
|
6054
|
+
closeWidget
|
|
6055
|
+
};
|
|
6056
|
+
}
|
|
6057
|
+
|
|
6058
|
+
export { AccountingApiError, Button, DEFAULT_LOCK_DURATION_SECONDS, DEFAULT_ONRAMP_LOCK_BUFFER, FiatOnRampForm, HOSTED_AUTH_CLOCK_SKEW_MS, HostedAuthError, HostedAuthRequiredError, HostedAuthStateMismatchError, HttpClient, LOCK_TYPES, MODIFY_LOCK_TYPES, NETWORK_CONFIG, NetworkError, PostDepositLockError, PrivanaClient, PrivanaProvider, SUPPORTED_CHAINS, SiweAuthProvider, Skeleton, TRANSFER_LOCKED_TYPES, TRANSFER_TYPES, ValidationError, WITHDRAW_FROM_LOCK_TYPES, WITHDRAW_TYPES, applyLockBuffer, applyRefreshResponse, buildHostedAuthSession, buildSiweStatement, buttonVariants, canRetryOnRampVerification, canUseBrowserStorage, canUseSharedBrowserStorage, clampLockAmount, clearHostedAuthPendingTransaction, clearPendingLock, cn, createDomain, createHostedAuthPendingStorageKey, createHostedAuthState, createHostedAuthStorageKey, createLockExpiry, createPkceChallenge, createPkceVerifier, createProductOnRampFlowSnapshot, createProductOnRampOutcomeCallbacks, createSignedLockRequest, formatCountdown, formatTimeRemaining, formatTokenAmount, getAccountingContract, getApiUrl, getBlockNumber, getBrowserStorageItem, getChainById, getChainId, getExplorerAddressUrl, getExplorerLabel, getSharedBrowserStorageItem, getTransactionReceipt, getTransakMinimumTargetBaseUnits, getWalletClient3 as getWalletClient, isHostedAuthRefreshActive, isHostedAuthSessionActive, isMoonPayProductOnRamp, isSignedLockUsable, loadPendingLock, matchesFrozenOnRampToken, matchesOnRampTransaction, matchesProductOnRampScope, normalizeAddress, normalizeHex, parseHostedAuthCallback, parseTokenAmount, persistHostedAuthPendingTransaction, readHostedAuthPendingTransaction, readStoredHostedAuthSession, removeBrowserStorageItem, removeSharedBrowserStorageItem, requireDepositLockOwner, requireServiceAddress, resolveProductOnRamp, savePendingLock, setBrowserStorageItem, setSharedBrowserStorageItem, shortenAddress, signLockMessage, signModifyLockMessage, signTransferLockedMessage, signTransferMessage, signWithdrawFromLockMessage, signWithdrawMessage, stripHostedAuthCallbackParams, submitPendingLock, syncHostedAuthSessionToClient, useDepositVerification, useEnsureCorrectChain, useFiatOnRamp, usePrivanaContext, usePrivateReadRequest, useSafeAccount, useSafePrivanaContext, useSiweAuth, useTransakOnRamp, waitForTransactionReceipt };
|
|
6059
|
+
//# sourceMappingURL=chunk-ONXJGUOH.js.map
|
|
6060
|
+
//# sourceMappingURL=chunk-ONXJGUOH.js.map
|