@reown/appkit-controllers
Version:
The full stack toolkit to build onchain app UX.
740 lines • 33 kB
JavaScript
import { proxy, subscribe as sub } from 'valtio/vanilla';
import { subscribeKey as subKey } from 'valtio/vanilla/utils';
import { NumberUtil } from '@reown/appkit-common';
import { ConstantsUtil as CommonConstantsUtil } from '@reown/appkit-common';
import { W3mFrameRpcConstants } from '@reown/appkit-wallet/utils';
import { BalanceUtil } from '../utils/BalanceUtil.js';
import { getActiveNetworkTokenAddress, getPreferredAccountType } from '../utils/ChainControllerUtil.js';
import { ConstantsUtil } from '../utils/ConstantsUtil.js';
import { CoreHelperUtil } from '../utils/CoreHelperUtil.js';
import { SwapApiUtil } from '../utils/SwapApiUtil.js';
import { SwapCalculationUtil } from '../utils/SwapCalculationUtil.js';
import { withErrorBoundary } from '../utils/withErrorBoundary.js';
import { AlertController } from './AlertController.js';
import { BlockchainApiController } from './BlockchainApiController.js';
import { ChainController } from './ChainController.js';
import { ConnectionController } from './ConnectionController.js';
import { ConnectorController } from './ConnectorController.js';
import { EventsController } from './EventsController.js';
import { RouterController } from './RouterController.js';
import { SnackController } from './SnackController.js';
// -- Constants ---------------------------------------- //
export const INITIAL_GAS_LIMIT = 150000;
export const TO_AMOUNT_DECIMALS = 6;
class TransactionError extends Error {
constructor(message, displayMessage) {
super(message);
this.name = 'TransactionError';
this.displayMessage = displayMessage;
}
}
// -- State --------------------------------------------- //
const initialState = {
// Loading states
initializing: false,
initialized: false,
loadingPrices: false,
loadingQuote: false,
loadingApprovalTransaction: false,
loadingBuildTransaction: false,
loadingTransaction: false,
// Control states
switchingTokens: false,
// Error states
fetchError: false,
// Approval & Swap transaction states
approvalTransaction: undefined,
swapTransaction: undefined,
transactionError: undefined,
// Input values
sourceToken: undefined,
sourceTokenAmount: '',
sourceTokenPriceInUSD: 0,
toToken: undefined,
toTokenAmount: '',
toTokenPriceInUSD: 0,
networkPrice: '0',
networkBalanceInUSD: '0',
networkTokenSymbol: '',
inputError: undefined,
// Request values
slippage: ConstantsUtil.CONVERT_SLIPPAGE_TOLERANCE,
// Tokens
tokens: undefined,
popularTokens: undefined,
suggestedTokens: undefined,
foundTokens: undefined,
myTokensWithBalance: undefined,
tokensPriceMap: {},
// Calculations
gasFee: '0',
gasPriceInUSD: 0,
priceImpact: undefined,
maxSlippage: undefined,
providerFee: undefined
};
const state = proxy({ ...initialState });
// -- Controller ---------------------------------------- //
const controller = {
state,
subscribe(callback) {
return sub(state, () => callback(state));
},
subscribeKey(key, callback) {
return subKey(state, key, callback);
},
getParams() {
const namespace = ChainController.state.activeChain;
const caipAddress = ChainController.getAccountData(namespace)?.caipAddress ??
ChainController.state.activeCaipAddress;
const address = CoreHelperUtil.getPlainAddress(caipAddress);
const networkAddress = getActiveNetworkTokenAddress();
const connectorId = ConnectorController.getConnectorId(ChainController.state.activeChain);
if (!address) {
throw new Error('No address found to swap the tokens from.');
}
const invalidToToken = !state.toToken?.address || !state.toToken?.decimals;
const invalidSourceToken = !state.sourceToken?.address ||
!state.sourceToken?.decimals ||
!NumberUtil.bigNumber(state.sourceTokenAmount).gt(0);
const invalidSourceTokenAmount = !state.sourceTokenAmount;
return {
networkAddress,
fromAddress: address,
fromCaipAddress: caipAddress,
sourceTokenAddress: state.sourceToken?.address,
toTokenAddress: state.toToken?.address,
toTokenAmount: state.toTokenAmount,
toTokenDecimals: state.toToken?.decimals,
sourceTokenAmount: state.sourceTokenAmount,
sourceTokenDecimals: state.sourceToken?.decimals,
invalidToToken,
invalidSourceToken,
invalidSourceTokenAmount,
availableToSwap: caipAddress && !invalidToToken && !invalidSourceToken && !invalidSourceTokenAmount,
isAuthConnector: connectorId === CommonConstantsUtil.CONNECTOR_ID.AUTH
};
},
async setSourceToken(sourceToken) {
if (!sourceToken) {
state.sourceToken = sourceToken;
state.sourceTokenAmount = '';
state.sourceTokenPriceInUSD = 0;
return;
}
state.sourceToken = sourceToken;
await SwapController.setTokenPrice(sourceToken.address, 'sourceToken');
},
setSourceTokenAmount(amount) {
state.sourceTokenAmount = amount;
},
async setToToken(toToken) {
if (!toToken) {
state.toToken = toToken;
state.toTokenAmount = '';
state.toTokenPriceInUSD = 0;
return;
}
state.toToken = toToken;
await SwapController.setTokenPrice(toToken.address, 'toToken');
},
setToTokenAmount(amount) {
state.toTokenAmount = amount ? NumberUtil.toFixed(amount, TO_AMOUNT_DECIMALS) : '';
},
async setTokenPrice(address, target) {
let price = state.tokensPriceMap[address] || 0;
if (!price) {
state.loadingPrices = true;
price = await SwapController.getAddressPrice(address);
}
if (target === 'sourceToken') {
state.sourceTokenPriceInUSD = price;
}
else if (target === 'toToken') {
state.toTokenPriceInUSD = price;
}
if (state.loadingPrices) {
state.loadingPrices = false;
}
if (SwapController.getParams().availableToSwap && !state.switchingTokens) {
SwapController.swapTokens();
}
},
async switchTokens() {
if (state.initializing || !state.initialized || state.switchingTokens) {
return;
}
state.switchingTokens = true;
try {
const newSourceToken = state.toToken ? { ...state.toToken } : undefined;
const newToToken = state.sourceToken ? { ...state.sourceToken } : undefined;
const newSourceTokenAmount = newSourceToken && state.toTokenAmount === '' ? '1' : state.toTokenAmount;
SwapController.setSourceTokenAmount(newSourceTokenAmount);
SwapController.setToTokenAmount('');
await SwapController.setSourceToken(newSourceToken);
await SwapController.setToToken(newToToken);
state.switchingTokens = false;
SwapController.swapTokens();
}
catch (error) {
state.switchingTokens = false;
throw error;
}
},
resetState() {
state.myTokensWithBalance = initialState.myTokensWithBalance;
state.tokensPriceMap = initialState.tokensPriceMap;
state.initialized = initialState.initialized;
state.initializing = initialState.initializing;
state.switchingTokens = initialState.switchingTokens;
state.sourceToken = initialState.sourceToken;
state.sourceTokenAmount = initialState.sourceTokenAmount;
state.sourceTokenPriceInUSD = initialState.sourceTokenPriceInUSD;
state.toToken = initialState.toToken;
state.toTokenAmount = initialState.toTokenAmount;
state.toTokenPriceInUSD = initialState.toTokenPriceInUSD;
state.networkPrice = initialState.networkPrice;
state.networkTokenSymbol = initialState.networkTokenSymbol;
state.networkBalanceInUSD = initialState.networkBalanceInUSD;
state.inputError = initialState.inputError;
},
resetValues() {
const { networkAddress } = SwapController.getParams();
const networkToken = state.tokens?.find(token => token.address === networkAddress);
SwapController.setSourceToken(networkToken);
SwapController.setToToken(undefined);
},
getApprovalLoadingState() {
return state.loadingApprovalTransaction;
},
clearError() {
state.transactionError = undefined;
},
async initializeState() {
if (state.initializing) {
return;
}
state.initializing = true;
if (!state.initialized) {
try {
await SwapController.fetchTokens();
state.initialized = true;
}
catch (error) {
state.initialized = false;
SnackController.showError('Failed to initialize swap');
RouterController.goBack();
}
}
state.initializing = false;
},
async fetchTokens() {
const { networkAddress } = SwapController.getParams();
await SwapController.getNetworkTokenPrice();
await SwapController.getMyTokensWithBalance();
const networkToken = state.myTokensWithBalance?.find(token => token.address === networkAddress);
if (networkToken) {
state.networkTokenSymbol = networkToken.symbol;
SwapController.setSourceToken(networkToken);
SwapController.setSourceTokenAmount('0');
}
},
async getTokenList() {
const activeCaipNetworkId = ChainController.state.activeCaipNetwork?.caipNetworkId;
if (state.caipNetworkId === activeCaipNetworkId && state.tokens) {
return;
}
try {
state.tokensLoading = true;
const tokens = await SwapApiUtil.getTokenList(activeCaipNetworkId);
state.tokens = tokens;
state.caipNetworkId = activeCaipNetworkId;
state.popularTokens = tokens.sort((aTokenInfo, bTokenInfo) => {
if (aTokenInfo.symbol < bTokenInfo.symbol) {
return -1;
}
if (aTokenInfo.symbol > bTokenInfo.symbol) {
return 1;
}
return 0;
});
const suggestedTokensByChain = (activeCaipNetworkId &&
ConstantsUtil.SUGGESTED_TOKENS_BY_CHAIN?.[activeCaipNetworkId]) ||
[];
const suggestedTokenObjects = suggestedTokensByChain
.map(symbol => tokens.find(t => t.symbol === symbol))
.filter((t) => Boolean(t));
const allSuggestedTokens = ConstantsUtil.SWAP_SUGGESTED_TOKENS || [];
const allSuggestedTokenObjects = allSuggestedTokens
.map(symbol => tokens.find(t => t.symbol === symbol))
.filter((t) => Boolean(t))
.filter(t => !suggestedTokenObjects.some(ct => ct.address === t.address));
state.suggestedTokens = [...suggestedTokenObjects, ...allSuggestedTokenObjects];
}
catch (error) {
state.tokens = [];
state.popularTokens = [];
state.suggestedTokens = [];
}
finally {
state.tokensLoading = false;
}
},
async getAddressPrice(address) {
const existPrice = state.tokensPriceMap[address];
if (existPrice) {
return existPrice;
}
const response = await BlockchainApiController.fetchTokenPrice({
addresses: [address]
});
const fungibles = response?.fungibles || [];
const allTokens = [...(state.tokens || []), ...(state.myTokensWithBalance || [])];
const symbol = allTokens?.find(token => token.address === address)?.symbol;
const price = fungibles.find(p => p.symbol.toLowerCase() === symbol?.toLowerCase())?.price || 0;
const priceAsFloat = parseFloat(price.toString());
state.tokensPriceMap[address] = priceAsFloat;
return priceAsFloat;
},
async getNetworkTokenPrice() {
const { networkAddress } = SwapController.getParams();
const response = await BlockchainApiController.fetchTokenPrice({
addresses: [networkAddress]
}).catch(() => {
SnackController.showError('Failed to fetch network token price');
return { fungibles: [] };
});
const token = response.fungibles?.[0];
const price = token?.price.toString() || '0';
state.tokensPriceMap[networkAddress] = parseFloat(price);
state.networkTokenSymbol = token?.symbol || '';
state.networkPrice = price;
},
async getMyTokensWithBalance(forceUpdate) {
const balances = await BalanceUtil.getMyTokensWithBalance({
forceUpdate,
caipNetwork: ChainController.state.activeCaipNetwork,
address: ChainController.getAccountData()?.address
});
const swapBalances = SwapApiUtil.mapBalancesToSwapTokens(balances);
if (!swapBalances) {
return;
}
await SwapController.getInitialGasPrice();
SwapController.setBalances(swapBalances);
},
setBalances(balances) {
const { networkAddress } = SwapController.getParams();
const caipNetwork = ChainController.state.activeCaipNetwork;
if (!caipNetwork) {
return;
}
const networkToken = balances.find(token => token.address === networkAddress);
balances.forEach(token => {
state.tokensPriceMap[token.address] = token.price || 0;
});
state.myTokensWithBalance = balances.filter(token => token.address.startsWith(caipNetwork.caipNetworkId));
state.networkBalanceInUSD = networkToken
? NumberUtil.multiply(networkToken.quantity.numeric, networkToken.price).toString()
: '0';
},
async getInitialGasPrice() {
const res = await SwapApiUtil.fetchGasPrice();
if (!res) {
return { gasPrice: null, gasPriceInUSD: null };
}
switch (ChainController.state?.activeCaipNetwork?.chainNamespace) {
case CommonConstantsUtil.CHAIN.SOLANA:
state.gasFee = res.standard ?? '0';
state.gasPriceInUSD = NumberUtil.multiply(res.standard, state.networkPrice)
.div(1e9)
.toNumber();
return {
gasPrice: BigInt(state.gasFee),
gasPriceInUSD: Number(state.gasPriceInUSD)
};
case CommonConstantsUtil.CHAIN.EVM:
default:
// eslint-disable-next-line no-case-declarations
const value = res.standard ?? '0';
// eslint-disable-next-line no-case-declarations
const gasFee = BigInt(value);
// eslint-disable-next-line no-case-declarations
const gasLimit = BigInt(INITIAL_GAS_LIMIT);
// eslint-disable-next-line no-case-declarations
const gasPrice = SwapCalculationUtil.getGasPriceInUSD(state.networkPrice, gasLimit, gasFee);
state.gasFee = value;
state.gasPriceInUSD = gasPrice;
return { gasPrice: gasFee, gasPriceInUSD: gasPrice };
}
},
// -- Swap -------------------------------------- //
async swapTokens() {
const address = ChainController.getAccountData()?.address;
const sourceToken = state.sourceToken;
const toToken = state.toToken;
const haveSourceTokenAmount = NumberUtil.bigNumber(state.sourceTokenAmount).gt(0);
if (!haveSourceTokenAmount) {
SwapController.setToTokenAmount('');
}
if (!toToken || !sourceToken || state.loadingPrices || !haveSourceTokenAmount || !address) {
return;
}
state.loadingQuote = true;
const amountDecimal = NumberUtil.bigNumber(state.sourceTokenAmount)
.times(10 ** sourceToken.decimals)
.round(0)
.toFixed(0);
try {
const quoteResponse = await BlockchainApiController.fetchSwapQuote({
userAddress: address,
from: sourceToken.address,
to: toToken.address,
gasPrice: state.gasFee,
amount: amountDecimal.toString()
});
state.loadingQuote = false;
const quoteToAmount = quoteResponse?.quotes?.[0]?.toAmount;
if (!quoteToAmount) {
AlertController.open({
displayMessage: 'Incorrect amount',
debugMessage: 'Please enter a valid amount'
}, 'error');
return;
}
const toTokenAmount = NumberUtil.bigNumber(quoteToAmount)
.div(10 ** toToken.decimals)
.toString();
SwapController.setToTokenAmount(toTokenAmount);
const isInsufficientToken = SwapController.hasInsufficientToken(state.sourceTokenAmount, sourceToken.address);
if (isInsufficientToken) {
state.inputError = 'Insufficient balance';
}
else {
state.inputError = undefined;
SwapController.setTransactionDetails();
}
}
catch (error) {
const response = await SwapApiUtil.handleSwapError(error);
state.loadingQuote = false;
state.inputError = response || 'Insufficient balance';
}
},
// -- Create Transactions -------------------------------------- //
async getTransaction() {
const { fromCaipAddress, availableToSwap } = SwapController.getParams();
const sourceToken = state.sourceToken;
const toToken = state.toToken;
if (!fromCaipAddress || !availableToSwap || !sourceToken || !toToken || state.loadingQuote) {
return undefined;
}
try {
state.loadingBuildTransaction = true;
const hasAllowance = await SwapApiUtil.fetchSwapAllowance({
userAddress: fromCaipAddress,
tokenAddress: sourceToken.address,
sourceTokenAmount: state.sourceTokenAmount,
sourceTokenDecimals: sourceToken.decimals
});
let transaction = undefined;
if (hasAllowance) {
transaction = await SwapController.createSwapTransaction();
}
else {
transaction = await SwapController.createAllowanceTransaction();
}
state.loadingBuildTransaction = false;
state.fetchError = false;
return transaction;
}
catch (error) {
RouterController.goBack();
SnackController.showError('Failed to check allowance');
state.loadingBuildTransaction = false;
state.approvalTransaction = undefined;
state.swapTransaction = undefined;
state.fetchError = true;
return undefined;
}
},
async createAllowanceTransaction() {
const { fromCaipAddress, sourceTokenAddress, toTokenAddress } = SwapController.getParams();
if (!fromCaipAddress || !toTokenAddress) {
return undefined;
}
if (!sourceTokenAddress) {
throw new Error('createAllowanceTransaction - No source token address found.');
}
try {
const response = await BlockchainApiController.generateApproveCalldata({
from: sourceTokenAddress,
to: toTokenAddress,
userAddress: fromCaipAddress
});
const address = CoreHelperUtil.getPlainAddress(response.tx.from);
if (!address) {
throw new Error('SwapController:createAllowanceTransaction - address is required');
}
const transaction = {
data: response.tx.data,
to: address,
gasPrice: BigInt(response.tx.eip155.gasPrice),
value: BigInt(response.tx.value),
toAmount: state.toTokenAmount
};
state.swapTransaction = undefined;
state.approvalTransaction = {
data: transaction.data,
to: transaction.to,
gasPrice: transaction.gasPrice,
value: transaction.value,
toAmount: transaction.toAmount
};
return {
data: transaction.data,
to: transaction.to,
gasPrice: transaction.gasPrice,
value: transaction.value,
toAmount: transaction.toAmount
};
}
catch (error) {
RouterController.goBack();
SnackController.showError('Failed to create approval transaction');
state.approvalTransaction = undefined;
state.swapTransaction = undefined;
state.fetchError = true;
return undefined;
}
},
async createSwapTransaction() {
const { networkAddress, fromCaipAddress, sourceTokenAmount } = SwapController.getParams();
const sourceToken = state.sourceToken;
const toToken = state.toToken;
if (!fromCaipAddress || !sourceTokenAmount || !sourceToken || !toToken) {
return undefined;
}
let amount = ConnectionController.parseUnits(sourceTokenAmount, sourceToken.decimals)?.toString();
try {
const isSourceTokenIsNetworkToken = sourceToken.address === networkAddress;
let response = await BlockchainApiController.generateSwapCalldata({
userAddress: fromCaipAddress,
from: sourceToken.address,
to: toToken.address,
amount: amount,
disableEstimate: true
});
let gas = BigInt(response.tx.eip155.gas);
let gasPrice = BigInt(response.tx.eip155.gasPrice);
/*
* For native-token source swaps (e.g. ETH → USDC), the transaction
* value IS the swap input amount, so the total ETH deducted from the
* wallet is value + gas×gasPrice. If that exceeds the balance the
* wallet's eth_estimateGas call will revert.
*
* The UI's Max button uses a rough constant to pre-subtract gas, but
* the API may return a different gas estimate. We correct here using
* the actual numbers from the first calldata response.
*/
if (isSourceTokenIsNetworkToken && amount) {
const nativeToken = state.myTokensWithBalance?.find(t => t.address === networkAddress);
if (nativeToken) {
const decimals = parseInt(nativeToken.quantity.decimals, 10);
const rawBalance = ConnectionController.parseUnits(nativeToken.quantity.numeric, decimals) ?? 0n;
const gasCost = gas * gasPrice;
const value = BigInt(amount);
if (value + gasCost > rawBalance) {
// Re-request calldata with an amount that leaves room for gas
const adjustedAmount = rawBalance > gasCost ? rawBalance - gasCost : 0n;
// eslint-disable-next-line max-depth
if (adjustedAmount > 0n) {
response = await BlockchainApiController.generateSwapCalldata({
userAddress: fromCaipAddress,
from: sourceToken.address,
to: toToken.address,
amount: adjustedAmount.toString(),
disableEstimate: true
});
amount = adjustedAmount.toString();
gas = BigInt(response.tx.eip155.gas);
gasPrice = BigInt(response.tx.eip155.gasPrice);
}
}
}
}
const address = CoreHelperUtil.getPlainAddress(response.tx.to);
if (!address) {
throw new Error('SwapController:createSwapTransaction - address is required');
}
const transaction = {
data: response.tx.data,
to: address,
gas,
gasPrice,
value: isSourceTokenIsNetworkToken ? BigInt(amount ?? '0') : BigInt('0'),
toAmount: state.toTokenAmount
};
state.gasPriceInUSD = SwapCalculationUtil.getGasPriceInUSD(state.networkPrice, gas, gasPrice);
state.approvalTransaction = undefined;
state.swapTransaction = transaction;
return transaction;
}
catch (error) {
RouterController.goBack();
SnackController.showError('Failed to create transaction');
state.approvalTransaction = undefined;
state.swapTransaction = undefined;
state.fetchError = true;
return undefined;
}
},
onEmbeddedWalletApprovalSuccess() {
SnackController.showLoading('Approve limit increase in your wallet');
RouterController.replace('SwapPreview');
},
// -- Send Transactions --------------------------------- //
async sendTransactionForApproval(data) {
const { fromAddress, isAuthConnector } = SwapController.getParams();
state.loadingApprovalTransaction = true;
const approveLimitMessage = `Approve limit increase in your wallet`;
if (isAuthConnector) {
RouterController.pushTransactionStack({
onSuccess: SwapController.onEmbeddedWalletApprovalSuccess
});
}
else {
SnackController.showLoading(approveLimitMessage);
}
try {
await ConnectionController.sendTransaction({
address: fromAddress,
to: data.to,
data: data.data,
value: data.value,
chainNamespace: CommonConstantsUtil.CHAIN.EVM
});
await SwapController.swapTokens();
await SwapController.getTransaction();
state.approvalTransaction = undefined;
state.loadingApprovalTransaction = false;
}
catch (err) {
const error = err;
state.transactionError = error?.displayMessage;
state.loadingApprovalTransaction = false;
SnackController.showError(error?.displayMessage || 'Transaction error');
EventsController.sendEvent({
type: 'track',
event: 'SWAP_APPROVAL_ERROR',
properties: {
message: error?.displayMessage || error?.message || 'Unknown',
network: ChainController.state.activeCaipNetwork?.caipNetworkId || '',
swapFromToken: SwapController.state.sourceToken?.symbol || '',
swapToToken: SwapController.state.toToken?.symbol || '',
swapFromAmount: SwapController.state.sourceTokenAmount || '',
swapToAmount: SwapController.state.toTokenAmount || '',
isSmartAccount: getPreferredAccountType(CommonConstantsUtil.CHAIN.EVM) ===
W3mFrameRpcConstants.ACCOUNT_TYPES.SMART_ACCOUNT
}
});
}
},
async sendTransactionForSwap(data) {
if (!data) {
return undefined;
}
const { fromAddress, toTokenAmount, isAuthConnector } = SwapController.getParams();
state.loadingTransaction = true;
const snackbarPendingMessage = `Swapping ${state.sourceToken?.symbol} to ${NumberUtil.formatNumberToLocalString(toTokenAmount, 3)} ${state.toToken?.symbol}`;
const snackbarSuccessMessage = `Swapped ${state.sourceToken?.symbol} to ${NumberUtil.formatNumberToLocalString(toTokenAmount, 3)} ${state.toToken?.symbol}`;
if (isAuthConnector) {
RouterController.pushTransactionStack({
onSuccess() {
RouterController.replace('Account');
SnackController.showLoading(snackbarPendingMessage);
controller.resetState();
}
});
}
else {
SnackController.showLoading('Confirm transaction in your wallet');
}
try {
const forceUpdateAddresses = [state.sourceToken?.address, state.toToken?.address].join(',');
const transactionHash = await ConnectionController.sendTransaction({
address: fromAddress,
to: data.to,
data: data.data,
value: data.value,
chainNamespace: CommonConstantsUtil.CHAIN.EVM
});
state.loadingTransaction = false;
SnackController.showSuccess(snackbarSuccessMessage);
EventsController.sendEvent({
type: 'track',
event: 'SWAP_SUCCESS',
properties: {
network: ChainController.state.activeCaipNetwork?.caipNetworkId || '',
swapFromToken: SwapController.state.sourceToken?.symbol || '',
swapToToken: SwapController.state.toToken?.symbol || '',
swapFromAmount: SwapController.state.sourceTokenAmount || '',
swapToAmount: SwapController.state.toTokenAmount || '',
isSmartAccount: getPreferredAccountType(CommonConstantsUtil.CHAIN.EVM) ===
W3mFrameRpcConstants.ACCOUNT_TYPES.SMART_ACCOUNT
}
});
controller.resetState();
if (!isAuthConnector) {
RouterController.replace('Account');
}
controller.getMyTokensWithBalance(forceUpdateAddresses);
return transactionHash;
}
catch (err) {
const error = err;
state.transactionError = error?.displayMessage;
state.loadingTransaction = false;
SnackController.showError(error?.displayMessage || 'Transaction error');
EventsController.sendEvent({
type: 'track',
event: 'SWAP_ERROR',
properties: {
message: error?.displayMessage || error?.message || 'Unknown',
network: ChainController.state.activeCaipNetwork?.caipNetworkId || '',
swapFromToken: SwapController.state.sourceToken?.symbol || '',
swapToToken: SwapController.state.toToken?.symbol || '',
swapFromAmount: SwapController.state.sourceTokenAmount || '',
swapToAmount: SwapController.state.toTokenAmount || '',
isSmartAccount: getPreferredAccountType(CommonConstantsUtil.CHAIN.EVM) ===
W3mFrameRpcConstants.ACCOUNT_TYPES.SMART_ACCOUNT
}
});
return undefined;
}
},
// -- Checks -------------------------------------------- //
hasInsufficientToken(sourceTokenAmount, sourceTokenAddress) {
const isInsufficientSourceTokenForSwap = SwapCalculationUtil.isInsufficientSourceTokenForSwap(sourceTokenAmount, sourceTokenAddress, state.myTokensWithBalance);
return isInsufficientSourceTokenForSwap;
},
// -- Calculations -------------------------------------- //
setTransactionDetails() {
const { toTokenAddress, toTokenDecimals } = SwapController.getParams();
if (!toTokenAddress || !toTokenDecimals) {
return;
}
state.gasPriceInUSD = SwapCalculationUtil.getGasPriceInUSD(state.networkPrice, BigInt(state.gasFee), BigInt(INITIAL_GAS_LIMIT));
state.priceImpact = SwapCalculationUtil.getPriceImpact({
sourceTokenAmount: state.sourceTokenAmount,
sourceTokenPriceInUSD: state.sourceTokenPriceInUSD,
toTokenPriceInUSD: state.toTokenPriceInUSD,
toTokenAmount: state.toTokenAmount
});
state.maxSlippage = SwapCalculationUtil.getMaxSlippage(state.slippage, state.toTokenAmount);
state.providerFee = SwapCalculationUtil.getProviderFee(state.sourceTokenAmount);
}
};
// Export the controller wrapped with our error boundary
export const SwapController = withErrorBoundary(controller);
//# sourceMappingURL=SwapController.js.map