UNPKG

@reown/appkit-controllers

Version:

#### 🔗 [Website](https://reown.com/appkit)

242 lines • 9.97 kB
import { proxy, ref, subscribe as sub } from 'valtio/vanilla'; import { subscribeKey as subKey } from 'valtio/vanilla/utils'; import { NumberUtil } from '@reown/appkit-common'; import { ContractUtil } from '@reown/appkit-common'; import { W3mFrameRpcConstants } from '@reown/appkit-wallet/utils'; import { BalanceUtil } from '../utils/BalanceUtil.js'; import { getActiveNetworkTokenAddress } from '../utils/ChainControllerUtil.js'; import { ConstantsUtil } from '../utils/ConstantsUtil.js'; import { CoreHelperUtil } from '../utils/CoreHelperUtil.js'; import { SwapApiUtil } from '../utils/SwapApiUtil.js'; import { withErrorBoundary } from '../utils/withErrorBoundary.js'; import { AccountController } from './AccountController.js'; import { ChainController } from './ChainController.js'; import { ConnectionController } from './ConnectionController.js'; import { EventsController } from './EventsController.js'; import { RouterController } from './RouterController.js'; import { SnackController } from './SnackController.js'; // -- State --------------------------------------------- // const state = proxy({ tokenBalances: [], loading: false }); // -- Controller ---------------------------------------- // const controller = { state, subscribe(callback) { return sub(state, () => callback(state)); }, subscribeKey(key, callback) { return subKey(state, key, callback); }, setToken(token) { if (token) { state.token = ref(token); } }, setTokenAmount(sendTokenAmount) { state.sendTokenAmount = sendTokenAmount; }, setReceiverAddress(receiverAddress) { state.receiverAddress = receiverAddress; }, setReceiverProfileImageUrl(receiverProfileImageUrl) { state.receiverProfileImageUrl = receiverProfileImageUrl; }, setReceiverProfileName(receiverProfileName) { state.receiverProfileName = receiverProfileName; }, setNetworkBalanceInUsd(networkBalanceInUSD) { state.networkBalanceInUSD = networkBalanceInUSD; }, setLoading(loading) { state.loading = loading; }, async sendToken() { try { SendController.setLoading(true); switch (ChainController.state.activeCaipNetwork?.chainNamespace) { case 'eip155': await SendController.sendEvmToken(); return; case 'solana': await SendController.sendSolanaToken(); return; default: throw new Error('Unsupported chain'); } } finally { SendController.setLoading(false); } }, async sendEvmToken() { const activeChainNamespace = ChainController.state.activeChain; const activeAccountType = AccountController.state.preferredAccountTypes?.[activeChainNamespace]; if (!SendController.state.sendTokenAmount || !SendController.state.receiverAddress) { throw new Error('An amount and receiver address are required'); } if (!SendController.state.token) { throw new Error('A token is required'); } if (SendController.state.token?.address) { EventsController.sendEvent({ type: 'track', event: 'SEND_INITIATED', properties: { isSmartAccount: activeAccountType === W3mFrameRpcConstants.ACCOUNT_TYPES.SMART_ACCOUNT, token: SendController.state.token.address, amount: SendController.state.sendTokenAmount, network: ChainController.state.activeCaipNetwork?.caipNetworkId || '' } }); await SendController.sendERC20Token({ receiverAddress: SendController.state.receiverAddress, tokenAddress: SendController.state.token.address, sendTokenAmount: SendController.state.sendTokenAmount, decimals: SendController.state.token.quantity.decimals }); } else { EventsController.sendEvent({ type: 'track', event: 'SEND_INITIATED', properties: { isSmartAccount: activeAccountType === W3mFrameRpcConstants.ACCOUNT_TYPES.SMART_ACCOUNT, token: SendController.state.token.symbol || '', amount: SendController.state.sendTokenAmount, network: ChainController.state.activeCaipNetwork?.caipNetworkId || '' } }); await SendController.sendNativeToken({ receiverAddress: SendController.state.receiverAddress, sendTokenAmount: SendController.state.sendTokenAmount, decimals: SendController.state.token.quantity.decimals }); } }, async fetchTokenBalance(onError) { state.loading = true; const chainId = ChainController.state.activeCaipNetwork?.caipNetworkId; const chain = ChainController.state.activeCaipNetwork?.chainNamespace; const caipAddress = ChainController.state.activeCaipAddress; const address = caipAddress ? CoreHelperUtil.getPlainAddress(caipAddress) : undefined; if (state.lastRetry && !CoreHelperUtil.isAllowedRetry(state.lastRetry, 30 * ConstantsUtil.ONE_SEC_MS)) { state.loading = false; return []; } try { if (address && chainId && chain) { const balances = await BalanceUtil.getMyTokensWithBalance(); state.tokenBalances = balances; state.lastRetry = undefined; return balances; } } catch (error) { state.lastRetry = Date.now(); onError?.(error); SnackController.showError('Token Balance Unavailable'); } finally { state.loading = false; } return []; }, fetchNetworkBalance() { if (state.tokenBalances.length === 0) { return; } const networkTokenBalances = SwapApiUtil.mapBalancesToSwapTokens(state.tokenBalances); if (!networkTokenBalances) { return; } const networkToken = networkTokenBalances.find(token => token.address === getActiveNetworkTokenAddress()); if (!networkToken) { return; } state.networkBalanceInUSD = networkToken ? NumberUtil.multiply(networkToken.quantity.numeric, networkToken.price).toString() : '0'; }, async sendNativeToken(params) { RouterController.pushTransactionStack({}); const to = params.receiverAddress; const address = AccountController.state.address; const value = ConnectionController.parseUnits(params.sendTokenAmount.toString(), Number(params.decimals)); const data = '0x'; await ConnectionController.sendTransaction({ chainNamespace: 'eip155', to, address, data, value: value ?? BigInt(0) }); EventsController.sendEvent({ type: 'track', event: 'SEND_SUCCESS', properties: { isSmartAccount: AccountController.state.preferredAccountTypes?.['eip155'] === W3mFrameRpcConstants.ACCOUNT_TYPES.SMART_ACCOUNT, token: SendController.state.token?.symbol || '', amount: params.sendTokenAmount, network: ChainController.state.activeCaipNetwork?.caipNetworkId || '' } }); ConnectionController._getClient()?.updateBalance('eip155'); SendController.resetSend(); }, async sendERC20Token(params) { RouterController.pushTransactionStack({ onSuccess() { RouterController.replace('Account'); } }); const amount = ConnectionController.parseUnits(params.sendTokenAmount.toString(), Number(params.decimals)); if (AccountController.state.address && params.sendTokenAmount && params.receiverAddress && params.tokenAddress) { const tokenAddress = CoreHelperUtil.getPlainAddress(params.tokenAddress); await ConnectionController.writeContract({ fromAddress: AccountController.state.address, tokenAddress, args: [params.receiverAddress, amount ?? BigInt(0)], method: 'transfer', abi: ContractUtil.getERC20Abi(tokenAddress), chainNamespace: 'eip155' }); SendController.resetSend(); } }, async sendSolanaToken() { if (!SendController.state.sendTokenAmount || !SendController.state.receiverAddress) { throw new Error('An amount and receiver address are required'); } RouterController.pushTransactionStack({ onSuccess() { RouterController.replace('Account'); } }); await ConnectionController.sendTransaction({ chainNamespace: 'solana', to: SendController.state.receiverAddress, value: SendController.state.sendTokenAmount }); ConnectionController._getClient()?.updateBalance('solana'); SendController.resetSend(); }, resetSend() { state.token = undefined; state.sendTokenAmount = undefined; state.receiverAddress = undefined; state.receiverProfileImageUrl = undefined; state.receiverProfileName = undefined; state.loading = false; state.tokenBalances = []; } }; // Export the controller wrapped with our error boundary export const SendController = withErrorBoundary(controller); //# sourceMappingURL=SendController.js.map