@reservoir0x/relay-kit-ui
Version:
Relay is the Fastest and Cheapest Way to Bridge and Transact Across Chains.
281 lines • 14.8 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { getDeadAddress, LogLevel } from '@reservoir0x/relay-sdk';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { Modal } from '../../../common/Modal.js';
import useRelayClient from '../../../../hooks/useRelayClient.js';
import { EventNames } from '../../../../constants/events.js';
import { useExecutionStatus, useQuote, useRequests, useTokenPrice } from '@reservoir0x/relay-kit-hooks';
import { extractDepositRequestId } from '../../../../utils/relayTransaction.js';
import { parseUnits, zeroAddress } from 'viem';
import { appendMetadataToRequest, extractDepositAddress } from '../../../../utils/quote.js';
import { getTxBlockExplorerUrl } from '../../../../utils/getTxBlockExplorerUrl.js';
import { OnrampConfirmingStep } from './steps/OnrampConfirmingStep.js';
import { OnrampProcessingStepUI } from './steps/OnrampProcessingStepUI.js';
import { OnrampProcessingPassthroughStep } from './steps/OnrampProcessingPassthroughStep.js';
import { OnrampSuccessStep } from './steps/OnrampSuccessStep.js';
import { OnrampMoonPayStep } from './steps/OnrampMoonPayStep.js';
import { formatBN } from '../../../../utils/numbers.js';
import { ErrorStep } from '../../../common/TransactionModal/steps/ErrorStep.js';
import { errorToJSON } from '../../../../utils/errors.js';
import { useAccount } from 'wagmi';
import { mainnet } from 'viem/chains';
export var OnrampStep;
(function (OnrampStep) {
OnrampStep[OnrampStep["Confirming"] = 0] = "Confirming";
OnrampStep[OnrampStep["Moonpay"] = 1] = "Moonpay";
OnrampStep[OnrampStep["Processing"] = 2] = "Processing";
OnrampStep[OnrampStep["ProcessingPassthrough"] = 3] = "ProcessingPassthrough";
OnrampStep[OnrampStep["Success"] = 4] = "Success";
OnrampStep[OnrampStep["Error"] = 5] = "Error";
})(OnrampStep || (OnrampStep = {}));
export var OnrampProcessingStep;
(function (OnrampProcessingStep) {
OnrampProcessingStep[OnrampProcessingStep["Finalizing"] = 0] = "Finalizing";
OnrampProcessingStep[OnrampProcessingStep["Relaying"] = 1] = "Relaying";
})(OnrampProcessingStep || (OnrampProcessingStep = {}));
export const OnrampModal = ({ open, amount, amountFormatted, recipient, fromToken, toToken, toChain, fromChain, fiatCurrency, isPassthrough, amountToTokenFormatted, moonPayCurrencyCode, usdRate, moonPayThemeId, moonPayThemeMode, moonPayApiKey, moonpayOnUrlSignatureRequested, onAnalyticEvent, onSuccess, onError, onOpenChange }) => {
const [swapError, setSwapError] = useState(null);
const [step, setStep] = useState(OnrampStep.Confirming);
const [processingStep, setProcessingStep] = useState();
const [moonPayRequestId, setMoonPayRequestId] = useState();
const client = useRelayClient();
const { connector } = useAccount();
const [moonPayIdAppended, setMoonPayIdAppended] = useState(false);
const { data: ethTokenPriceResponse } = useTokenPrice(client?.baseApiUrl, {
address: zeroAddress,
chainId: mainnet.id,
referrer: client?.source
}, {
refetchInterval: 60000 * 5, //5 minutes
refetchOnWindowFocus: false
});
const [passthroughExternalId, setPassthroughExternalId] = useState();
useEffect(() => {
if (!open) {
setStep(OnrampStep.Confirming);
setProcessingStep(undefined);
setMoonPayRequestId(undefined);
setMoonPayIdAppended(false);
setSwapError(null);
setPassthroughExternalId(undefined);
}
else {
if (isPassthrough) {
setStep(OnrampStep.Moonpay);
setPassthroughExternalId(`${recipient ? recipient.slice(recipient.length - 5) : ''}${Date.now()}`);
}
onAnalyticEvent?.(EventNames.ONRAMP_MODAL_OPEN, {
isPassthrough
});
}
}, [open]);
const ethAmount = ethTokenPriceResponse && ethTokenPriceResponse.price
? +(amount ?? '0') / ethTokenPriceResponse.price
: 0;
const { data: quote, error: quoteError, isFetching: isFetchingQuote } = useQuote(client ?? undefined, undefined, {
originChainId: fromToken.chainId,
originCurrency: fromToken.address,
destinationChainId: toToken.chainId,
destinationCurrency: toToken.address,
useDepositAddress: true,
tradeType: 'EXACT_INPUT',
amount: fromToken.symbol === 'USDC'
? parseUnits(`${amount}`, 6).toString()
: parseUnits(`${ethAmount}`, fromToken.decimals).toString(),
user: getDeadAddress(),
recipient
}, undefined, undefined, {
refetchOnMount: false,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
enabled: Boolean(amount &&
amount.length > 0 &&
toToken &&
fromToken &&
!isPassthrough &&
recipient !== undefined &&
open)
});
const totalAmount = quote?.fees && amount && !isPassthrough
? `${Math.floor((Number(quote.fees.relayer?.amountUsd ?? 0) +
Number(quote.fees.gas?.amountUsd ?? 0) +
Number(quote.fees.app?.amountUsd ?? 0) +
+amount) *
100) / 100}`
: amount;
const ethTotalAmount = formatBN(totalAmount ? +totalAmount / (ethTokenPriceResponse?.price ?? 0) : 0, 5, 18);
const toTokenTotalAmount = formatBN(+(totalAmount ?? '0') / (usdRate ?? 0), 5, toToken.decimals);
const depositAddress = useMemo(() => extractDepositAddress(quote?.steps), [quote]);
const baseTransactionUrl = client?.baseApiUrl.includes('testnets')
? 'https://testnets.relay.link'
: 'https://relay.link';
const requestId = useMemo(() => extractDepositRequestId(quote?.steps), [quote]);
const { data: executionStatus } = useExecutionStatus(client ? client : undefined, {
requestId: requestId ?? undefined,
referrer: client?.source
}, undefined, undefined, {
enabled: requestId !== null && step === OnrampStep.Processing && open,
refetchInterval(query) {
const observableStates = ['waiting', 'pending', 'delayed'];
if (!query.state.data?.status ||
(requestId && observableStates.includes(query.state.data?.status))) {
return 1000;
}
return 0;
}
});
const fillTxHash = useMemo(() => {
if (executionStatus?.txHashes && executionStatus?.txHashes[0]) {
return executionStatus?.txHashes[0];
}
}, [executionStatus]);
const { data: transactions, isLoading: isLoadingTransaction } = useRequests(step === OnrampStep.Success && fillTxHash
? {
user: recipient,
hash: fillTxHash
}
: undefined, client?.baseApiUrl, {
enabled: Boolean(step === OnrampStep.Success && fillTxHash),
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchInterval: false,
retryOnMount: false
});
const transaction = transactions && transactions[0] ? transactions[0] : null;
const toAmountFormatted = transaction?.data?.metadata?.currencyOut
?.amountFormatted
? (formatBN(+transaction.data.metadata.currencyOut.amountFormatted, 5, transaction?.data?.metadata?.currencyOut?.currency?.decimals ?? 18) ?? undefined)
: amountToTokenFormatted;
const fillTxUrl = fillTxHash
? getTxBlockExplorerUrl(toToken.chainId, client?.chains, fillTxHash)
: undefined;
const moonpayTxUrl = moonPayRequestId
? `https://buy.moonpay.com/transaction_receipt?transactionId=${moonPayRequestId}`
: null;
useEffect(() => {
if (executionStatus?.status === 'pending' &&
(step !== OnrampStep.Processing ||
processingStep !== OnrampProcessingStep.Relaying)) {
setStep(OnrampStep.Processing);
setProcessingStep(OnrampProcessingStep.Relaying);
}
if (executionStatus?.status === 'success') {
setStep(OnrampStep.Success);
onAnalyticEvent?.(EventNames.ONRAMP_SUCCESS, {
chain_id_in: fromToken?.chainId,
currency_in: fromToken?.symbol,
chain_id_out: toToken?.chainId,
currency_out: toToken?.symbol,
quote_id: requestId,
txHashes: executionStatus.txHashes,
amount,
totalAmount,
ethTotalAmount,
moonPayRequestId,
isPassthrough
});
onSuccess?.(quote, moonPayRequestId);
setProcessingStep(undefined);
}
if (executionStatus?.status === 'failure' ||
executionStatus?.status === 'refund' ||
quoteError) {
const swapError = new Error(executionStatus?.details ??
'Oops! Something went wrong while processing your transaction.');
onOnrampError(errorToJSON(executionStatus?.details ?? quoteError), swapError);
}
}, [executionStatus, quoteError]);
useEffect(() => {
if (moonPayRequestId &&
quote &&
executionStatus?.inTxHashes &&
executionStatus?.inTxHashes[0] &&
!moonPayIdAppended) {
setMoonPayIdAppended(true);
appendMetadataToRequest(client?.baseApiUrl, `${requestId}`, {
moonPayId: moonPayRequestId
}, client?.source)
?.then(() => {
client?.log(['Posting MoonPay request id', moonPayRequestId, requestId], LogLevel.Verbose);
})
.catch((e) => {
setMoonPayIdAppended(false);
throw e;
});
}
}, [moonPayRequestId, executionStatus]);
const allTxHashes = useMemo(() => {
const isRefund = executionStatus?.status === 'refund';
const _allTxHashes = [];
executionStatus?.txHashes?.forEach((txHash) => {
_allTxHashes.push({
txHash,
chainId: isRefund
? fromToken?.chainId
: toToken?.chainId
});
});
executionStatus?.inTxHashes?.forEach((txHash) => {
_allTxHashes.push({
txHash,
chainId: fromToken?.chainId
});
});
return _allTxHashes;
}, [executionStatus?.txHashes, executionStatus?.inTxHashes]);
const onOnrampError = useCallback((errorMsg, error) => {
if (step !== OnrampStep.Error) {
onError?.(error.message, quote, moonPayRequestId);
}
setStep(OnrampStep.Error);
onAnalyticEvent?.(EventNames.ONRAMP_ERROR, {
error_message: errorMsg,
wallet_connector: connector?.name,
quote_id: requestId,
amount_in: amount,
currency_in: fromToken?.symbol,
chain_id_in: fromToken?.chainId,
amount_out: quote?.details?.currencyOut?.amountFormatted,
currency_out: toToken?.symbol,
chain_id_out: toToken?.chainId,
txHashes: executionStatus?.txHashes ?? []
});
setSwapError(error);
}, [
executionStatus,
connector,
requestId,
quote,
toToken,
fromToken,
amount,
swapError,
moonPayRequestId,
step
]);
return (_jsxs(Modal, { trigger: null, open: open, onOpenChange: (open) => {
if (!open) {
onAnalyticEvent?.(EventNames.ONRAMP_MODAL_CLOSE);
}
onOpenChange(open);
}, css: {
overflow: 'hidden',
p: '4',
maxWidth: '412px !important'
}, children: [step === OnrampStep.Confirming ? (_jsx(OnrampConfirmingStep, { toToken: toToken, fromToken: fromToken, fromChain: fromChain, toChain: toChain, requestId: requestId ?? undefined, depositAddress: depositAddress, recipient: recipient, amount: amount, totalAmount: totalAmount ?? undefined, ethTotalAmount: ethTotalAmount, isFetchingQuote: isFetchingQuote, onAnalyticEvent: onAnalyticEvent, setStep: setStep })) : null, _jsx(OnrampMoonPayStep, { step: step, processingStep: processingStep, toToken: toToken, fromToken: fromToken, fromChain: fromChain, toChain: toChain, recipient: recipient, depositAddress: depositAddress, totalAmount: isPassthrough
? toTokenTotalAmount
: fromToken.symbol === 'ETH'
? ethTotalAmount
: (totalAmount ?? undefined), fiatCurrency: fiatCurrency, isPassthrough: isPassthrough, moonPayCurrencyCode: moonPayCurrencyCode, onAnalyticEvent: onAnalyticEvent, setStep: setStep, setProcessingStep: setProcessingStep, setMoonPayRequestId: setMoonPayRequestId, moonpayOnUrlSignatureRequested: moonpayOnUrlSignatureRequested, onPassthroughSuccess: () => {
setStep(OnrampStep.Success);
onAnalyticEvent?.(EventNames.ONRAMP_PASSTHROUGH_SUCCESS, {
moonPayRequestId,
amount,
totalAmount,
ethTotalAmount
});
}, moonPayThemeId: moonPayThemeId, moonPayThemeMode: moonPayThemeMode, moonPayApiKey: moonPayApiKey, quoteRequestId: requestId, passthroughExternalId: passthroughExternalId, onError: (error) => {
onOnrampError(error.message, error);
} }), step === OnrampStep.ProcessingPassthrough ? (_jsx(OnrampProcessingPassthroughStep, { toToken: toToken, amountToTokenFormatted: amountToTokenFormatted, moonpayTxUrl: moonpayTxUrl ?? undefined, amount: amountFormatted })) : null, step === OnrampStep.Processing ? (_jsx(OnrampProcessingStepUI, { toToken: toToken, fromToken: fromToken, fromChain: fromChain, toChain: toChain, moonpayTxUrl: moonpayTxUrl ?? undefined, fillTxUrl: fillTxUrl, fillTxHash: fillTxHash, processingStep: processingStep, baseTransactionUrl: baseTransactionUrl, requestId: requestId ?? undefined })) : null, step === OnrampStep.Success ? (_jsx(OnrampSuccessStep, { toToken: toToken, isLoadingTransaction: isLoadingTransaction, fillTxHash: fillTxHash, fillTxUrl: fillTxUrl, moonpayTxUrl: moonpayTxUrl ?? undefined, toAmountFormatted: toAmountFormatted, baseTransactionUrl: baseTransactionUrl, onOpenChange: onOpenChange })) : null, step === OnrampStep.Error ? (_jsx(ErrorStep, { error: quoteError || swapError, allTxHashes: allTxHashes, address: recipient, onOpenChange: onOpenChange, transaction: transaction ?? undefined, fromChain: fromChain, toChain: toChain })) : null] }));
};
//# sourceMappingURL=OnrampModal.js.map