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