@oasisprotocol/privana-sdk 0.5.4 → 0.5.5
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 +28 -19
- package/dist/{chunk-FGWMLLEC.cjs → chunk-7VNAGKRC.cjs} +916 -364
- package/dist/chunk-7VNAGKRC.cjs.map +1 -0
- package/dist/{chunk-QQG4RPYZ.js → chunk-PMBU2F2R.js} +917 -365
- package/dist/chunk-PMBU2F2R.js.map +1 -0
- package/dist/index.cjs +278 -252
- 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 +7 -4
- package/dist/index.d.ts +7 -4
- package/dist/index.js +37 -11
- package/dist/index.js.map +1 -1
- package/dist/on-ramp.cjs +3 -3
- package/dist/on-ramp.d.cts +81 -52
- package/dist/on-ramp.d.ts +81 -52
- package/dist/on-ramp.js +1 -1
- package/dist/{pending-lock-DSEqUHOw.d.cts → pending-lock-IgzZyg9C.d.cts} +22 -18
- package/dist/{pending-lock-DSEqUHOw.d.ts → pending-lock-IgzZyg9C.d.ts} +22 -18
- package/package.json +1 -1
- package/dist/chunk-FGWMLLEC.cjs.map +0 -1
- package/dist/chunk-QQG4RPYZ.js.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,
|
|
7
|
+
import { parseAbiItem, zeroAddress, walletActions, hexToString, formatUnits, parseUnits, 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';
|
|
@@ -620,9 +620,7 @@ var PrivanaClient = class _PrivanaClient {
|
|
|
620
620
|
wallet_address: request.wallet_address ? normalizeAddress(request.wallet_address) : void 0,
|
|
621
621
|
token_id: normalizeHex(request.token_id),
|
|
622
622
|
chain_id: request.chain_id,
|
|
623
|
-
moonpay_currency_code: request.moonpay_currency_code
|
|
624
|
-
base_currency_code: request.base_currency_code,
|
|
625
|
-
base_currency_amount: request.base_currency_amount
|
|
623
|
+
moonpay_currency_code: request.moonpay_currency_code
|
|
626
624
|
});
|
|
627
625
|
}
|
|
628
626
|
async updateOnRamp(transactionId, request) {
|
|
@@ -641,8 +639,15 @@ var PrivanaClient = class _PrivanaClient {
|
|
|
641
639
|
}
|
|
642
640
|
);
|
|
643
641
|
}
|
|
644
|
-
async getPendingOnRamps() {
|
|
645
|
-
|
|
642
|
+
async getPendingOnRamps(externalTransactionIds = []) {
|
|
643
|
+
const params = new URLSearchParams();
|
|
644
|
+
for (const transactionId of externalTransactionIds.slice(0, 10)) {
|
|
645
|
+
params.append("externalTransactionId", transactionId);
|
|
646
|
+
}
|
|
647
|
+
const query = params.toString();
|
|
648
|
+
return this.http.get(
|
|
649
|
+
`/v1/accounting/onramp/pending${query ? `?${query}` : ""}`
|
|
650
|
+
);
|
|
646
651
|
}
|
|
647
652
|
/**
|
|
648
653
|
* @deprecated This mutates the shared client's headers and displaces its Authorization
|
|
@@ -2222,6 +2227,49 @@ function loadPendingLock(userAddress, correlationId) {
|
|
|
2222
2227
|
function clearPendingLock(userAddress, correlationId) {
|
|
2223
2228
|
removeBrowserStorageItem(pendingLockKey(userAddress, correlationId));
|
|
2224
2229
|
}
|
|
2230
|
+
|
|
2231
|
+
// src/sdk/hooks/deposit-finality.ts
|
|
2232
|
+
async function checkDepositWithFinalityRetry({
|
|
2233
|
+
checkDeposit,
|
|
2234
|
+
isStale,
|
|
2235
|
+
onRetry,
|
|
2236
|
+
timeoutMs,
|
|
2237
|
+
retryIntervalMs,
|
|
2238
|
+
startedAt = Date.now(),
|
|
2239
|
+
now = Date.now,
|
|
2240
|
+
sleep = defaultSleep
|
|
2241
|
+
}) {
|
|
2242
|
+
while (true) {
|
|
2243
|
+
if (isStale()) return { kind: "stale" };
|
|
2244
|
+
try {
|
|
2245
|
+
const response = await checkDeposit();
|
|
2246
|
+
if (!isInsufficientFinalityMessage(response.detail) || response.status !== "error") {
|
|
2247
|
+
return { kind: "response", response };
|
|
2248
|
+
}
|
|
2249
|
+
if (response.detail) onRetry(response.detail);
|
|
2250
|
+
} catch (error) {
|
|
2251
|
+
if (isStale()) return { kind: "stale" };
|
|
2252
|
+
if (!isInsufficientFinalityError(error)) throw error;
|
|
2253
|
+
onRetry(
|
|
2254
|
+
error instanceof AccountingApiError && error.detail ? error.detail : error instanceof Error ? error.message : String(error)
|
|
2255
|
+
);
|
|
2256
|
+
}
|
|
2257
|
+
if (now() - startedAt > timeoutMs) return { kind: "timeout" };
|
|
2258
|
+
await sleep(retryIntervalMs);
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
function isInsufficientFinalityError(error) {
|
|
2262
|
+
if (error instanceof AccountingApiError) {
|
|
2263
|
+
return isInsufficientFinalityMessage(error.detail) || isInsufficientFinalityMessage(error.message);
|
|
2264
|
+
}
|
|
2265
|
+
return error instanceof Error && isInsufficientFinalityMessage(error.message);
|
|
2266
|
+
}
|
|
2267
|
+
function isInsufficientFinalityMessage(message) {
|
|
2268
|
+
return message?.includes("Insufficient finality") ?? false;
|
|
2269
|
+
}
|
|
2270
|
+
function defaultSleep(milliseconds) {
|
|
2271
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
2272
|
+
}
|
|
2225
2273
|
var INITIAL_AUTH_BACKOFF_MS = 5e3;
|
|
2226
2274
|
var MAX_AUTH_BACKOFF_MS = 6e4;
|
|
2227
2275
|
var privateReadFailureCache = /* @__PURE__ */ new Map();
|
|
@@ -2461,44 +2509,27 @@ function useDepositVerification(options = {}) {
|
|
|
2461
2509
|
queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
|
|
2462
2510
|
};
|
|
2463
2511
|
try {
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
if (isStale()) return;
|
|
2484
|
-
continue;
|
|
2485
|
-
}
|
|
2486
|
-
triggerResult = result;
|
|
2487
|
-
} catch (err) {
|
|
2488
|
-
if (isStale()) return;
|
|
2489
|
-
if (!isInsufficientFinalityError(err)) {
|
|
2490
|
-
throw err;
|
|
2491
|
-
}
|
|
2492
|
-
const message = err instanceof AccountingApiError && err.detail ? err.detail : err instanceof Error ? err.message : String(err);
|
|
2493
|
-
onCheckRetryRef.current?.(message);
|
|
2494
|
-
if (Date.now() - pollStartTime > pollTimeout) {
|
|
2495
|
-
markVerificationTimedOut();
|
|
2496
|
-
return;
|
|
2497
|
-
}
|
|
2498
|
-
await sleep(finalityRetryInterval);
|
|
2499
|
-
if (isStale()) return;
|
|
2500
|
-
}
|
|
2512
|
+
const finality = await checkDepositWithFinalityRetry({
|
|
2513
|
+
checkDeposit: () => executePrivateRead(
|
|
2514
|
+
(readClient) => readClient.checkDeposit({
|
|
2515
|
+
chain_id: chainId,
|
|
2516
|
+
tx_hash: hash,
|
|
2517
|
+
amount: amount.toString(),
|
|
2518
|
+
log_index: logIndex
|
|
2519
|
+
})
|
|
2520
|
+
),
|
|
2521
|
+
isStale,
|
|
2522
|
+
onRetry: (message) => onCheckRetryRef.current?.(message),
|
|
2523
|
+
timeoutMs: pollTimeout,
|
|
2524
|
+
retryIntervalMs: finalityRetryInterval,
|
|
2525
|
+
startedAt: pollStartTime
|
|
2526
|
+
});
|
|
2527
|
+
if (finality.kind === "stale") return;
|
|
2528
|
+
if (finality.kind === "timeout") {
|
|
2529
|
+
markVerificationTimedOut();
|
|
2530
|
+
return;
|
|
2501
2531
|
}
|
|
2532
|
+
const triggerResult = finality.response;
|
|
2502
2533
|
if (isStale()) return;
|
|
2503
2534
|
if (triggerResult.status === "credited") {
|
|
2504
2535
|
const creditedAmount = creditedAmountFromResponse(triggerResult, amount);
|
|
@@ -2643,18 +2674,6 @@ function creditedAmountFromResponse(response, requestedAmount) {
|
|
|
2643
2674
|
function isDefinitiveCandidateFailure(error) {
|
|
2644
2675
|
return error instanceof AccountingApiError && error.statusCode === 400;
|
|
2645
2676
|
}
|
|
2646
|
-
function sleep(ms) {
|
|
2647
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2648
|
-
}
|
|
2649
|
-
function isInsufficientFinalityError(error) {
|
|
2650
|
-
if (error instanceof AccountingApiError) {
|
|
2651
|
-
return isInsufficientFinalityMessage(error.detail) || isInsufficientFinalityMessage(error.message);
|
|
2652
|
-
}
|
|
2653
|
-
return error instanceof Error && isInsufficientFinalityMessage(error.message);
|
|
2654
|
-
}
|
|
2655
|
-
function isInsufficientFinalityMessage(message) {
|
|
2656
|
-
return message?.includes("Insufficient finality") ?? false;
|
|
2657
|
-
}
|
|
2658
2677
|
function cn(...inputs) {
|
|
2659
2678
|
return twMerge(clsx(inputs));
|
|
2660
2679
|
}
|
|
@@ -2758,6 +2777,31 @@ function Skeleton({ className, ...props }) {
|
|
|
2758
2777
|
);
|
|
2759
2778
|
}
|
|
2760
2779
|
|
|
2780
|
+
// src/sdk/on-ramp/moonpay-adapter.ts
|
|
2781
|
+
var moonPayOnRampAdapter = {
|
|
2782
|
+
provider: "moonpay",
|
|
2783
|
+
pollPendingWhileOpen: true,
|
|
2784
|
+
buildIntentRequest: ({ walletAddress, tokenId, chainId, providerAssetCode }) => ({
|
|
2785
|
+
wallet_address: walletAddress,
|
|
2786
|
+
token_id: tokenId,
|
|
2787
|
+
chain_id: chainId,
|
|
2788
|
+
moonpay_currency_code: providerAssetCode
|
|
2789
|
+
}),
|
|
2790
|
+
registerTransaction: async ({ client, intentId, providerTransactionId, tokenId, chainId }) => client.updateOnRamp(intentId, {
|
|
2791
|
+
token_id: tokenId,
|
|
2792
|
+
chain_id: chainId,
|
|
2793
|
+
moonpay_transaction_id: intentId === providerTransactionId ? void 0 : providerTransactionId
|
|
2794
|
+
})
|
|
2795
|
+
};
|
|
2796
|
+
function normalizeMoonPayProviderEvent(kind, event) {
|
|
2797
|
+
return {
|
|
2798
|
+
provider: "moonpay",
|
|
2799
|
+
kind,
|
|
2800
|
+
providerTransactionId: event.id,
|
|
2801
|
+
intentId: event.externalTransactionId || void 0
|
|
2802
|
+
};
|
|
2803
|
+
}
|
|
2804
|
+
|
|
2761
2805
|
// ../../node_modules/@wagmi/core/dist/esm/utils/getAction.js
|
|
2762
2806
|
function getAction(client, actionFn, name) {
|
|
2763
2807
|
const action_implicit = client[actionFn.name];
|
|
@@ -3008,6 +3052,239 @@ async function waitForTransactionReceipt(config, parameters) {
|
|
|
3008
3052
|
chainId: client.chain.id
|
|
3009
3053
|
};
|
|
3010
3054
|
}
|
|
3055
|
+
|
|
3056
|
+
// src/sdk/on-ramp/provider.ts
|
|
3057
|
+
function resolveOnRampProviderEventTarget(configuredProvider, activeIntentId, event) {
|
|
3058
|
+
if (event.provider !== configuredProvider) {
|
|
3059
|
+
throw new Error(
|
|
3060
|
+
`On-ramp event provider ${event.provider} does not match configured adapter ${configuredProvider}`
|
|
3061
|
+
);
|
|
3062
|
+
}
|
|
3063
|
+
if (!event.providerTransactionId) {
|
|
3064
|
+
throw new Error("On-ramp provider event is missing a transaction id");
|
|
3065
|
+
}
|
|
3066
|
+
const intentId = event.intentId || activeIntentId || event.providerTransactionId;
|
|
3067
|
+
const isActive = activeIntentId !== null && intentId === activeIntentId;
|
|
3068
|
+
return {
|
|
3069
|
+
intentId,
|
|
3070
|
+
isActive,
|
|
3071
|
+
isStale: activeIntentId !== null && event.intentId !== void 0 && !isActive
|
|
3072
|
+
};
|
|
3073
|
+
}
|
|
3074
|
+
function assertOnRampRecordProvider(record, configuredProvider) {
|
|
3075
|
+
if (record.provider !== configuredProvider) {
|
|
3076
|
+
throw new Error(
|
|
3077
|
+
`On-ramp record provider ${String(record.provider)} does not match configured adapter ${configuredProvider}`
|
|
3078
|
+
);
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
function matchesOnRampTransaction(record, transactionId) {
|
|
3082
|
+
return record.transaction_id === transactionId || record.external_transaction_id === transactionId || record.provider_transaction_id === transactionId || record.moonpay_transaction_id === transactionId;
|
|
3083
|
+
}
|
|
3084
|
+
function getOnRampVerificationKey(record) {
|
|
3085
|
+
return record.on_chain_tx_hash ?? record.transaction_id;
|
|
3086
|
+
}
|
|
3087
|
+
function getOnRampIntentId(record) {
|
|
3088
|
+
return record.external_transaction_id ?? record.transaction_id;
|
|
3089
|
+
}
|
|
3090
|
+
async function verifyPendingOnRampsSequentially({
|
|
3091
|
+
records,
|
|
3092
|
+
shouldStop,
|
|
3093
|
+
wasTriggered,
|
|
3094
|
+
trigger,
|
|
3095
|
+
waitForTerminal
|
|
3096
|
+
}) {
|
|
3097
|
+
for (const record of records) {
|
|
3098
|
+
if (shouldStop()) return;
|
|
3099
|
+
if (!record.on_chain_tx_hash) continue;
|
|
3100
|
+
const key = getOnRampVerificationKey(record);
|
|
3101
|
+
if (wasTriggered(key)) continue;
|
|
3102
|
+
try {
|
|
3103
|
+
await trigger(record);
|
|
3104
|
+
} catch {
|
|
3105
|
+
continue;
|
|
3106
|
+
}
|
|
3107
|
+
await waitForTerminal(key);
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
|
|
3111
|
+
// src/sdk/on-ramp/recovery.ts
|
|
3112
|
+
var MAX_UNRESOLVED_ONRAMP_INTENTS = 10;
|
|
3113
|
+
var MIN_ONRAMP_PENDING_REQUEST_INTERVAL_MS = 1e4;
|
|
3114
|
+
var ONRAMP_INTENT_RETENTION_MS = 365 * 24 * 60 * 60 * 1e3;
|
|
3115
|
+
var ONRAMP_RECOVERY_VERSION = 1;
|
|
3116
|
+
function recoveryKey(scope) {
|
|
3117
|
+
const api = encodeURIComponent(scope.apiUrl.replace(/\/$/, ""));
|
|
3118
|
+
return `privana:onramp-intents:${api}:${scope.chainId}:${scope.userAddress.toLowerCase()}`;
|
|
3119
|
+
}
|
|
3120
|
+
function isIntent(value) {
|
|
3121
|
+
if (!value || typeof value !== "object") return false;
|
|
3122
|
+
const intent = value;
|
|
3123
|
+
return typeof intent.transactionId === "string" && intent.transactionId.length > 0 && intent.transactionId.length <= 512 && Number.isFinite(intent.savedAt) && (intent.savedAt ?? 0) > 0;
|
|
3124
|
+
}
|
|
3125
|
+
function writeIntents(scope, intents) {
|
|
3126
|
+
if (intents.length === 0) {
|
|
3127
|
+
removeBrowserStorageItem(recoveryKey(scope));
|
|
3128
|
+
return true;
|
|
3129
|
+
}
|
|
3130
|
+
return setBrowserStorageItem(
|
|
3131
|
+
recoveryKey(scope),
|
|
3132
|
+
JSON.stringify({
|
|
3133
|
+
version: ONRAMP_RECOVERY_VERSION,
|
|
3134
|
+
intents
|
|
3135
|
+
})
|
|
3136
|
+
);
|
|
3137
|
+
}
|
|
3138
|
+
function loadUnresolvedOnRampIntents(scope, now = Date.now()) {
|
|
3139
|
+
const key = recoveryKey(scope);
|
|
3140
|
+
try {
|
|
3141
|
+
const raw = getBrowserStorageItem(key);
|
|
3142
|
+
if (!raw) return [];
|
|
3143
|
+
const parsed = JSON.parse(raw);
|
|
3144
|
+
if (parsed.version !== ONRAMP_RECOVERY_VERSION || !Array.isArray(parsed.intents)) {
|
|
3145
|
+
removeBrowserStorageItem(key);
|
|
3146
|
+
return [];
|
|
3147
|
+
}
|
|
3148
|
+
const retained = parsed.intents.filter(isIntent).filter((intent) => now - intent.savedAt <= ONRAMP_INTENT_RETENTION_MS).sort((left, right) => left.savedAt - right.savedAt).slice(-MAX_UNRESOLVED_ONRAMP_INTENTS);
|
|
3149
|
+
if (retained.length !== parsed.intents.length) writeIntents(scope, retained);
|
|
3150
|
+
return retained;
|
|
3151
|
+
} catch {
|
|
3152
|
+
removeBrowserStorageItem(key);
|
|
3153
|
+
return [];
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
function rememberUnresolvedOnRampIntent(scope, transactionId, now = Date.now()) {
|
|
3157
|
+
const intents = loadUnresolvedOnRampIntents(scope, now).filter(
|
|
3158
|
+
(intent) => intent.transactionId !== transactionId
|
|
3159
|
+
);
|
|
3160
|
+
intents.push({ transactionId, savedAt: now });
|
|
3161
|
+
return writeIntents(scope, intents.slice(-MAX_UNRESOLVED_ONRAMP_INTENTS));
|
|
3162
|
+
}
|
|
3163
|
+
function forgetUnresolvedOnRampIntent(scope, transactionId, now = Date.now()) {
|
|
3164
|
+
const intents = loadUnresolvedOnRampIntents(scope, now).filter(
|
|
3165
|
+
(intent) => intent.transactionId !== transactionId
|
|
3166
|
+
);
|
|
3167
|
+
writeIntents(scope, intents);
|
|
3168
|
+
}
|
|
3169
|
+
function discardInvalidOnRampIntent(scope, invalidIntentId, activeIntentId) {
|
|
3170
|
+
forgetUnresolvedOnRampIntent(scope, invalidIntentId);
|
|
3171
|
+
const invalidatedActiveIntent = activeIntentId === invalidIntentId;
|
|
3172
|
+
return {
|
|
3173
|
+
activeIntentId: invalidatedActiveIntent ? null : activeIntentId,
|
|
3174
|
+
invalidatedActiveIntent
|
|
3175
|
+
};
|
|
3176
|
+
}
|
|
3177
|
+
function getOnRampCloseRecoveryAction(activeIntentId, purchaseEventObserved) {
|
|
3178
|
+
if (!activeIntentId) return "refresh";
|
|
3179
|
+
return purchaseEventObserved ? "poll-for-delivery" : "refresh-and-retain";
|
|
3180
|
+
}
|
|
3181
|
+
function createPendingOnRampReadCoordinator({
|
|
3182
|
+
read,
|
|
3183
|
+
intervalMs = MIN_ONRAMP_PENDING_REQUEST_INTERVAL_MS,
|
|
3184
|
+
now = Date.now,
|
|
3185
|
+
sleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds))
|
|
3186
|
+
}) {
|
|
3187
|
+
const safeInterval = Math.max(
|
|
3188
|
+
MIN_ONRAMP_PENDING_REQUEST_INTERVAL_MS,
|
|
3189
|
+
Number.isFinite(intervalMs) ? intervalMs : MIN_ONRAMP_PENDING_REQUEST_INTERVAL_MS
|
|
3190
|
+
);
|
|
3191
|
+
let inFlight = null;
|
|
3192
|
+
let nextReadAt = 0;
|
|
3193
|
+
return () => {
|
|
3194
|
+
if (inFlight) return inFlight;
|
|
3195
|
+
const request = (async () => {
|
|
3196
|
+
const delay = Math.max(0, nextReadAt - now());
|
|
3197
|
+
if (delay > 0) await sleep(delay);
|
|
3198
|
+
nextReadAt = now() + safeInterval;
|
|
3199
|
+
return read();
|
|
3200
|
+
})();
|
|
3201
|
+
const tracked = request.finally(() => {
|
|
3202
|
+
if (inFlight === tracked) inFlight = null;
|
|
3203
|
+
});
|
|
3204
|
+
inFlight = tracked;
|
|
3205
|
+
return tracked;
|
|
3206
|
+
};
|
|
3207
|
+
}
|
|
3208
|
+
async function getPendingOnRampsWithRecovery({
|
|
3209
|
+
client,
|
|
3210
|
+
intentIds,
|
|
3211
|
+
onInvalidIntent
|
|
3212
|
+
}) {
|
|
3213
|
+
const bounded = [...new Set(intentIds)].slice(-MAX_UNRESOLVED_ONRAMP_INTENTS);
|
|
3214
|
+
try {
|
|
3215
|
+
return await client.getPendingOnRamps(bounded);
|
|
3216
|
+
} catch (error) {
|
|
3217
|
+
if (!isBadRequest(error) || bounded.length === 0) throw error;
|
|
3218
|
+
}
|
|
3219
|
+
const valid = [];
|
|
3220
|
+
for (const intentId of bounded) {
|
|
3221
|
+
try {
|
|
3222
|
+
await client.getPendingOnRamps([intentId]);
|
|
3223
|
+
valid.push(intentId);
|
|
3224
|
+
} catch (error) {
|
|
3225
|
+
if (!isBadRequest(error)) throw error;
|
|
3226
|
+
onInvalidIntent(intentId);
|
|
3227
|
+
}
|
|
3228
|
+
}
|
|
3229
|
+
return client.getPendingOnRamps(valid);
|
|
3230
|
+
}
|
|
3231
|
+
function isBadRequest(error) {
|
|
3232
|
+
return error instanceof AccountingApiError && error.statusCode === 400;
|
|
3233
|
+
}
|
|
3234
|
+
var ERC20_TRANSFER_EVENT = parseAbiItem(
|
|
3235
|
+
"event Transfer(address indexed from, address indexed to, uint256 value)"
|
|
3236
|
+
);
|
|
3237
|
+
function assertErc20OnRampToken(tokenAddress) {
|
|
3238
|
+
if (tokenAddress.toLowerCase() === zeroAddress) {
|
|
3239
|
+
throw new Error("On-ramp verification supports ERC-20 tokens only");
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3242
|
+
function erc20MinDepositBaseUnits(minDepositByChain, chainId) {
|
|
3243
|
+
const minimum = minDepositByChain?.[String(chainId)]?.erc20;
|
|
3244
|
+
return minimum !== void 0 && /^\d+$/.test(minimum) ? BigInt(minimum) : void 0;
|
|
3245
|
+
}
|
|
3246
|
+
function deliveredErc20Amount(logs, tokenAddress, depositAddress) {
|
|
3247
|
+
let delivered = 0n;
|
|
3248
|
+
for (const log of logs) {
|
|
3249
|
+
if (log.address.toLowerCase() !== tokenAddress.toLowerCase()) continue;
|
|
3250
|
+
try {
|
|
3251
|
+
const decoded = decodeEventLog({
|
|
3252
|
+
abi: [ERC20_TRANSFER_EVENT],
|
|
3253
|
+
data: log.data,
|
|
3254
|
+
topics: log.topics
|
|
3255
|
+
});
|
|
3256
|
+
if (decoded.eventName === "Transfer" && decoded.args.to.toLowerCase() === depositAddress.toLowerCase()) {
|
|
3257
|
+
delivered += decoded.args.value;
|
|
3258
|
+
}
|
|
3259
|
+
} catch {
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
return delivered;
|
|
3263
|
+
}
|
|
3264
|
+
|
|
3265
|
+
// src/sdk/on-ramp/settlement.ts
|
|
3266
|
+
async function settlePendingOnRampLock({
|
|
3267
|
+
client,
|
|
3268
|
+
userAddress,
|
|
3269
|
+
transactionId,
|
|
3270
|
+
creditedAmount
|
|
3271
|
+
}) {
|
|
3272
|
+
const payload = loadPendingLock(userAddress, transactionId);
|
|
3273
|
+
if (!payload) {
|
|
3274
|
+
clearPendingLock(userAddress, transactionId);
|
|
3275
|
+
return { kind: "not-found" };
|
|
3276
|
+
}
|
|
3277
|
+
try {
|
|
3278
|
+
const response = await submitPendingLock({
|
|
3279
|
+
client,
|
|
3280
|
+
payload,
|
|
3281
|
+
creditedAmount
|
|
3282
|
+
});
|
|
3283
|
+
return { kind: "submitted", payload, response };
|
|
3284
|
+
} finally {
|
|
3285
|
+
clearPendingLock(userAddress, transactionId);
|
|
3286
|
+
}
|
|
3287
|
+
}
|
|
3011
3288
|
function useEnsureCorrectChain() {
|
|
3012
3289
|
const config = useConfig();
|
|
3013
3290
|
const chainId = useChainId();
|
|
@@ -3060,15 +3337,13 @@ function useEnsureCorrectChain() {
|
|
|
3060
3337
|
};
|
|
3061
3338
|
}
|
|
3062
3339
|
|
|
3063
|
-
// src/sdk/hooks/use-
|
|
3340
|
+
// src/sdk/hooks/use-on-ramp.ts
|
|
3064
3341
|
var DEFAULT_DELIVERY_TIMEOUT_MS = 12e4;
|
|
3065
3342
|
var DEFAULT_VERIFICATION_TIMEOUT_MS = 10 * 6e4;
|
|
3066
3343
|
var DEFAULT_FINALITY_RETRY_INTERVAL_MS = 15e3;
|
|
3067
|
-
|
|
3068
|
-
"event Transfer(address indexed from, address indexed to, uint256 value)"
|
|
3069
|
-
);
|
|
3070
|
-
function useFiatOnRamp(options) {
|
|
3344
|
+
function useOnRamp(options) {
|
|
3071
3345
|
const {
|
|
3346
|
+
adapter,
|
|
3072
3347
|
tokenId,
|
|
3073
3348
|
postDepositLock,
|
|
3074
3349
|
onCredited,
|
|
@@ -3078,24 +3353,40 @@ function useFiatOnRamp(options) {
|
|
|
3078
3353
|
onDebugEvent
|
|
3079
3354
|
} = options;
|
|
3080
3355
|
const deliveryTimeout = options.deliveryTimeout ?? DEFAULT_DELIVERY_TIMEOUT_MS;
|
|
3081
|
-
const
|
|
3356
|
+
const requestedDeliveryPollInterval = options.deliveryPollInterval ?? MIN_ONRAMP_PENDING_REQUEST_INTERVAL_MS;
|
|
3357
|
+
const deliveryPollInterval = Number.isFinite(requestedDeliveryPollInterval) ? Math.max(requestedDeliveryPollInterval, MIN_ONRAMP_PENDING_REQUEST_INTERVAL_MS) : MIN_ONRAMP_PENDING_REQUEST_INTERVAL_MS;
|
|
3082
3358
|
const verificationTimeout = options.verificationTimeout ?? DEFAULT_VERIFICATION_TIMEOUT_MS;
|
|
3083
3359
|
const finalityRetryInterval = options.finalityRetryInterval ?? DEFAULT_FINALITY_RETRY_INTERVAL_MS;
|
|
3084
3360
|
const { address } = useAccount();
|
|
3085
3361
|
const { data: walletClient } = useWalletClient();
|
|
3086
3362
|
const { client, enabledTokens, networkConfig, serviceAddress } = usePrivanaContext();
|
|
3087
3363
|
const { executePrivateRead, privateReadAddress, privateReadReady } = usePrivateReadRequest();
|
|
3364
|
+
const executeOnRampPrivateRead = executePrivateRead;
|
|
3088
3365
|
const privateReadAddressRef = useRef(privateReadAddress);
|
|
3089
3366
|
privateReadAddressRef.current = privateReadAddress;
|
|
3090
3367
|
const { ensureCorrectChain } = useEnsureCorrectChain();
|
|
3091
3368
|
const wagmiConfig = useConfig();
|
|
3092
3369
|
const queryClient = useQueryClient();
|
|
3093
3370
|
const selectedToken = enabledTokens.find((t) => t.id.toLowerCase() === tokenId.toLowerCase());
|
|
3371
|
+
const recoveryScope = useMemo(
|
|
3372
|
+
() => privateReadAddress ? {
|
|
3373
|
+
apiUrl: networkConfig.apiUrl,
|
|
3374
|
+
chainId: networkConfig.chainId,
|
|
3375
|
+
userAddress: privateReadAddress
|
|
3376
|
+
} : null,
|
|
3377
|
+
[networkConfig.apiUrl, networkConfig.chainId, privateReadAddress]
|
|
3378
|
+
);
|
|
3379
|
+
const recoveryScopeRef = useRef(recoveryScope);
|
|
3380
|
+
recoveryScopeRef.current = recoveryScope;
|
|
3381
|
+
const flowIdentity = `${networkConfig.apiUrl}\0${networkConfig.chainId}\0${privateReadAddress ?? ""}\0${privateReadReady}\0${tokenId}\0${adapter.provider}`;
|
|
3382
|
+
const flowSession = useMemo(() => Symbol(flowIdentity), [flowIdentity]);
|
|
3383
|
+
const flowSessionRef = useRef(flowSession);
|
|
3384
|
+
flowSessionRef.current = flowSession;
|
|
3094
3385
|
const [status, setStatus] = useState("idle");
|
|
3095
3386
|
const [pending, setPending] = useState([]);
|
|
3096
3387
|
const [error, setError] = useState(null);
|
|
3097
3388
|
const [depositAddress, setDepositAddress] = useState();
|
|
3098
|
-
const [
|
|
3389
|
+
const [minDepositByChain, setMinDepositByChain] = useState();
|
|
3099
3390
|
const [activeIntentId, setActiveIntentId] = useState(null);
|
|
3100
3391
|
const [activeVerificationId, setActiveVerificationId] = useState(null);
|
|
3101
3392
|
const [finalityProgress, setFinalityProgress] = useState({});
|
|
@@ -3105,14 +3396,21 @@ function useFiatOnRamp(options) {
|
|
|
3105
3396
|
const onErrorRef = useRef(onError);
|
|
3106
3397
|
const onDebugEventRef = useRef(onDebugEvent);
|
|
3107
3398
|
const statusRef = useRef(status);
|
|
3399
|
+
const depositAddressSessionRef = useRef(null);
|
|
3108
3400
|
const activeIntentIdRef = useRef(null);
|
|
3401
|
+
const activeIntentSessionRef = useRef(null);
|
|
3109
3402
|
const activeVerificationRecordRef = useRef(null);
|
|
3110
3403
|
const activeVerificationKeyRef = useRef(null);
|
|
3404
|
+
const activeVerificationSurfacesFailureRef = useRef(false);
|
|
3111
3405
|
const lockOwnerRef = useRef(null);
|
|
3112
3406
|
const triggeredVerificationKeysRef = useRef(/* @__PURE__ */ new Set());
|
|
3113
3407
|
const activeVerificationDoneRef = useRef(null);
|
|
3114
3408
|
const closeReconcilePromiseRef = useRef(null);
|
|
3409
|
+
const deliveryWaitPromiseRef = useRef(null);
|
|
3115
3410
|
const purchaseInitiatedRef = useRef(false);
|
|
3411
|
+
const scopedDepositAddress = depositAddressSessionRef.current === flowSession ? depositAddress : void 0;
|
|
3412
|
+
const scopedMinDepositByChain = depositAddressSessionRef.current === flowSession ? minDepositByChain : void 0;
|
|
3413
|
+
const scopedMinDepositBaseUnits = selectedToken ? erc20MinDepositBaseUnits(scopedMinDepositByChain, selectedToken.chainId) : void 0;
|
|
3116
3414
|
useEffect(() => {
|
|
3117
3415
|
onCreditedRef.current = onCredited;
|
|
3118
3416
|
onLockSubmittedRef.current = onLockSubmitted;
|
|
@@ -3126,10 +3424,6 @@ function useFiatOnRamp(options) {
|
|
|
3126
3424
|
useEffect(() => {
|
|
3127
3425
|
activeIntentIdRef.current = activeIntentId;
|
|
3128
3426
|
}, [activeIntentId]);
|
|
3129
|
-
useEffect(() => {
|
|
3130
|
-
activeIntentIdRef.current = null;
|
|
3131
|
-
setActiveIntentId(null);
|
|
3132
|
-
}, [tokenId]);
|
|
3133
3427
|
const emitDebug = useCallback(
|
|
3134
3428
|
(event, payload) => {
|
|
3135
3429
|
onDebugEventRef.current?.({
|
|
@@ -3146,24 +3440,28 @@ function useFiatOnRamp(options) {
|
|
|
3146
3440
|
emitDebug("private-read-state", { privateReadReady });
|
|
3147
3441
|
}, [emitDebug, privateReadReady]);
|
|
3148
3442
|
useEffect(() => {
|
|
3443
|
+
depositAddressSessionRef.current = null;
|
|
3149
3444
|
if (!privateReadReady) {
|
|
3150
3445
|
emitDebug("deposit-address:skip", { reason: "private-read-not-ready" });
|
|
3151
3446
|
setDepositAddress(void 0);
|
|
3152
|
-
|
|
3447
|
+
setMinDepositByChain(void 0);
|
|
3153
3448
|
return;
|
|
3154
3449
|
}
|
|
3450
|
+
setDepositAddress(void 0);
|
|
3451
|
+
setMinDepositByChain(void 0);
|
|
3155
3452
|
let cancelled = false;
|
|
3156
3453
|
void (async () => {
|
|
3157
3454
|
try {
|
|
3158
3455
|
emitDebug("deposit-address:request");
|
|
3159
|
-
const resp = await
|
|
3160
|
-
if (cancelled) return;
|
|
3456
|
+
const resp = await executeOnRampPrivateRead((readClient) => readClient.getDepositAddress());
|
|
3457
|
+
if (cancelled || flowSessionRef.current !== flowSession) return;
|
|
3458
|
+
depositAddressSessionRef.current = flowSession;
|
|
3161
3459
|
setDepositAddress(resp.deposit_address);
|
|
3460
|
+
setMinDepositByChain(resp.min_deposit);
|
|
3162
3461
|
const token = enabledTokens.find((t) => t.id.toLowerCase() === tokenId.toLowerCase());
|
|
3163
3462
|
const mins = token ? resp.min_deposit?.[String(token.chainId)] : void 0;
|
|
3164
|
-
if (mins?.erc20) setMinDepositBaseUnits(BigInt(mins.erc20));
|
|
3165
3463
|
emitDebug("deposit-address:success", {
|
|
3166
|
-
|
|
3464
|
+
depositAddressReady: true,
|
|
3167
3465
|
selectedToken: token ? summariseToken(token) : null,
|
|
3168
3466
|
minDepositBaseUnits: mins?.erc20 ?? null
|
|
3169
3467
|
});
|
|
@@ -3177,8 +3475,53 @@ function useFiatOnRamp(options) {
|
|
|
3177
3475
|
return () => {
|
|
3178
3476
|
cancelled = true;
|
|
3179
3477
|
};
|
|
3180
|
-
}, [
|
|
3478
|
+
}, [emitDebug, enabledTokens, executeOnRampPrivateRead, flowSession, privateReadReady, tokenId]);
|
|
3479
|
+
const fetchPendingRows = useCallback(async () => {
|
|
3480
|
+
const intents = recoveryScope ? loadUnresolvedOnRampIntents(recoveryScope) : [];
|
|
3481
|
+
const intentIds = intents.map((intent) => intent.transactionId);
|
|
3482
|
+
if (activeIntentIdRef.current && activeIntentSessionRef.current === flowSession) {
|
|
3483
|
+
intentIds.push(activeIntentIdRef.current);
|
|
3484
|
+
}
|
|
3485
|
+
const { pending: rows } = await executeOnRampPrivateRead(
|
|
3486
|
+
(readClient) => getPendingOnRampsWithRecovery({
|
|
3487
|
+
client: readClient,
|
|
3488
|
+
intentIds,
|
|
3489
|
+
onInvalidIntent: (intentId) => {
|
|
3490
|
+
if (!recoveryScope || flowSessionRef.current !== flowSession) return;
|
|
3491
|
+
const disposition = discardInvalidOnRampIntent(
|
|
3492
|
+
recoveryScope,
|
|
3493
|
+
intentId,
|
|
3494
|
+
activeIntentIdRef.current
|
|
3495
|
+
);
|
|
3496
|
+
if (disposition.invalidatedActiveIntent) {
|
|
3497
|
+
activeIntentIdRef.current = null;
|
|
3498
|
+
activeIntentSessionRef.current = null;
|
|
3499
|
+
setActiveIntentId(null);
|
|
3500
|
+
purchaseInitiatedRef.current = false;
|
|
3501
|
+
lockOwnerRef.current = null;
|
|
3502
|
+
if (statusRef.current !== "verifying" && statusRef.current !== "credited") {
|
|
3503
|
+
statusRef.current = "idle";
|
|
3504
|
+
setStatus("idle");
|
|
3505
|
+
setError(null);
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3508
|
+
emitDebug("pending:discard-invalid-intent", {
|
|
3509
|
+
invalidatedActiveIntent: disposition.invalidatedActiveIntent
|
|
3510
|
+
});
|
|
3511
|
+
}
|
|
3512
|
+
})
|
|
3513
|
+
);
|
|
3514
|
+
return rows;
|
|
3515
|
+
}, [emitDebug, executeOnRampPrivateRead, flowSession, recoveryScope]);
|
|
3516
|
+
const readPendingRows = useMemo(
|
|
3517
|
+
() => createPendingOnRampReadCoordinator({
|
|
3518
|
+
read: fetchPendingRows,
|
|
3519
|
+
intervalMs: deliveryPollInterval
|
|
3520
|
+
}),
|
|
3521
|
+
[deliveryPollInterval, fetchPendingRows]
|
|
3522
|
+
);
|
|
3181
3523
|
const refreshPending = useCallback(async () => {
|
|
3524
|
+
if (flowSessionRef.current !== flowSession) return;
|
|
3182
3525
|
if (!privateReadReady) {
|
|
3183
3526
|
emitDebug("pending:skip", { reason: "private-read-not-ready" });
|
|
3184
3527
|
setPending([]);
|
|
@@ -3186,79 +3529,75 @@ function useFiatOnRamp(options) {
|
|
|
3186
3529
|
}
|
|
3187
3530
|
try {
|
|
3188
3531
|
emitDebug("pending:request");
|
|
3189
|
-
const
|
|
3190
|
-
|
|
3191
|
-
);
|
|
3532
|
+
const rows = await readPendingRows();
|
|
3533
|
+
if (flowSessionRef.current !== flowSession) return;
|
|
3192
3534
|
setPending(rows);
|
|
3193
3535
|
emitDebug("pending:success", {
|
|
3194
3536
|
count: rows.length,
|
|
3195
3537
|
rows: rows.map(summariseOnRampRecord)
|
|
3196
3538
|
});
|
|
3197
3539
|
} catch (err) {
|
|
3540
|
+
if (flowSessionRef.current !== flowSession) return;
|
|
3198
3541
|
emitDebug("pending:error", errorPayload(err));
|
|
3199
3542
|
console.warn("Failed to load pending on-ramps:", err);
|
|
3200
3543
|
}
|
|
3201
|
-
}, [emitDebug,
|
|
3202
|
-
useEffect(() => {
|
|
3203
|
-
refreshPending();
|
|
3204
|
-
}, [refreshPending]);
|
|
3544
|
+
}, [emitDebug, flowSession, privateReadReady, readPendingRows]);
|
|
3205
3545
|
const clearActiveVerification = useCallback((expectedKey) => {
|
|
3206
3546
|
const key = activeVerificationKeyRef.current;
|
|
3207
3547
|
if (expectedKey != null && key !== null && key !== expectedKey) return;
|
|
3208
3548
|
if (key) triggeredVerificationKeysRef.current.delete(key);
|
|
3209
3549
|
activeVerificationKeyRef.current = null;
|
|
3210
3550
|
activeVerificationRecordRef.current = null;
|
|
3551
|
+
activeVerificationSurfacesFailureRef.current = false;
|
|
3211
3552
|
setActiveVerificationId(null);
|
|
3212
3553
|
activeVerificationDoneRef.current?.();
|
|
3213
3554
|
activeVerificationDoneRef.current = null;
|
|
3214
3555
|
}, []);
|
|
3215
3556
|
const submitPendingLockAfterCredit = useCallback(
|
|
3216
3557
|
async (transactionId, userAddress, creditedAmount) => {
|
|
3217
|
-
const signedLock = loadPendingLock(userAddress, transactionId);
|
|
3218
|
-
if (!signedLock) {
|
|
3219
|
-
clearPendingLock(userAddress, transactionId);
|
|
3220
|
-
if (!postDepositLock || transactionId !== activeIntentIdRef.current) return;
|
|
3221
|
-
const error2 = new PostDepositLockError(
|
|
3222
|
-
"No persisted signed lock found for this on-ramp",
|
|
3223
|
-
"not-found"
|
|
3224
|
-
);
|
|
3225
|
-
emitDebug("lock:not-found", { transactionId });
|
|
3226
|
-
(onLockFailedRef.current ?? onErrorRef.current)?.(error2);
|
|
3227
|
-
return;
|
|
3228
|
-
}
|
|
3229
3558
|
try {
|
|
3230
|
-
const
|
|
3559
|
+
const settlement = await settlePendingOnRampLock({
|
|
3560
|
+
client,
|
|
3561
|
+
userAddress,
|
|
3562
|
+
transactionId,
|
|
3563
|
+
creditedAmount
|
|
3564
|
+
});
|
|
3565
|
+
if (settlement.kind === "not-found") {
|
|
3566
|
+
if (!postDepositLock || transactionId !== activeIntentIdRef.current) return;
|
|
3567
|
+
const lockError = new PostDepositLockError(
|
|
3568
|
+
"No persisted signed lock found for this on-ramp",
|
|
3569
|
+
"not-found"
|
|
3570
|
+
);
|
|
3571
|
+
emitDebug("lock:not-found");
|
|
3572
|
+
(onLockFailedRef.current ?? onErrorRef.current)?.(lockError);
|
|
3573
|
+
return;
|
|
3574
|
+
}
|
|
3231
3575
|
queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
|
|
3232
3576
|
queryClient.invalidateQueries({ queryKey: ["accounting-locked-funds"] });
|
|
3233
3577
|
queryClient.invalidateQueries({ queryKey: ["accounting-total-locked-balance"] });
|
|
3234
3578
|
queryClient.invalidateQueries({ queryKey: ["accounting-history"] });
|
|
3235
3579
|
emitDebug("lock:submitted", {
|
|
3236
|
-
|
|
3237
|
-
amount: signedLock.amount,
|
|
3238
|
-
submissionId: result.submission_id
|
|
3580
|
+
submissionIdPresent: Boolean(settlement.response.submission_id)
|
|
3239
3581
|
});
|
|
3240
|
-
onLockSubmittedRef.current?.(
|
|
3582
|
+
onLockSubmittedRef.current?.(settlement.response);
|
|
3241
3583
|
} catch (err) {
|
|
3242
3584
|
const error2 = err instanceof PostDepositLockError ? err : new PostDepositLockError(
|
|
3243
3585
|
err instanceof Error ? err.message : "Lock submission failed",
|
|
3244
3586
|
"submission-failed",
|
|
3245
|
-
|
|
3587
|
+
void 0,
|
|
3246
3588
|
creditedAmount,
|
|
3247
3589
|
{ cause: err }
|
|
3248
3590
|
);
|
|
3249
3591
|
emitDebug("lock:failed", {
|
|
3250
|
-
transactionId,
|
|
3251
3592
|
reason: error2.reason,
|
|
3252
3593
|
message: error2.message
|
|
3253
3594
|
});
|
|
3254
3595
|
(onLockFailedRef.current ?? onErrorRef.current)?.(error2);
|
|
3255
|
-
} finally {
|
|
3256
|
-
clearPendingLock(userAddress, transactionId);
|
|
3257
3596
|
}
|
|
3258
3597
|
},
|
|
3259
3598
|
[client, emitDebug, postDepositLock, queryClient]
|
|
3260
3599
|
);
|
|
3261
|
-
const { verify } = useDepositVerification({
|
|
3600
|
+
const { verify, reset: resetDepositVerification } = useDepositVerification({
|
|
3262
3601
|
pollTimeout: verificationTimeout,
|
|
3263
3602
|
pollInterval: options.verificationPollInterval,
|
|
3264
3603
|
finalityRetryInterval,
|
|
@@ -3273,12 +3612,13 @@ function useFiatOnRamp(options) {
|
|
|
3273
3612
|
},
|
|
3274
3613
|
onCredited: (depositTxHash, _response, creditedAmount) => {
|
|
3275
3614
|
const record = activeVerificationRecordRef.current;
|
|
3615
|
+
const recoveryScopeAtCredit = recoveryScopeRef.current;
|
|
3276
3616
|
const verificationKey = record ? getOnRampVerificationKey(record) : null;
|
|
3277
3617
|
emitDebug("verification:credited", {
|
|
3278
3618
|
depositTxHash,
|
|
3279
3619
|
record: record ? summariseOnRampRecord(record) : null
|
|
3280
3620
|
});
|
|
3281
|
-
if (record && activeIntentIdRef.current
|
|
3621
|
+
if (record && activeIntentIdRef.current && matchesOnRampTransaction(record, activeIntentIdRef.current)) {
|
|
3282
3622
|
setStatus("credited");
|
|
3283
3623
|
}
|
|
3284
3624
|
if (record) {
|
|
@@ -3292,7 +3632,7 @@ function useFiatOnRamp(options) {
|
|
|
3292
3632
|
if (lockOwner) {
|
|
3293
3633
|
void submitPendingLockAfterCredit(record.transaction_id, lockOwner, creditedAmount);
|
|
3294
3634
|
} else if (postDepositLock) {
|
|
3295
|
-
emitDebug("lock:owner-unavailable"
|
|
3635
|
+
emitDebug("lock:owner-unavailable");
|
|
3296
3636
|
(onLockFailedRef.current ?? onErrorRef.current)?.(
|
|
3297
3637
|
new PostDepositLockError(
|
|
3298
3638
|
"No wallet address available to look up the signed lock for this on-ramp",
|
|
@@ -3305,10 +3645,9 @@ function useFiatOnRamp(options) {
|
|
|
3305
3645
|
try {
|
|
3306
3646
|
if (record && depositTxHash.startsWith("0x")) {
|
|
3307
3647
|
emitDebug("onramp:mark-deposit-triggered-request", {
|
|
3308
|
-
transactionId: record.transaction_id,
|
|
3309
3648
|
depositTxHash
|
|
3310
3649
|
});
|
|
3311
|
-
const updated = await
|
|
3650
|
+
const updated = await executeOnRampPrivateRead(
|
|
3312
3651
|
(readClient) => readClient.updateOnRamp(record.transaction_id, {
|
|
3313
3652
|
deposit_tx_hash: depositTxHash
|
|
3314
3653
|
})
|
|
@@ -3321,11 +3660,17 @@ function useFiatOnRamp(options) {
|
|
|
3321
3660
|
emitDebug("onramp:mark-deposit-triggered-error", errorPayload(err));
|
|
3322
3661
|
console.warn("Failed to mark on-ramp row complete:", err);
|
|
3323
3662
|
} finally {
|
|
3663
|
+
if (record && recoveryScopeAtCredit) {
|
|
3664
|
+
forgetUnresolvedOnRampIntent(recoveryScopeAtCredit, getOnRampIntentId(record));
|
|
3665
|
+
}
|
|
3324
3666
|
await refreshPending();
|
|
3325
3667
|
clearActiveVerification(verificationKey);
|
|
3326
|
-
if (record && activeIntentIdRef.current
|
|
3668
|
+
if (record && activeIntentIdRef.current && matchesOnRampTransaction(record, activeIntentIdRef.current)) {
|
|
3327
3669
|
activeIntentIdRef.current = null;
|
|
3670
|
+
activeIntentSessionRef.current = null;
|
|
3328
3671
|
setActiveIntentId(null);
|
|
3672
|
+
purchaseInitiatedRef.current = false;
|
|
3673
|
+
lockOwnerRef.current = null;
|
|
3329
3674
|
}
|
|
3330
3675
|
}
|
|
3331
3676
|
})();
|
|
@@ -3333,12 +3678,13 @@ function useFiatOnRamp(options) {
|
|
|
3333
3678
|
},
|
|
3334
3679
|
onCheckTimeout: (depositTxHash) => {
|
|
3335
3680
|
const record = activeVerificationRecordRef.current;
|
|
3681
|
+
const shouldSurfaceFailure = activeVerificationSurfacesFailureRef.current || !record;
|
|
3336
3682
|
const err = new Error(
|
|
3337
3683
|
"Privana verification is still pending. Retry from the pending on-ramp list if it does not complete."
|
|
3338
3684
|
);
|
|
3339
3685
|
emitDebug("verification:timeout", { depositTxHash, message: err.message });
|
|
3340
3686
|
clearActiveVerification();
|
|
3341
|
-
if (
|
|
3687
|
+
if (shouldSurfaceFailure) {
|
|
3342
3688
|
setStatus("failed");
|
|
3343
3689
|
setError(err);
|
|
3344
3690
|
}
|
|
@@ -3347,18 +3693,37 @@ function useFiatOnRamp(options) {
|
|
|
3347
3693
|
},
|
|
3348
3694
|
onError: (err) => {
|
|
3349
3695
|
const record = activeVerificationRecordRef.current;
|
|
3696
|
+
const shouldSurfaceFailure = activeVerificationSurfacesFailureRef.current || !record;
|
|
3350
3697
|
emitDebug("verification:error", errorPayload(err));
|
|
3351
3698
|
clearActiveVerification();
|
|
3352
|
-
if (
|
|
3699
|
+
if (shouldSurfaceFailure) {
|
|
3353
3700
|
setStatus("failed");
|
|
3354
3701
|
setError(err);
|
|
3355
3702
|
}
|
|
3356
3703
|
onErrorRef.current?.(err);
|
|
3357
3704
|
}
|
|
3358
3705
|
});
|
|
3706
|
+
useEffect(() => {
|
|
3707
|
+
activeIntentIdRef.current = null;
|
|
3708
|
+
activeIntentSessionRef.current = null;
|
|
3709
|
+
setActiveIntentId(null);
|
|
3710
|
+
purchaseInitiatedRef.current = false;
|
|
3711
|
+
lockOwnerRef.current = null;
|
|
3712
|
+
resetDepositVerification();
|
|
3713
|
+
clearActiveVerification();
|
|
3714
|
+
triggeredVerificationKeysRef.current.clear();
|
|
3715
|
+
setPending([]);
|
|
3716
|
+
setFinalityProgress({});
|
|
3717
|
+
statusRef.current = "idle";
|
|
3718
|
+
setStatus("idle");
|
|
3719
|
+
setError(null);
|
|
3720
|
+
}, [clearActiveVerification, flowSession, resetDepositVerification]);
|
|
3721
|
+
useEffect(() => {
|
|
3722
|
+
void refreshPending();
|
|
3723
|
+
}, [refreshPending]);
|
|
3359
3724
|
const prepareOnRampIntent = useCallback(
|
|
3360
3725
|
async ({
|
|
3361
|
-
|
|
3726
|
+
providerAssetCode,
|
|
3362
3727
|
baseCurrencyCode,
|
|
3363
3728
|
baseCurrencyAmount,
|
|
3364
3729
|
quoteCurrencyAmount
|
|
@@ -3368,7 +3733,7 @@ function useFiatOnRamp(options) {
|
|
|
3368
3733
|
purchaseInitiatedRef.current = false;
|
|
3369
3734
|
const token = enabledTokens.find((t) => t.id.toLowerCase() === tokenId.toLowerCase());
|
|
3370
3735
|
if (!token) throw new Error(`Unknown token: ${tokenId}`);
|
|
3371
|
-
if (!
|
|
3736
|
+
if (!scopedDepositAddress) throw new Error("Privana deposit address is not ready");
|
|
3372
3737
|
let lockAmount;
|
|
3373
3738
|
let lockOwner;
|
|
3374
3739
|
if (postDepositLock) {
|
|
@@ -3395,22 +3760,35 @@ function useFiatOnRamp(options) {
|
|
|
3395
3760
|
emitDebug("intent:create-request", {
|
|
3396
3761
|
tokenId,
|
|
3397
3762
|
chainId: token.chainId,
|
|
3398
|
-
|
|
3763
|
+
provider: adapter.provider,
|
|
3764
|
+
providerAssetCode,
|
|
3399
3765
|
baseCurrencyCode: baseCurrencyCode ?? null,
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3766
|
+
baseCurrencyAmountPresent: baseCurrencyAmount !== void 0,
|
|
3767
|
+
quoteCurrencyAmountPresent: quoteCurrencyAmount !== void 0,
|
|
3768
|
+
depositAddressReady: true
|
|
3403
3769
|
});
|
|
3404
|
-
const record = await
|
|
3405
|
-
(readClient) => readClient.createOnRampIntent(
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3770
|
+
const record = await executeOnRampPrivateRead(
|
|
3771
|
+
(readClient) => readClient.createOnRampIntent(
|
|
3772
|
+
adapter.buildIntentRequest({
|
|
3773
|
+
walletAddress: scopedDepositAddress,
|
|
3774
|
+
tokenId,
|
|
3775
|
+
chainId: token.chainId,
|
|
3776
|
+
providerAssetCode
|
|
3777
|
+
})
|
|
3778
|
+
)
|
|
3413
3779
|
);
|
|
3780
|
+
if (flowSessionRef.current !== flowSession) {
|
|
3781
|
+
throw new Error("On-ramp account or network changed while creating the intent");
|
|
3782
|
+
}
|
|
3783
|
+
assertOnRampRecordProvider(record, adapter.provider);
|
|
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
|
+
}
|
|
3414
3792
|
if (postDepositLock && lockOwner && lockAmount !== void 0) {
|
|
3415
3793
|
const signingWalletClient = await getWalletClient3(wagmiConfig, {
|
|
3416
3794
|
chainId: networkConfig.chainId
|
|
@@ -3428,15 +3806,23 @@ function useFiatOnRamp(options) {
|
|
|
3428
3806
|
if (privateReadAddressRef.current?.toLowerCase() !== lockOwner.toLowerCase()) {
|
|
3429
3807
|
throw new Error("Authenticated deposit account changed while signing");
|
|
3430
3808
|
}
|
|
3809
|
+
if (flowSessionRef.current !== flowSession) {
|
|
3810
|
+
throw new Error("On-ramp account or network changed while signing");
|
|
3811
|
+
}
|
|
3431
3812
|
savePendingLock(lockOwner, record.transaction_id, signedLock);
|
|
3432
3813
|
lockOwnerRef.current = lockOwner;
|
|
3433
3814
|
emitDebug("intent:lock-signed", {
|
|
3434
|
-
|
|
3435
|
-
amount: signedLock.amount,
|
|
3436
|
-
expiry: signedLock.expiry
|
|
3815
|
+
lockConfigured: true
|
|
3437
3816
|
});
|
|
3438
3817
|
}
|
|
3818
|
+
if (recoveryScope && !rememberUnresolvedOnRampIntent(recoveryScope, record.transaction_id)) {
|
|
3819
|
+
emitDebug("intent:recovery-storage-unavailable");
|
|
3820
|
+
}
|
|
3821
|
+
if (flowSessionRef.current !== flowSession) {
|
|
3822
|
+
throw new Error("On-ramp account or network changed while preparing the purchase");
|
|
3823
|
+
}
|
|
3439
3824
|
activeIntentIdRef.current = record.transaction_id;
|
|
3825
|
+
activeIntentSessionRef.current = flowSession;
|
|
3440
3826
|
setActiveIntentId(record.transaction_id);
|
|
3441
3827
|
emitDebug("intent:create-success", {
|
|
3442
3828
|
record: summariseOnRampRecord(record)
|
|
@@ -3444,150 +3830,163 @@ function useFiatOnRamp(options) {
|
|
|
3444
3830
|
return record;
|
|
3445
3831
|
} catch (err) {
|
|
3446
3832
|
const e = err instanceof Error ? err : new Error("Failed to create on-ramp intent");
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3833
|
+
if (flowSessionRef.current === flowSession) {
|
|
3834
|
+
setStatus("failed");
|
|
3835
|
+
setError(e);
|
|
3836
|
+
emitDebug("intent:create-error", errorPayload(e));
|
|
3837
|
+
onErrorRef.current?.(e);
|
|
3838
|
+
}
|
|
3451
3839
|
throw e;
|
|
3452
3840
|
}
|
|
3453
3841
|
},
|
|
3454
3842
|
[
|
|
3843
|
+
adapter,
|
|
3455
3844
|
address,
|
|
3456
3845
|
client,
|
|
3457
|
-
depositAddress,
|
|
3458
3846
|
emitDebug,
|
|
3459
3847
|
enabledTokens,
|
|
3460
3848
|
ensureCorrectChain,
|
|
3461
|
-
|
|
3849
|
+
executeOnRampPrivateRead,
|
|
3850
|
+
flowSession,
|
|
3462
3851
|
networkConfig,
|
|
3463
3852
|
postDepositLock,
|
|
3464
3853
|
privateReadAddress,
|
|
3854
|
+
recoveryScope,
|
|
3855
|
+
scopedDepositAddress,
|
|
3465
3856
|
serviceAddress,
|
|
3466
3857
|
tokenId,
|
|
3467
3858
|
wagmiConfig,
|
|
3468
3859
|
walletClient
|
|
3469
3860
|
]
|
|
3470
3861
|
);
|
|
3471
|
-
const
|
|
3472
|
-
async (
|
|
3862
|
+
const registerProviderTransaction = useCallback(
|
|
3863
|
+
async (event, intentId) => {
|
|
3864
|
+
if (flowSessionRef.current !== flowSession) return;
|
|
3865
|
+
const register = adapter.registerTransaction;
|
|
3866
|
+
if (!register) return;
|
|
3473
3867
|
const token = enabledTokens.find((t) => t.id.toLowerCase() === tokenId.toLowerCase());
|
|
3474
3868
|
if (!token) {
|
|
3475
|
-
emitDebug("register-
|
|
3476
|
-
|
|
3869
|
+
emitDebug("provider-transaction:register-skip", {
|
|
3870
|
+
provider: adapter.provider,
|
|
3477
3871
|
reason: "selected-token-not-found"
|
|
3478
3872
|
});
|
|
3479
3873
|
return;
|
|
3480
3874
|
}
|
|
3481
|
-
const transactionId = activeIntentIdRef.current ?? moonpayTransactionId;
|
|
3482
3875
|
try {
|
|
3483
|
-
emitDebug("register-
|
|
3484
|
-
|
|
3485
|
-
|
|
3876
|
+
emitDebug("provider-transaction:register-request", {
|
|
3877
|
+
provider: adapter.provider,
|
|
3878
|
+
intentIdPresent: Boolean(intentId),
|
|
3879
|
+
providerTransactionIdPresent: Boolean(event.providerTransactionId),
|
|
3486
3880
|
tokenId,
|
|
3487
3881
|
chainId: token.chainId
|
|
3488
3882
|
});
|
|
3489
|
-
const record = await
|
|
3490
|
-
(readClient) =>
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3883
|
+
const record = await executeOnRampPrivateRead(
|
|
3884
|
+
(readClient) => register({
|
|
3885
|
+
client: readClient,
|
|
3886
|
+
intentId,
|
|
3887
|
+
providerTransactionId: event.providerTransactionId,
|
|
3888
|
+
tokenId,
|
|
3889
|
+
chainId: token.chainId
|
|
3494
3890
|
})
|
|
3495
3891
|
);
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3892
|
+
if (flowSessionRef.current !== flowSession) return;
|
|
3893
|
+
if (record) assertOnRampRecordProvider(record, adapter.provider);
|
|
3894
|
+
emitDebug("provider-transaction:register-success", {
|
|
3895
|
+
provider: adapter.provider,
|
|
3896
|
+
record: record ? summariseOnRampRecord(record) : null
|
|
3500
3897
|
});
|
|
3501
3898
|
} catch (err) {
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3899
|
+
if (flowSessionRef.current !== flowSession) return;
|
|
3900
|
+
emitDebug("provider-transaction:register-error", {
|
|
3901
|
+
provider: adapter.provider,
|
|
3505
3902
|
...errorPayload(err)
|
|
3506
3903
|
});
|
|
3507
3904
|
console.warn("Failed to register on-ramp token mapping:", err);
|
|
3508
3905
|
}
|
|
3509
3906
|
},
|
|
3510
|
-
[emitDebug, enabledTokens,
|
|
3511
|
-
);
|
|
3512
|
-
const handleTransactionCreated = useCallback(
|
|
3513
|
-
async (props) => {
|
|
3514
|
-
emitDebug("moonpay:onTransactionCreated", summariseMoonPayEventProps(props));
|
|
3515
|
-
purchaseInitiatedRef.current = true;
|
|
3516
|
-
await registerOnRampTokenMapping(props.id);
|
|
3517
|
-
},
|
|
3518
|
-
[emitDebug, registerOnRampTokenMapping]
|
|
3907
|
+
[adapter, emitDebug, enabledTokens, executeOnRampPrivateRead, flowSession, tokenId]
|
|
3519
3908
|
);
|
|
3520
|
-
const
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
onErrorRef.current?.(e);
|
|
3539
|
-
throw err;
|
|
3540
|
-
}
|
|
3909
|
+
const handleProviderLaunchReady = useCallback(() => {
|
|
3910
|
+
if (flowSessionRef.current !== flowSession) return;
|
|
3911
|
+
setError(null);
|
|
3912
|
+
statusRef.current = "awaiting-purchase";
|
|
3913
|
+
setStatus("awaiting-purchase");
|
|
3914
|
+
emitDebug("provider:launch-ready", { provider: adapter.provider });
|
|
3915
|
+
}, [adapter.provider, emitDebug, flowSession]);
|
|
3916
|
+
const handleProviderLaunchFailed = useCallback(
|
|
3917
|
+
(launchError) => {
|
|
3918
|
+
if (flowSessionRef.current !== flowSession) return;
|
|
3919
|
+
statusRef.current = "failed";
|
|
3920
|
+
setStatus("failed");
|
|
3921
|
+
setError(launchError);
|
|
3922
|
+
emitDebug("provider:launch-error", {
|
|
3923
|
+
provider: adapter.provider,
|
|
3924
|
+
...errorPayload(launchError)
|
|
3925
|
+
});
|
|
3926
|
+
onErrorRef.current?.(launchError);
|
|
3541
3927
|
},
|
|
3542
|
-
[emitDebug,
|
|
3928
|
+
[adapter.provider, emitDebug, flowSession]
|
|
3543
3929
|
);
|
|
3544
3930
|
const waitForOnChainHash = useCallback(
|
|
3545
3931
|
async (transactionId) => {
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3932
|
+
if (flowSessionRef.current !== flowSession) return null;
|
|
3933
|
+
const existing = deliveryWaitPromiseRef.current;
|
|
3934
|
+
if (existing?.flowSession === flowSession && existing.transactionId === transactionId) {
|
|
3935
|
+
return existing.promise;
|
|
3936
|
+
}
|
|
3937
|
+
const promise = (async () => {
|
|
3938
|
+
const startTime = Date.now();
|
|
3939
|
+
emitDebug("delivery-poll:start", {
|
|
3940
|
+
deliveryTimeout,
|
|
3941
|
+
deliveryPollInterval
|
|
3942
|
+
});
|
|
3943
|
+
while (Date.now() - startTime < deliveryTimeout) {
|
|
3944
|
+
if (flowSessionRef.current !== flowSession) return null;
|
|
3945
|
+
try {
|
|
3946
|
+
const rows = await readPendingRows();
|
|
3947
|
+
if (flowSessionRef.current !== flowSession) return null;
|
|
3948
|
+
setPending(rows);
|
|
3949
|
+
const record = rows.find((r) => matchesOnRampTransaction(r, transactionId));
|
|
3950
|
+
emitDebug("delivery-poll:tick", {
|
|
3951
|
+
count: rows.length,
|
|
3952
|
+
matchingRecord: record ? summariseOnRampRecord(record) : null
|
|
3953
|
+
});
|
|
3954
|
+
if (record?.on_chain_tx_hash) {
|
|
3955
|
+
emitDebug("delivery-poll:success", {
|
|
3956
|
+
record: summariseOnRampRecord(record)
|
|
3957
|
+
});
|
|
3958
|
+
return record;
|
|
3959
|
+
}
|
|
3960
|
+
} catch (err) {
|
|
3961
|
+
emitDebug("delivery-poll:error", {
|
|
3962
|
+
...errorPayload(err)
|
|
3568
3963
|
});
|
|
3569
|
-
|
|
3964
|
+
console.warn("Polling pending on-ramps failed:", err);
|
|
3570
3965
|
}
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3966
|
+
await new Promise((resolve) => setTimeout(resolve, deliveryPollInterval));
|
|
3967
|
+
}
|
|
3968
|
+
if (flowSessionRef.current !== flowSession) return null;
|
|
3969
|
+
emitDebug("delivery-poll:timeout");
|
|
3970
|
+
return null;
|
|
3971
|
+
})();
|
|
3972
|
+
const entry = { flowSession, transactionId, promise };
|
|
3973
|
+
deliveryWaitPromiseRef.current = entry;
|
|
3974
|
+
try {
|
|
3975
|
+
return await promise;
|
|
3976
|
+
} finally {
|
|
3977
|
+
if (deliveryWaitPromiseRef.current === entry) {
|
|
3978
|
+
deliveryWaitPromiseRef.current = null;
|
|
3577
3979
|
}
|
|
3578
|
-
await new Promise((r) => setTimeout(r, deliveryPollInterval));
|
|
3579
3980
|
}
|
|
3580
|
-
emitDebug("delivery-poll:timeout", { transactionId });
|
|
3581
|
-
return null;
|
|
3582
3981
|
},
|
|
3583
|
-
[deliveryPollInterval, deliveryTimeout, emitDebug,
|
|
3982
|
+
[deliveryPollInterval, deliveryTimeout, emitDebug, flowSession, readPendingRows]
|
|
3584
3983
|
);
|
|
3585
3984
|
const triggerVerification = useCallback(
|
|
3586
|
-
async (record) => {
|
|
3985
|
+
async (record, surfaceFailure = false) => {
|
|
3986
|
+
if (flowSessionRef.current !== flowSession) return;
|
|
3587
3987
|
const verificationKey = getOnRampVerificationKey(record);
|
|
3588
3988
|
if (triggeredVerificationKeysRef.current.has(verificationKey)) {
|
|
3589
3989
|
emitDebug("verification:skip-duplicate", {
|
|
3590
|
-
verificationKey,
|
|
3591
3990
|
record: summariseOnRampRecord(record)
|
|
3592
3991
|
});
|
|
3593
3992
|
return;
|
|
@@ -3601,6 +4000,9 @@ function useFiatOnRamp(options) {
|
|
|
3601
4000
|
triggeredVerificationKeysRef.current.add(verificationKey);
|
|
3602
4001
|
activeVerificationKeyRef.current = verificationKey;
|
|
3603
4002
|
activeVerificationRecordRef.current = record;
|
|
4003
|
+
activeVerificationSurfacesFailureRef.current = surfaceFailure || Boolean(
|
|
4004
|
+
activeIntentIdRef.current && matchesOnRampTransaction(record, activeIntentIdRef.current)
|
|
4005
|
+
);
|
|
3604
4006
|
setActiveVerificationId(record.transaction_id);
|
|
3605
4007
|
setFinalityProgress((prev) => {
|
|
3606
4008
|
if (!(record.transaction_id in prev)) return prev;
|
|
@@ -3609,14 +4011,11 @@ function useFiatOnRamp(options) {
|
|
|
3609
4011
|
return next;
|
|
3610
4012
|
});
|
|
3611
4013
|
emitDebug("verification:start", {
|
|
3612
|
-
verificationKey,
|
|
3613
4014
|
record: summariseOnRampRecord(record)
|
|
3614
4015
|
});
|
|
3615
4016
|
try {
|
|
3616
|
-
if (!record.on_chain_tx_hash
|
|
3617
|
-
|
|
3618
|
-
}
|
|
3619
|
-
if (record.chain_id === void 0 || !record.wallet_address) {
|
|
4017
|
+
if (!record.on_chain_tx_hash) throw new Error("On-ramp record missing on-chain tx hash");
|
|
4018
|
+
if (record.chain_id == null || !record.wallet_address) {
|
|
3620
4019
|
throw new Error("On-ramp record missing chain id or wallet address");
|
|
3621
4020
|
}
|
|
3622
4021
|
const recordTokenId = record.token_id;
|
|
@@ -3635,20 +4034,22 @@ function useFiatOnRamp(options) {
|
|
|
3635
4034
|
chainId: record.chain_id,
|
|
3636
4035
|
walletAddress: record.wallet_address,
|
|
3637
4036
|
token,
|
|
3638
|
-
fallbackAmount: record.quote_currency_amount,
|
|
3639
4037
|
wagmiConfig,
|
|
3640
4038
|
emitDebug
|
|
3641
4039
|
});
|
|
3642
|
-
if (
|
|
4040
|
+
if (flowSessionRef.current !== flowSession) return;
|
|
4041
|
+
const recordMinDepositBaseUnits = erc20MinDepositBaseUnits(
|
|
4042
|
+
scopedMinDepositByChain,
|
|
4043
|
+
record.chain_id
|
|
4044
|
+
);
|
|
4045
|
+
if (recordMinDepositBaseUnits !== void 0 && amount < recordMinDepositBaseUnits) {
|
|
3643
4046
|
emitDebug("verification:below-minimum", {
|
|
3644
|
-
|
|
3645
|
-
minDepositBaseUnits: String(
|
|
4047
|
+
deliveredAmount: amount.toString(),
|
|
4048
|
+
minDepositBaseUnits: String(recordMinDepositBaseUnits)
|
|
3646
4049
|
});
|
|
3647
|
-
throw new Error(
|
|
3648
|
-
`Delivered amount (${record.quote_currency_amount}) is below the minimum deposit.`
|
|
3649
|
-
);
|
|
4050
|
+
throw new Error(`Delivered amount (${amount} base units) is below the minimum deposit.`);
|
|
3650
4051
|
}
|
|
3651
|
-
if (activeIntentIdRef.current
|
|
4052
|
+
if (activeIntentIdRef.current && matchesOnRampTransaction(record, activeIntentIdRef.current)) {
|
|
3652
4053
|
setStatus("verifying");
|
|
3653
4054
|
}
|
|
3654
4055
|
emitDebug("verification:check-deposit-request", {
|
|
@@ -3662,107 +4063,180 @@ function useFiatOnRamp(options) {
|
|
|
3662
4063
|
amount
|
|
3663
4064
|
});
|
|
3664
4065
|
} catch (err) {
|
|
4066
|
+
if (flowSessionRef.current !== flowSession) return;
|
|
3665
4067
|
triggeredVerificationKeysRef.current.delete(verificationKey);
|
|
3666
4068
|
if (activeVerificationKeyRef.current === verificationKey) {
|
|
3667
4069
|
activeVerificationKeyRef.current = null;
|
|
3668
4070
|
activeVerificationRecordRef.current = null;
|
|
4071
|
+
activeVerificationSurfacesFailureRef.current = false;
|
|
3669
4072
|
setActiveVerificationId(null);
|
|
3670
4073
|
}
|
|
3671
4074
|
throw err;
|
|
3672
4075
|
}
|
|
3673
4076
|
},
|
|
3674
|
-
[emitDebug, enabledTokens,
|
|
4077
|
+
[emitDebug, enabledTokens, flowSession, scopedMinDepositByChain, verify, wagmiConfig]
|
|
3675
4078
|
);
|
|
3676
|
-
const
|
|
3677
|
-
async (
|
|
3678
|
-
|
|
4079
|
+
const handleProviderEvent = useCallback(
|
|
4080
|
+
async (event) => {
|
|
4081
|
+
if (flowSessionRef.current !== flowSession) return;
|
|
4082
|
+
let target;
|
|
3679
4083
|
try {
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
4084
|
+
target = resolveOnRampProviderEventTarget(
|
|
4085
|
+
adapter.provider,
|
|
4086
|
+
activeIntentIdRef.current,
|
|
4087
|
+
event
|
|
4088
|
+
);
|
|
4089
|
+
emitDebug(`provider:${event.kind}`, {
|
|
4090
|
+
provider: event.provider,
|
|
4091
|
+
intentIdPresent: Boolean(target.intentId),
|
|
4092
|
+
providerTransactionIdPresent: Boolean(event.providerTransactionId),
|
|
4093
|
+
stale: target.isStale
|
|
4094
|
+
});
|
|
4095
|
+
if (target.isActive) purchaseInitiatedRef.current = true;
|
|
4096
|
+
if (event.kind === "transaction-created") {
|
|
4097
|
+
await registerProviderTransaction(event, target.intentId);
|
|
4098
|
+
if (flowSessionRef.current !== flowSession) return;
|
|
4099
|
+
if (target.isStale) await refreshPending();
|
|
4100
|
+
return;
|
|
4101
|
+
}
|
|
4102
|
+
if (target.isActive || activeIntentIdRef.current === null) {
|
|
4103
|
+
statusRef.current = "awaiting-delivery";
|
|
4104
|
+
setStatus("awaiting-delivery");
|
|
4105
|
+
}
|
|
4106
|
+
await registerProviderTransaction(event, target.intentId);
|
|
4107
|
+
if (flowSessionRef.current !== flowSession) return;
|
|
4108
|
+
if (target.isStale) {
|
|
4109
|
+
await refreshPending();
|
|
4110
|
+
return;
|
|
4111
|
+
}
|
|
4112
|
+
const record = await waitForOnChainHash(target.intentId);
|
|
4113
|
+
if (flowSessionRef.current !== flowSession) return;
|
|
3684
4114
|
if (!record) {
|
|
3685
|
-
const
|
|
4115
|
+
const deliveryError = new Error(
|
|
3686
4116
|
"Backend has not yet confirmed delivery. You can finish from the pending list."
|
|
3687
4117
|
);
|
|
3688
|
-
emitDebug("
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
message: err.message
|
|
4118
|
+
emitDebug("provider:completed-without-backend-row", {
|
|
4119
|
+
provider: event.provider,
|
|
4120
|
+
message: deliveryError.message
|
|
3692
4121
|
});
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
4122
|
+
if (target.isActive || activeIntentIdRef.current === null) {
|
|
4123
|
+
statusRef.current = "failed";
|
|
4124
|
+
setStatus("failed");
|
|
4125
|
+
setError(deliveryError);
|
|
4126
|
+
onErrorRef.current?.(deliveryError);
|
|
4127
|
+
}
|
|
3696
4128
|
return;
|
|
3697
4129
|
}
|
|
3698
|
-
await triggerVerification(record);
|
|
4130
|
+
await triggerVerification(record, true);
|
|
3699
4131
|
} catch (err) {
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
4132
|
+
if (flowSessionRef.current !== flowSession) return;
|
|
4133
|
+
const providerError = err instanceof Error ? err : new Error("Verification failed");
|
|
4134
|
+
emitDebug("provider:event-error", {
|
|
4135
|
+
provider: event.provider,
|
|
4136
|
+
kind: event.kind,
|
|
4137
|
+
...errorPayload(providerError)
|
|
4138
|
+
});
|
|
4139
|
+
if (!target?.isStale) {
|
|
4140
|
+
statusRef.current = "failed";
|
|
4141
|
+
setStatus("failed");
|
|
4142
|
+
setError(providerError);
|
|
4143
|
+
onErrorRef.current?.(providerError);
|
|
4144
|
+
}
|
|
3705
4145
|
}
|
|
3706
4146
|
},
|
|
3707
|
-
[
|
|
4147
|
+
[
|
|
4148
|
+
adapter.provider,
|
|
4149
|
+
emitDebug,
|
|
4150
|
+
flowSession,
|
|
4151
|
+
refreshPending,
|
|
4152
|
+
registerProviderTransaction,
|
|
4153
|
+
triggerVerification,
|
|
4154
|
+
waitForOnChainHash
|
|
4155
|
+
]
|
|
3708
4156
|
);
|
|
3709
|
-
const
|
|
3710
|
-
if (
|
|
3711
|
-
closeReconcilePromiseRef.current
|
|
4157
|
+
const handleProviderClosed = useCallback(async () => {
|
|
4158
|
+
if (flowSessionRef.current !== flowSession) return;
|
|
4159
|
+
const existing = closeReconcilePromiseRef.current;
|
|
4160
|
+
if (existing?.flowSession === flowSession) return existing.promise;
|
|
4161
|
+
const promise = (async () => {
|
|
3712
4162
|
const previousStatus = statusRef.current;
|
|
3713
4163
|
const transactionId = activeIntentIdRef.current;
|
|
3714
|
-
|
|
4164
|
+
const action = getOnRampCloseRecoveryAction(transactionId, purchaseInitiatedRef.current);
|
|
4165
|
+
emitDebug("provider:closed-reconcile", {
|
|
4166
|
+
provider: adapter.provider,
|
|
3715
4167
|
previousStatus,
|
|
3716
|
-
transactionId
|
|
4168
|
+
intentPresent: Boolean(transactionId),
|
|
4169
|
+
action
|
|
3717
4170
|
});
|
|
3718
|
-
if (
|
|
4171
|
+
if (action === "refresh") {
|
|
3719
4172
|
await refreshPending();
|
|
3720
4173
|
return;
|
|
3721
4174
|
}
|
|
3722
|
-
if (
|
|
3723
|
-
emitDebug("
|
|
4175
|
+
if (action === "refresh-and-retain") {
|
|
4176
|
+
emitDebug("provider:closed-without-event", {
|
|
4177
|
+
provider: adapter.provider,
|
|
3724
4178
|
previousStatus,
|
|
3725
|
-
|
|
4179
|
+
recoveryRetained: true
|
|
3726
4180
|
});
|
|
3727
|
-
if (address) clearPendingLock(address, transactionId);
|
|
3728
4181
|
await refreshPending();
|
|
4182
|
+
if (flowSessionRef.current !== flowSession) return;
|
|
3729
4183
|
if (previousStatus === "awaiting-purchase" || previousStatus === "awaiting-delivery") {
|
|
4184
|
+
statusRef.current = "idle";
|
|
3730
4185
|
setStatus("idle");
|
|
3731
4186
|
}
|
|
3732
4187
|
return;
|
|
3733
4188
|
}
|
|
3734
4189
|
try {
|
|
4190
|
+
statusRef.current = "awaiting-delivery";
|
|
3735
4191
|
setStatus("awaiting-delivery");
|
|
3736
4192
|
const record = await waitForOnChainHash(transactionId);
|
|
4193
|
+
if (flowSessionRef.current !== flowSession) return;
|
|
3737
4194
|
if (record) {
|
|
3738
|
-
await triggerVerification(record);
|
|
4195
|
+
await triggerVerification(record, true);
|
|
3739
4196
|
return;
|
|
3740
4197
|
}
|
|
3741
4198
|
await refreshPending();
|
|
4199
|
+
if (flowSessionRef.current !== flowSession) return;
|
|
3742
4200
|
if (previousStatus === "awaiting-purchase" || previousStatus === "awaiting-delivery") {
|
|
4201
|
+
statusRef.current = "idle";
|
|
3743
4202
|
setStatus("idle");
|
|
3744
4203
|
}
|
|
3745
4204
|
} catch (err) {
|
|
4205
|
+
if (flowSessionRef.current !== flowSession) return;
|
|
3746
4206
|
const e = err instanceof Error ? err : new Error("On-ramp reconciliation failed");
|
|
3747
|
-
emitDebug("
|
|
4207
|
+
emitDebug("provider:closed-reconcile-error", {
|
|
4208
|
+
provider: adapter.provider,
|
|
4209
|
+
...errorPayload(e)
|
|
4210
|
+
});
|
|
3748
4211
|
setStatus("failed");
|
|
3749
4212
|
setError(e);
|
|
3750
4213
|
onErrorRef.current?.(e);
|
|
3751
4214
|
}
|
|
3752
4215
|
})();
|
|
4216
|
+
const entry = { flowSession, promise };
|
|
4217
|
+
closeReconcilePromiseRef.current = entry;
|
|
3753
4218
|
try {
|
|
3754
|
-
await
|
|
4219
|
+
await promise;
|
|
3755
4220
|
} finally {
|
|
3756
|
-
closeReconcilePromiseRef.current
|
|
4221
|
+
if (closeReconcilePromiseRef.current === entry) {
|
|
4222
|
+
closeReconcilePromiseRef.current = null;
|
|
4223
|
+
}
|
|
3757
4224
|
}
|
|
3758
|
-
}, [
|
|
4225
|
+
}, [
|
|
4226
|
+
adapter.provider,
|
|
4227
|
+
emitDebug,
|
|
4228
|
+
flowSession,
|
|
4229
|
+
refreshPending,
|
|
4230
|
+
triggerVerification,
|
|
4231
|
+
waitForOnChainHash
|
|
4232
|
+
]);
|
|
3759
4233
|
const finishPendingVerification = useCallback(
|
|
3760
4234
|
async (record) => {
|
|
3761
4235
|
try {
|
|
3762
4236
|
emitDebug("pending:finish-verification", {
|
|
3763
4237
|
record: summariseOnRampRecord(record)
|
|
3764
4238
|
});
|
|
3765
|
-
await triggerVerification(record);
|
|
4239
|
+
await triggerVerification(record, true);
|
|
3766
4240
|
} catch (err) {
|
|
3767
4241
|
const e = err instanceof Error ? err : new Error("Verification failed");
|
|
3768
4242
|
emitDebug("pending:finish-verification-error", errorPayload(e));
|
|
@@ -3781,22 +4255,17 @@ function useFiatOnRamp(options) {
|
|
|
3781
4255
|
useEffect(() => {
|
|
3782
4256
|
let cancelled = false;
|
|
3783
4257
|
void (async () => {
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
}
|
|
3794
|
-
|
|
3795
|
-
await new Promise((resolve) => {
|
|
3796
|
-
activeVerificationDoneRef.current = resolve;
|
|
3797
|
-
});
|
|
3798
|
-
}
|
|
3799
|
-
}
|
|
4258
|
+
await verifyPendingOnRampsSequentially({
|
|
4259
|
+
records: pending,
|
|
4260
|
+
shouldStop: () => cancelled,
|
|
4261
|
+
wasTriggered: (key) => triggeredVerificationKeysRef.current.has(key),
|
|
4262
|
+
trigger: (record) => triggerVerificationRef.current(record),
|
|
4263
|
+
// verify() resolves after scheduling status polling. Waiting for its
|
|
4264
|
+
// terminal callback prevents the next record from cancelling it.
|
|
4265
|
+
waitForTerminal: (key) => activeVerificationKeyRef.current === key ? new Promise((resolve) => {
|
|
4266
|
+
activeVerificationDoneRef.current = resolve;
|
|
4267
|
+
}) : Promise.resolve()
|
|
4268
|
+
});
|
|
3800
4269
|
})();
|
|
3801
4270
|
return () => {
|
|
3802
4271
|
cancelled = true;
|
|
@@ -3809,15 +4278,15 @@ function useFiatOnRamp(options) {
|
|
|
3809
4278
|
activeVerificationId,
|
|
3810
4279
|
error,
|
|
3811
4280
|
finalityProgress,
|
|
3812
|
-
depositAddress,
|
|
3813
|
-
minDepositBaseUnits,
|
|
4281
|
+
depositAddress: scopedDepositAddress,
|
|
4282
|
+
minDepositBaseUnits: scopedMinDepositBaseUnits,
|
|
3814
4283
|
selectedToken,
|
|
3815
4284
|
prepareOnRampIntent,
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
4285
|
+
handleProviderLaunchReady,
|
|
4286
|
+
handleProviderLaunchFailed,
|
|
4287
|
+
handleProviderEvent,
|
|
3819
4288
|
finishPendingVerification,
|
|
3820
|
-
|
|
4289
|
+
handleProviderClosed,
|
|
3821
4290
|
refreshPending
|
|
3822
4291
|
};
|
|
3823
4292
|
}
|
|
@@ -3831,17 +4300,20 @@ function summariseToken(token) {
|
|
|
3831
4300
|
}
|
|
3832
4301
|
function summariseOnRampRecord(record) {
|
|
3833
4302
|
return {
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
moonpay_transaction_id: record.moonpay_transaction_id ?? null,
|
|
4303
|
+
provider: record.provider,
|
|
4304
|
+
provider_asset_code: record.provider_asset_code,
|
|
3837
4305
|
status: record.status,
|
|
3838
|
-
wallet_address: record.wallet_address,
|
|
3839
4306
|
token_id: record.token_id,
|
|
3840
4307
|
chain_id: record.chain_id,
|
|
3841
4308
|
moonpay_currency_code: record.moonpay_currency_code ?? null,
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
4309
|
+
external_transaction_id_present: Boolean(record.external_transaction_id),
|
|
4310
|
+
provider_transaction_id_present: Boolean(record.provider_transaction_id),
|
|
4311
|
+
moonpay_transaction_id_present: Boolean(record.moonpay_transaction_id),
|
|
4312
|
+
wallet_address_present: Boolean(record.wallet_address),
|
|
4313
|
+
quote_currency_amount_present: record.quote_currency_amount != null,
|
|
4314
|
+
on_chain_tx_hash_present: Boolean(record.on_chain_tx_hash),
|
|
4315
|
+
deposit_id_present: Boolean(record.deposit_id),
|
|
4316
|
+
deposit_tx_hash_present: Boolean(record.deposit_tx_hash),
|
|
3845
4317
|
deposit_triggered_at: record.deposit_triggered_at ?? null,
|
|
3846
4318
|
credited_at: record.credited_at ?? null
|
|
3847
4319
|
};
|
|
@@ -3851,13 +4323,10 @@ async function resolveDeliveredAmount({
|
|
|
3851
4323
|
chainId,
|
|
3852
4324
|
walletAddress,
|
|
3853
4325
|
token,
|
|
3854
|
-
fallbackAmount,
|
|
3855
4326
|
wagmiConfig,
|
|
3856
4327
|
emitDebug
|
|
3857
4328
|
}) {
|
|
3858
|
-
|
|
3859
|
-
return parseUnits(fallbackAmount, token.decimals);
|
|
3860
|
-
}
|
|
4329
|
+
assertErc20OnRampToken(token.contract);
|
|
3861
4330
|
let receiptError;
|
|
3862
4331
|
try {
|
|
3863
4332
|
const receipt = await waitForTransactionReceipt(wagmiConfig, {
|
|
@@ -3866,63 +4335,142 @@ async function resolveDeliveredAmount({
|
|
|
3866
4335
|
timeout: 6e4,
|
|
3867
4336
|
pollingInterval: 4e3
|
|
3868
4337
|
});
|
|
3869
|
-
|
|
3870
|
-
for (const log of receipt.logs) {
|
|
3871
|
-
if (log.address.toLowerCase() !== token.contract.toLowerCase()) continue;
|
|
3872
|
-
try {
|
|
3873
|
-
const decoded = decodeEventLog({
|
|
3874
|
-
abi: [ERC20_TRANSFER_EVENT],
|
|
3875
|
-
data: log.data,
|
|
3876
|
-
topics: log.topics
|
|
3877
|
-
});
|
|
3878
|
-
if (decoded.eventName !== "Transfer") continue;
|
|
3879
|
-
const to = decoded.args.to.toLowerCase();
|
|
3880
|
-
if (to !== walletAddress.toLowerCase()) continue;
|
|
3881
|
-
delivered += decoded.args.value;
|
|
3882
|
-
} catch {
|
|
3883
|
-
}
|
|
3884
|
-
}
|
|
4338
|
+
const delivered = deliveredErc20Amount(receipt.logs, token.contract, walletAddress);
|
|
3885
4339
|
if (delivered > 0n) {
|
|
3886
4340
|
emitDebug("verification:amount-from-receipt", {
|
|
3887
4341
|
amount: delivered.toString(),
|
|
3888
4342
|
tokenAddress: token.contract,
|
|
3889
|
-
|
|
3890
|
-
moonpayQuoteCurrencyAmount: fallbackAmount
|
|
4343
|
+
depositAddressMatched: true
|
|
3891
4344
|
});
|
|
3892
4345
|
return delivered;
|
|
3893
4346
|
}
|
|
3894
4347
|
emitDebug("verification:amount-from-receipt-missing", {
|
|
3895
4348
|
tokenAddress: token.contract,
|
|
3896
|
-
|
|
3897
|
-
moonpayQuoteCurrencyAmount: fallbackAmount
|
|
4349
|
+
depositAddressMatched: false
|
|
3898
4350
|
});
|
|
3899
4351
|
} catch (err) {
|
|
3900
4352
|
emitDebug("verification:amount-from-receipt-error", errorPayload(err));
|
|
3901
4353
|
receiptError = err;
|
|
3902
4354
|
}
|
|
3903
|
-
const errorDetail = receiptError instanceof Error ? receiptError.message : receiptError === void 0 ? `no ${token.symbol} Transfer to
|
|
4355
|
+
const errorDetail = receiptError instanceof Error ? receiptError.message : receiptError === void 0 ? `no ${token.symbol} Transfer to the derived deposit address found` : String(receiptError);
|
|
3904
4356
|
throw new Error(
|
|
3905
|
-
`Unable to derive delivered ${token.symbol} amount from
|
|
4357
|
+
`Unable to derive delivered ${token.symbol} amount from its receipt: ${errorDetail}`
|
|
3906
4358
|
);
|
|
3907
4359
|
}
|
|
3908
|
-
function
|
|
3909
|
-
|
|
4360
|
+
function errorPayload(err) {
|
|
4361
|
+
if (err instanceof Error) {
|
|
4362
|
+
return {
|
|
4363
|
+
name: err.name,
|
|
4364
|
+
message: err.message,
|
|
4365
|
+
stack: err.stack?.split("\n").slice(0, 4).join("\n")
|
|
4366
|
+
};
|
|
4367
|
+
}
|
|
4368
|
+
return { message: String(err) };
|
|
3910
4369
|
}
|
|
3911
|
-
|
|
3912
|
-
|
|
4370
|
+
|
|
4371
|
+
// src/sdk/hooks/use-fiat-on-ramp.ts
|
|
4372
|
+
function useFiatOnRamp(options) {
|
|
4373
|
+
const { executePrivateRead } = usePrivateReadRequest();
|
|
4374
|
+
const core = useOnRamp({ ...options, adapter: moonPayOnRampAdapter });
|
|
4375
|
+
const {
|
|
4376
|
+
prepareOnRampIntent: prepareProviderIntent,
|
|
4377
|
+
handleProviderLaunchReady,
|
|
4378
|
+
handleProviderLaunchFailed,
|
|
4379
|
+
handleProviderEvent,
|
|
4380
|
+
handleProviderClosed
|
|
4381
|
+
} = core;
|
|
4382
|
+
const debugRef = useRef(options.onDebugEvent);
|
|
4383
|
+
const statusRef = useRef(core.status);
|
|
4384
|
+
useEffect(() => {
|
|
4385
|
+
debugRef.current = options.onDebugEvent;
|
|
4386
|
+
statusRef.current = core.status;
|
|
4387
|
+
}, [core.status, options.onDebugEvent]);
|
|
4388
|
+
const emitDebug = useCallback(
|
|
4389
|
+
(event, payload) => {
|
|
4390
|
+
debugRef.current?.({
|
|
4391
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4392
|
+
event,
|
|
4393
|
+
status: statusRef.current,
|
|
4394
|
+
tokenId: options.tokenId,
|
|
4395
|
+
payload
|
|
4396
|
+
});
|
|
4397
|
+
},
|
|
4398
|
+
[options.tokenId]
|
|
4399
|
+
);
|
|
4400
|
+
const prepareOnRampIntent = useCallback(
|
|
4401
|
+
({
|
|
4402
|
+
currencyCode,
|
|
4403
|
+
baseCurrencyCode,
|
|
4404
|
+
baseCurrencyAmount,
|
|
4405
|
+
quoteCurrencyAmount
|
|
4406
|
+
}) => prepareProviderIntent({
|
|
4407
|
+
providerAssetCode: currencyCode,
|
|
4408
|
+
baseCurrencyCode,
|
|
4409
|
+
baseCurrencyAmount,
|
|
4410
|
+
quoteCurrencyAmount
|
|
4411
|
+
}),
|
|
4412
|
+
[prepareProviderIntent]
|
|
4413
|
+
);
|
|
4414
|
+
const signUrl = useCallback(
|
|
4415
|
+
async (url) => {
|
|
4416
|
+
emitDebug("moonpay:onUrlSignatureRequested", summariseMoonPayUrl(url));
|
|
4417
|
+
try {
|
|
4418
|
+
const { signature } = await executePrivateRead(
|
|
4419
|
+
(readClient) => readClient.signOnRampUrl({ url })
|
|
4420
|
+
);
|
|
4421
|
+
handleProviderLaunchReady();
|
|
4422
|
+
emitDebug("sign-url:success", { signatureLength: signature.length });
|
|
4423
|
+
return signature;
|
|
4424
|
+
} catch (error) {
|
|
4425
|
+
const launchError = error instanceof Error ? error : new Error("Failed to sign on-ramp URL");
|
|
4426
|
+
handleProviderLaunchFailed(launchError);
|
|
4427
|
+
emitDebug("sign-url:error", errorPayload2(launchError));
|
|
4428
|
+
throw launchError;
|
|
4429
|
+
}
|
|
4430
|
+
},
|
|
4431
|
+
[emitDebug, executePrivateRead, handleProviderLaunchFailed, handleProviderLaunchReady]
|
|
4432
|
+
);
|
|
4433
|
+
const handleTransactionCreated = useCallback(
|
|
4434
|
+
async (props) => {
|
|
4435
|
+
emitDebug("moonpay:onTransactionCreated", summariseMoonPayEventProps(props));
|
|
4436
|
+
await handleProviderEvent(normalizeMoonPayProviderEvent("transaction-created", props));
|
|
4437
|
+
},
|
|
4438
|
+
[emitDebug, handleProviderEvent]
|
|
4439
|
+
);
|
|
4440
|
+
const handleTransactionCompleted = useCallback(
|
|
4441
|
+
async (props) => {
|
|
4442
|
+
emitDebug("moonpay:onTransactionCompleted", summariseMoonPayEventProps(props));
|
|
4443
|
+
await handleProviderEvent(normalizeMoonPayProviderEvent("transaction-completed", props));
|
|
4444
|
+
},
|
|
4445
|
+
[emitDebug, handleProviderEvent]
|
|
4446
|
+
);
|
|
4447
|
+
return {
|
|
4448
|
+
status: core.status,
|
|
4449
|
+
activeIntentId: core.activeIntentId,
|
|
4450
|
+
pending: core.pending,
|
|
4451
|
+
activeVerificationId: core.activeVerificationId,
|
|
4452
|
+
error: core.error,
|
|
4453
|
+
finalityProgress: core.finalityProgress,
|
|
4454
|
+
depositAddress: core.depositAddress,
|
|
4455
|
+
minDepositBaseUnits: core.minDepositBaseUnits,
|
|
4456
|
+
selectedToken: core.selectedToken,
|
|
4457
|
+
finishPendingVerification: core.finishPendingVerification,
|
|
4458
|
+
refreshPending: core.refreshPending,
|
|
4459
|
+
prepareOnRampIntent,
|
|
4460
|
+
signUrl,
|
|
4461
|
+
handleTransactionCreated,
|
|
4462
|
+
handleTransactionCompleted,
|
|
4463
|
+
handleWidgetClosed: handleProviderClosed
|
|
4464
|
+
};
|
|
3913
4465
|
}
|
|
3914
4466
|
function summariseMoonPayEventProps(props) {
|
|
3915
4467
|
return {
|
|
3916
|
-
|
|
3917
|
-
|
|
4468
|
+
transactionIdPresent: Boolean(props.id),
|
|
4469
|
+
externalTransactionIdPresent: Boolean(props.externalTransactionId),
|
|
3918
4470
|
status: props.status,
|
|
3919
|
-
walletAddress: props.walletAddress,
|
|
3920
|
-
walletAddressTag: props.walletAddressTag,
|
|
3921
|
-
baseCurrencyAmount: props.baseCurrencyAmount,
|
|
3922
|
-
quoteCurrencyAmount: props.quoteCurrencyAmount,
|
|
3923
4471
|
baseCurrency: props.baseCurrency,
|
|
3924
4472
|
quoteCurrency: props.quoteCurrency,
|
|
3925
|
-
|
|
4473
|
+
walletAddressPresent: Boolean(props.walletAddress)
|
|
3926
4474
|
};
|
|
3927
4475
|
}
|
|
3928
4476
|
function summariseMoonPayUrl(url) {
|
|
@@ -3932,36 +4480,34 @@ function summariseMoonPayUrl(url) {
|
|
|
3932
4480
|
return {
|
|
3933
4481
|
origin: parsed.origin,
|
|
3934
4482
|
pathname: parsed.pathname,
|
|
3935
|
-
|
|
4483
|
+
apiKeyPresent: params.has("apiKey"),
|
|
3936
4484
|
currencyCode: params.get("currencyCode"),
|
|
3937
4485
|
baseCurrencyCode: params.get("baseCurrencyCode"),
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
4486
|
+
baseCurrencyAmountPresent: params.has("baseCurrencyAmount"),
|
|
4487
|
+
walletAddressPresent: params.has("walletAddress"),
|
|
4488
|
+
externalCustomerIdPresent: params.has("externalCustomerId"),
|
|
4489
|
+
externalTransactionIdPresent: params.has("externalTransactionId"),
|
|
4490
|
+
redirectURLPresent: params.has("redirectURL"),
|
|
3943
4491
|
signaturePresent: params.has("signature")
|
|
3944
4492
|
};
|
|
3945
4493
|
} catch {
|
|
3946
4494
|
return { parseError: true, length: url.length };
|
|
3947
4495
|
}
|
|
3948
4496
|
}
|
|
3949
|
-
function
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
};
|
|
3956
|
-
}
|
|
3957
|
-
return { message: String(err) };
|
|
4497
|
+
function errorPayload2(error) {
|
|
4498
|
+
return {
|
|
4499
|
+
name: error.name,
|
|
4500
|
+
message: error.message,
|
|
4501
|
+
stack: error.stack?.split("\n").slice(0, 4).join("\n")
|
|
4502
|
+
};
|
|
3958
4503
|
}
|
|
3959
|
-
function
|
|
4504
|
+
function useMoonPayOnRampAdapter({
|
|
3960
4505
|
variant,
|
|
3961
4506
|
visible,
|
|
3962
4507
|
autoStart,
|
|
3963
4508
|
canBuy,
|
|
3964
4509
|
openWidget,
|
|
4510
|
+
shouldPollPending,
|
|
3965
4511
|
refreshPending,
|
|
3966
4512
|
theme,
|
|
3967
4513
|
themeId,
|
|
@@ -3989,10 +4535,12 @@ function useMoonPayBuyWidget({
|
|
|
3989
4535
|
void openWidget();
|
|
3990
4536
|
}, [autoStart, canBuy, openWidget]);
|
|
3991
4537
|
useEffect(() => {
|
|
3992
|
-
if (variant !== "embedded" || !visible)
|
|
3993
|
-
|
|
4538
|
+
if (!moonPayOnRampAdapter.pollPendingWhileOpen || variant !== "embedded" || !visible || !shouldPollPending) {
|
|
4539
|
+
return;
|
|
4540
|
+
}
|
|
4541
|
+
const id = setInterval(() => void refreshPending(), MIN_ONRAMP_PENDING_REQUEST_INTERVAL_MS);
|
|
3994
4542
|
return () => clearInterval(id);
|
|
3995
|
-
}, [variant, visible, refreshPending]);
|
|
4543
|
+
}, [variant, visible, shouldPollPending, refreshPending]);
|
|
3996
4544
|
const callbacksRef = useRef({
|
|
3997
4545
|
onClose,
|
|
3998
4546
|
onCloseOverlay,
|
|
@@ -4180,9 +4728,9 @@ function FiatOnRampForm({
|
|
|
4180
4728
|
tokenSymbol: displaySymbol,
|
|
4181
4729
|
tokenDecimals: decimals ?? null,
|
|
4182
4730
|
baseCurrencyCode,
|
|
4183
|
-
defaultBaseCurrencyAmount,
|
|
4184
|
-
|
|
4185
|
-
|
|
4731
|
+
baseCurrencyAmountPresent: Boolean(defaultBaseCurrencyAmount),
|
|
4732
|
+
depositAddressReady: Boolean(depositAddress),
|
|
4733
|
+
walletConnected: Boolean(address),
|
|
4186
4734
|
status
|
|
4187
4735
|
});
|
|
4188
4736
|
return;
|
|
@@ -4195,8 +4743,8 @@ function FiatOnRampForm({
|
|
|
4195
4743
|
tokenSymbol: displaySymbol,
|
|
4196
4744
|
tokenDecimals: decimals ?? null,
|
|
4197
4745
|
baseCurrencyCode,
|
|
4198
|
-
defaultBaseCurrencyAmount,
|
|
4199
|
-
|
|
4746
|
+
baseCurrencyAmountPresent: Boolean(defaultBaseCurrencyAmount),
|
|
4747
|
+
depositAddressReady: Boolean(depositAddress),
|
|
4200
4748
|
walletConnected: Boolean(address)
|
|
4201
4749
|
});
|
|
4202
4750
|
try {
|
|
@@ -4207,8 +4755,8 @@ function FiatOnRampForm({
|
|
|
4207
4755
|
quoteCurrencyAmount
|
|
4208
4756
|
});
|
|
4209
4757
|
emitFormDebug("form:intent-ready", {
|
|
4210
|
-
|
|
4211
|
-
|
|
4758
|
+
transactionIdPresent: Boolean(intent.transaction_id),
|
|
4759
|
+
externalTransactionIdPresent: Boolean(intent.external_transaction_id)
|
|
4212
4760
|
});
|
|
4213
4761
|
setVisible(true);
|
|
4214
4762
|
} catch (err) {
|
|
@@ -4248,12 +4796,13 @@ function FiatOnRampForm({
|
|
|
4248
4796
|
const handleReady = useCallback(async () => {
|
|
4249
4797
|
emitFormDebug("moonpay:onReady");
|
|
4250
4798
|
}, [emitFormDebug]);
|
|
4251
|
-
const widgetElement =
|
|
4799
|
+
const widgetElement = useMoonPayOnRampAdapter({
|
|
4252
4800
|
variant,
|
|
4253
4801
|
visible,
|
|
4254
4802
|
autoStart,
|
|
4255
4803
|
canBuy,
|
|
4256
4804
|
openWidget: handleOpen,
|
|
4805
|
+
shouldPollPending: status === "awaiting-purchase",
|
|
4257
4806
|
refreshPending,
|
|
4258
4807
|
theme,
|
|
4259
4808
|
themeId,
|
|
@@ -4274,6 +4823,9 @@ function FiatOnRampForm({
|
|
|
4274
4823
|
onTransactionCreated: handleTransactionCreated,
|
|
4275
4824
|
onTransactionCompleted: handleTransactionCompleted
|
|
4276
4825
|
});
|
|
4826
|
+
useEffect(() => {
|
|
4827
|
+
if (visible && !activeIntentId && status === "idle") setVisible(false);
|
|
4828
|
+
}, [activeIntentId, status, visible]);
|
|
4277
4829
|
useEffect(() => {
|
|
4278
4830
|
if (variant !== "embedded" || !visible) return;
|
|
4279
4831
|
if (isVerifying || status === "credited") {
|
|
@@ -4374,5 +4926,5 @@ function parseFinalityProgress(message) {
|
|
|
4374
4926
|
}
|
|
4375
4927
|
|
|
4376
4928
|
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, canUseBrowserStorage, canUseSharedBrowserStorage, clampLockAmount, clearHostedAuthPendingTransaction, clearPendingLock, cn, createDomain, createHostedAuthPendingStorageKey, createHostedAuthState, createHostedAuthStorageKey, createLockExpiry, createPkceChallenge, createPkceVerifier, createSignedLockRequest, formatCountdown, formatTimeRemaining, formatTokenAmount, getAccountingContract, getApiUrl, getBlockNumber, getBrowserStorageItem, getChainById, getChainId, getExplorerAddressUrl, getExplorerLabel, getSharedBrowserStorageItem, getTransactionReceipt, getWalletClient3 as getWalletClient, isHostedAuthRefreshActive, isHostedAuthSessionActive, isSignedLockUsable, loadPendingLock, normalizeAddress, normalizeHex, parseHostedAuthCallback, parseTokenAmount, persistHostedAuthPendingTransaction, readHostedAuthPendingTransaction, readStoredHostedAuthSession, removeBrowserStorageItem, removeSharedBrowserStorageItem, requireDepositLockOwner, requireServiceAddress, savePendingLock, setBrowserStorageItem, setSharedBrowserStorageItem, shortenAddress, signLockMessage, signModifyLockMessage, signTransferLockedMessage, signTransferMessage, signWithdrawFromLockMessage, signWithdrawMessage, stripHostedAuthCallbackParams, submitPendingLock, syncHostedAuthSessionToClient, useDepositVerification, useEnsureCorrectChain, useFiatOnRamp, usePrivanaContext, usePrivateReadRequest, useSafeAccount, useSafePrivanaContext, useSiweAuth, waitForTransactionReceipt };
|
|
4377
|
-
//# sourceMappingURL=chunk-
|
|
4378
|
-
//# sourceMappingURL=chunk-
|
|
4929
|
+
//# sourceMappingURL=chunk-PMBU2F2R.js.map
|
|
4930
|
+
//# sourceMappingURL=chunk-PMBU2F2R.js.map
|