diff --git a/packages/wallet-ui/src/App.tsx b/packages/wallet-ui/src/App.tsx index d0cee58f..140af5fe 100644 --- a/packages/wallet-ui/src/App.tsx +++ b/packages/wallet-ui/src/App.tsx @@ -37,11 +37,10 @@ function App() { } = useAppSelector((state) => state.modals); const { loader } = useAppSelector((state) => state.UI); const networks = useAppSelector((state) => state.networks); - const { accounts } = useAppSelector((state) => state.wallet); + const { currentAccount } = useAppSelector((state) => state.wallet); const { hasMetamask } = useHasMetamask(); - - const address = - accounts?.length > 0 ? (accounts[0] as unknown as string) : DUMMY_ADDRESS; + const chainId = networks.items?.[networks.activeNetwork]?.chainId; + const address = currentAccount ?? DUMMY_ADDRESS; useEffect(() => { if (!provider) { @@ -57,12 +56,11 @@ function App() { }, [connected, forceReconnect, hasMetamask, provider]); useEffect(() => { - if (provider && networks.items.length > 0) { - const chainId = networks.items[networks.activeNetwork].chainId; + if (provider && networks.items.length > 0 && chainId) { getWalletData(chainId); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [networks.activeNetwork, provider]); + }, [networks.activeNetwork, provider, chainId]); const loading = loader.isLoading; return ( diff --git a/packages/wallet-ui/src/components/ui/molecule/TransactionsList/TransactionsList.view.tsx b/packages/wallet-ui/src/components/ui/molecule/TransactionsList/TransactionsList.view.tsx index efa01a26..70482521 100644 --- a/packages/wallet-ui/src/components/ui/molecule/TransactionsList/TransactionsList.view.tsx +++ b/packages/wallet-ui/src/components/ui/molecule/TransactionsList/TransactionsList.view.tsx @@ -16,19 +16,23 @@ export const TransactionsListView = ({ transactions }: Props) => { const networks = useAppSelector((state) => state.networks); const wallet = useAppSelector((state) => state.wallet); const timeoutHandle = useRef(setTimeout(() => {})); + const chainId = networks.items[networks.activeNetwork]?.chainId; + const { + currentAccount, + erc20TokenBalanceSelected, + transactions: walletTransactions, + } = wallet; useEffect(() => { - const chain = networks.items[networks.activeNetwork]?.chainId; - const address = wallet.accounts?.[0] as unknown as string; - if (chain && address) { + if (chainId && currentAccount) { clearTimeout(timeoutHandle.current); // cancel the timeout that was in-flight timeoutHandle.current = setTimeout( () => getTransactions( - address, - wallet.erc20TokenBalanceSelected.address, + currentAccount, + erc20TokenBalanceSelected.address, 10, - chain, + chainId, false, true, ), @@ -37,30 +41,29 @@ export const TransactionsListView = ({ transactions }: Props) => { return () => clearTimeout(timeoutHandle.current); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [wallet.transactions]); + }, [walletTransactions]); useEffect( () => { - const chain = networks.items[networks.activeNetwork]?.chainId; - const address = wallet.accounts?.[0] as unknown as string; - if (chain && address) { + if (chainId && currentAccount) { clearTimeout(timeoutHandle.current); // cancel the timeout that was in-flight getTransactions( - address, - wallet.erc20TokenBalanceSelected.address, + currentAccount, + erc20TokenBalanceSelected.address, 10, - chain, + chainId, ); } }, // eslint-disable-next-line react-hooks/exhaustive-deps [ // eslint-disable-next-line react-hooks/exhaustive-deps - wallet.erc20TokenBalanceSelected.address, + erc20TokenBalanceSelected.address, // eslint-disable-next-line react-hooks/exhaustive-deps - wallet.erc20TokenBalanceSelected.chainId, + erc20TokenBalanceSelected.chainId, // eslint-disable-next-line react-hooks/exhaustive-deps - wallet.accounts?.[0], + currentAccount, + chainId, ], ); diff --git a/packages/wallet-ui/src/components/ui/organism/AddTokenModal/AddTokenModal.view.tsx b/packages/wallet-ui/src/components/ui/organism/AddTokenModal/AddTokenModal.view.tsx index 4db86b2d..76031c8f 100644 --- a/packages/wallet-ui/src/components/ui/organism/AddTokenModal/AddTokenModal.view.tsx +++ b/packages/wallet-ui/src/components/ui/organism/AddTokenModal/AddTokenModal.view.tsx @@ -26,8 +26,8 @@ export const AddTokenModalView = ({ closeModal }: Props) => { const { setErc20TokenBalance, addErc20Token } = useStarkNetSnap(); const [enabled, setEnabled] = useState(false); const networks = useAppSelector((state) => state.networks); - const { accounts } = useAppSelector((state) => state.wallet); - const chain = networks && networks.items[networks.activeNetwork].chainId; + const { currentAccount } = useAppSelector((state) => state.wallet); + const chainId = networks && networks.items[networks.activeNetwork].chainId; const [isValidAddress, setIsValidAddress] = useState(false); const [fields, setFields] = useState({ address: '', @@ -105,8 +105,8 @@ export const AddTokenModalView = ({ closeModal }: Props) => { fields.name, fields.symbol, parseFloat(fields.decimal), - chain, - accounts[0] as unknown as string, + chainId, + currentAccount, ); if (newToken) { setErc20TokenBalance(newToken); diff --git a/packages/wallet-ui/src/components/ui/organism/Header/SendSummaryModal/SendSummaryModal.view.tsx b/packages/wallet-ui/src/components/ui/organism/Header/SendSummaryModal/SendSummaryModal.view.tsx index 95807fbf..945516fb 100644 --- a/packages/wallet-ui/src/components/ui/organism/Header/SendSummaryModal/SendSummaryModal.view.tsx +++ b/packages/wallet-ui/src/components/ui/organism/Header/SendSummaryModal/SendSummaryModal.view.tsx @@ -50,6 +50,7 @@ export const SendSummaryModalView = ({ selectedFeeToken, }: Props) => { const wallet = useAppSelector((state) => state.wallet); + const currentAccount = wallet.currentAccount; const [estimatingGas, setEstimatingGas] = useState(true); const [gasFees, setGasFees] = useState({ suggestedMaxFee: '0', @@ -80,7 +81,7 @@ export const SendSummaryModalView = ({ useEffect(() => { const fetchGasFee = () => { - if (wallet.accounts) { + if (currentAccount) { setGasFeesError(false); setEstimatingGas(true); const amountBN = ethers.utils.parseUnits( @@ -92,7 +93,7 @@ export const SendSummaryModalView = ({ wallet.erc20TokenBalanceSelected.address, ContractFuncName.Transfer, callData, - wallet.accounts[0] as unknown as string, + currentAccount, chainId, selectedFeeToken === FeeToken.STRK ? constants.TRANSACTION_VERSION.V3 @@ -113,7 +114,7 @@ export const SendSummaryModalView = ({ } }; fetchGasFee(); - }, []); + }, [currentAccount]); useEffect(() => { if (gasFees?.suggestedMaxFee) { @@ -175,7 +176,7 @@ export const SendSummaryModalView = ({ }, [amount, wallet.erc20TokenBalanceSelected]); const handleConfirmClick = () => { - if (wallet.accounts) { + if (currentAccount) { const amountBN = ethers.utils.parseUnits( amount, wallet.erc20TokenBalanceSelected.decimals, @@ -185,7 +186,7 @@ export const SendSummaryModalView = ({ wallet.erc20TokenBalanceSelected.address, ContractFuncName.Transfer, callData, - wallet.accounts[0] as unknown as string, + currentAccount, gasFees.suggestedMaxFee, chainId, selectedFeeToken, @@ -194,7 +195,7 @@ export const SendSummaryModalView = ({ if (result) { toastr.success('Transaction sent successfully'); getTransactions( - wallet.accounts[0] as unknown as string, + currentAccount, wallet.erc20TokenBalanceSelected.address, 10, chainId, diff --git a/packages/wallet-ui/src/components/ui/organism/SideBar/SideBar.style.ts b/packages/wallet-ui/src/components/ui/organism/SideBar/SideBar.style.ts index a9f2ef20..fb1d015f 100644 --- a/packages/wallet-ui/src/components/ui/organism/SideBar/SideBar.style.ts +++ b/packages/wallet-ui/src/components/ui/organism/SideBar/SideBar.style.ts @@ -39,6 +39,11 @@ export const InfoIcon = styled(RoundedIcon)` margin-right: ${(props) => props.theme.spacing.tiny2}; `; +export const AddIcon = styled(RoundedIcon)` + cursor: pointer; + margin-left: ${(props) => props.theme.spacing.tiny2}; +`; + export const AddTokenButton = styled(Button).attrs((props) => ({ textStyle: { fontWeight: props.theme.typography.bold.fontWeight, diff --git a/packages/wallet-ui/src/components/ui/organism/SideBar/SideBar.view.tsx b/packages/wallet-ui/src/components/ui/organism/SideBar/SideBar.view.tsx index 6221b8aa..b2236945 100644 --- a/packages/wallet-ui/src/components/ui/organism/SideBar/SideBar.view.tsx +++ b/packages/wallet-ui/src/components/ui/organism/SideBar/SideBar.view.tsx @@ -12,6 +12,7 @@ import { AccountDetailsContent, AccountImageStyled, AccountLabel, + AddIcon, AddTokenButton, DivList, InfoIcon, @@ -37,7 +38,7 @@ export const SideBarView = ({ address }: Props) => { const [accountDetailsOpen, setAccountDetailsOpen] = useState(false); const wallet = useAppSelector((state) => state.wallet); const [addTokenOpen, setAddTokenOpen] = useState(false); - const { getStarkName } = useStarkNetSnap(); + const { getStarkName, addNewAccount } = useStarkNetSnap(); const [starkName, setStarkName] = useState(undefined); const ref = useRef(); @@ -114,6 +115,7 @@ export const SideBarView = ({ address }: Props) => { setInfoModalOpen(true)}>i + await addNewAccount(chainId)}>+ diff --git a/packages/wallet-ui/src/services/useStarkNetSnap.ts b/packages/wallet-ui/src/services/useStarkNetSnap.ts index 17b95dd7..3d121d2d 100644 --- a/packages/wallet-ui/src/services/useStarkNetSnap.ts +++ b/packages/wallet-ui/src/services/useStarkNetSnap.ts @@ -251,6 +251,8 @@ export const useStarkNetSnap = () => { } }; + // FIXME: this method has to rewrite when switch account enabled + // we should get the active account, instead of recovering all accounts const getWalletData = async (chainId: string, networks?: Network[]) => { if (!loader.isLoading && !networks) { dispatch(enableLoadingWithMessage('Getting network data ...')); @@ -302,10 +304,62 @@ export const useStarkNetSnap = () => { dispatch(disableLoading()); }; + // FIXME: this method has to replace getWalletData() + const initWalletData = async ({ + account, + }: { + // TODO: we should allow the account to be optional + // and get the active account from the snap + account: Account; + }) => { + if (!loader.isLoading) { + dispatch(enableLoadingWithMessage('Getting network data ...')); + } + + const { address, chainId } = account; + + await setAccount(account); + + await initTokensAndBalances(chainId, address); + + dispatch(disableLoading()); + }; + + const setAccount = async (account: Account) => { + const { upgradeRequired, deployRequired } = account; + dispatch(setAccounts(account)); + + // FIXME: hardcode to set the info modal visible, + // but it should only visible when the account is not deployed + dispatch(setInfoModalVisible(true)); + + dispatch(setUpgradeModalVisible(upgradeRequired)); + dispatch(setDeployModalVisible(deployRequired)); + }; + const setErc20TokenBalance = (erc20TokenBalance: Erc20TokenBalance) => { dispatch(setErc20TokenBalanceSelected(erc20TokenBalance)); }; + const initTokensAndBalances = async (chainId: string, address: string) => { + const tokens = await getTokens(chainId); + + // Get all tokens balance, USD value, and format them into Erc20TokenBalance type + const tokensWithBalances: Erc20TokenBalance[] = await Promise.all( + tokens.map(async (token) => { + const balance = await getTokenBalance(token.address, address, chainId); + const usdPrice = await getAssetPriceUSD(token); + return await getTokenBalanceWithDetails(balance, token, usdPrice); + }), + ); + + dispatch(setErc20TokenBalances(tokensWithBalances)); + + if (tokensWithBalances.length > 0) { + setErc20TokenBalance(tokensWithBalances[0]); + } + }; + async function getPrivateKeyFromAddress(address: string, chainId: string) { try { await provider.request({ @@ -337,6 +391,9 @@ export const useStarkNetSnap = () => { chainId: string, transactionVersion?: typeof constants.TRANSACTION_VERSION.V3, ) { + console.log('contractCallData', contractCallData); + console.log('contractAddress', contractAddress); + console.log('address', address); try { const invocations: Invocations = [ { @@ -974,6 +1031,35 @@ export const useStarkNetSnap = () => { } }; + const addNewAccount = async (chainId: string) => { + dispatch(enableLoadingWithMessage('Adding new account...')); + try { + const account = (await provider.request({ + method: 'wallet_invokeSnap', + params: { + snapId, + request: { + method: 'starkNet_addAccount', + params: { + chainId, + }, + }, + }, + })) as Account; + + await initWalletData({ + account, + }); + + return account; + } catch (err: any) { + const toastr = new Toastr(); + toastr.error(err.message as unknown as string); + } finally { + dispatch(disableLoading()); + } + }; + return { connectToSnap, getNetworks, @@ -1004,6 +1090,7 @@ export const useStarkNetSnap = () => { getCurrentNetwork, getStarkName, getAddrFromStarkName, + addNewAccount, satisfiesVersion: oldVersionDetected, }; }; diff --git a/packages/wallet-ui/src/slices/walletSlice.ts b/packages/wallet-ui/src/slices/walletSlice.ts index 3f61a813..59a1ee9c 100644 --- a/packages/wallet-ui/src/slices/walletSlice.ts +++ b/packages/wallet-ui/src/slices/walletSlice.ts @@ -8,7 +8,8 @@ export interface WalletState { connected: boolean; isLoading: boolean; forceReconnect: boolean; - accounts: Account[]; + accounts: string[]; + currentAccount: string; erc20TokenBalances: Erc20TokenBalance[]; erc20TokenBalanceSelected: Erc20TokenBalance; transactions: Transaction[]; @@ -21,6 +22,7 @@ const initialState: WalletState = { isLoading: false, forceReconnect: false, accounts: [], + currentAccount: '', erc20TokenBalances: [], erc20TokenBalanceSelected: {} as Erc20TokenBalance, transactions: [], @@ -41,12 +43,26 @@ export const walletSlice = createSlice({ setForceReconnect: (state, { payload }) => { state.forceReconnect = payload; }, - setAccounts: (state, { payload }) => { + setAccounts: ( + state, + { + payload, + }: { + payload: Account | Account[]; + }, + ) => { if (Array.isArray(payload)) { + // When switching networks, we clean up the accounts from the previous network + // Hence, we can assume that setAccounts is called with an array of accounts from the same network state.accounts = payload.map((account) => account.address); } else { state.accounts.push(payload.address); } + // FIXME: this is a hack to set the current account to the last one added + // We should have a way to get the active account + const currentAccountIdx = state.accounts.length - 1; + const currentAccount = state.accounts[currentAccountIdx]; + state.currentAccount = currentAccount; }, setErc20TokenBalances: (state, { payload }) => { state.erc20TokenBalances = payload; @@ -94,6 +110,7 @@ export const walletSlice = createSlice({ }, clearAccounts: (state) => { state.accounts = []; + state.currentAccount = ''; }, resetWallet: (state) => { return { diff --git a/packages/wallet-ui/src/types/index.ts b/packages/wallet-ui/src/types/index.ts index 657538af..a53da518 100644 --- a/packages/wallet-ui/src/types/index.ts +++ b/packages/wallet-ui/src/types/index.ts @@ -32,6 +32,7 @@ export type Account = { publicKey: string; upgradeRequired: boolean; deployRequired: boolean; + chainId: string; }; export type Network = {