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