@reown/appkit-controllers
Version:
#### 🔗 [Website](https://reown.com/appkit)
242 lines • 9.97 kB
JavaScript
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