From ec9e8cf3786b6fda16325d7434b69fa95e0a67d5 Mon Sep 17 00:00:00 2001 From: MirkoSisko Date: Fri, 24 Jan 2025 12:08:36 +0100 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9C=A8=20Implement=20transfer=20of=20cus?= =?UTF-8?q?tom=20tokens?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/background/contracts.ts | 11 +++- src/components/account/accountSend.tsx | 24 +++++-- src/components/account/tokenDropdown.tsx | 84 ++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 7 deletions(-) create mode 100644 src/components/account/tokenDropdown.tsx diff --git a/src/background/contracts.ts b/src/background/contracts.ts index b573469..9c74b39 100644 --- a/src/background/contracts.ts +++ b/src/background/contracts.ts @@ -148,13 +148,17 @@ export async function modifyEthBalance(amount: bigint) { } } -export async function sendToAccount(recipientAddr: string, amount: bigint) { +export async function sendToAccount( + recipientAddr: string, + amount: bigint, + tokenAddress: string = ETH_ADDRESS +) { try { const provider = await getProvider(); const acc = await getSelectedAccount(); - const contract = await provider.getClassAt(ETH_ADDRESS); - const erc20 = new Contract(contract.abi, ETH_ADDRESS, provider); + const contract = await provider.getClassAt(tokenAddress); + const erc20 = new Contract(contract.abi, tokenAddress, provider); erc20.connect(acc); const balance = await erc20.balanceOf(acc.address); @@ -170,6 +174,7 @@ export async function sendToAccount(recipientAddr: string, amount: bigint) { entrypoint: 'transfer', calldata: [recipientAddr, uint256.bnToUint256(amount)], }); + if (estimate.suggestedMaxFee > amount) { transferResponse = await erc20.transfer(recipientAddr, amount + estimate.suggestedMaxFee); } else { diff --git a/src/components/account/accountSend.tsx b/src/components/account/accountSend.tsx index 9cb70fb..79e77bb 100644 --- a/src/components/account/accountSend.tsx +++ b/src/components/account/accountSend.tsx @@ -6,6 +6,7 @@ import { Typography, Container, CircularProgress, + SelectChangeEvent, } from '@mui/material'; import React, { useState, useCallback, FormEvent } from 'react'; import { useNavigate } from 'react-router-dom'; @@ -13,14 +14,21 @@ import { ChevronLeft } from '@mui/icons-material'; import { fetchCurrentBlockNumber } from '../../background/utils'; import { useSharedState } from '../context/context'; import { sendToAccount } from '../../background/contracts'; +import { TokenDropdown } from './tokenDropdown'; +import { ETH_ADDRESS } from '../../background/constants'; export const AccountSend: React.FC = () => { const navigate = useNavigate(); const { selectedAccount, updateCurrentBalance, setLastFetchedUrl, setCurrentBlock } = useSharedState(); - const [formData, setFormData] = useState<{ recipient: string; amount: number }>({ + const [formData, setFormData] = useState<{ + recipient: string; + amount: number; + tokenAddress: string; + }>({ recipient: '', amount: 0, + tokenAddress: '', }); const [isSubmitting, setIsSubmitting] = useState(false); @@ -41,8 +49,8 @@ export const AccountSend: React.FC = () => { if (!formData.recipient || formData.amount <= 0) return; setIsSubmitting(true); const sendAmountWei = BigInt(formData.amount * 10 ** 18); - const balance = await sendToAccount(formData.recipient, sendAmountWei); - if (balance) { + const balance = await sendToAccount(formData.recipient, sendAmountWei, formData.tokenAddress); + if (balance && formData.tokenAddress === ETH_ADDRESS) { await updateCurrentBalance(balance); await updateCurrentBlockNumber(); } @@ -80,6 +88,14 @@ export const AccountSend: React.FC = () => { {!isSubmitting ? ( <> + + + setFormData((prev) => ({ ...prev, tokenAddress: e.target.value })) + } + /> + { fullWidth name="amount" value={formData.amount} - label={'Amount (ETH)'} + label={'Amount'} onChange={(e) => { setFormData({ ...formData, amount: parseInt(e.target.value, 10) }); }} diff --git a/src/components/account/tokenDropdown.tsx b/src/components/account/tokenDropdown.tsx new file mode 100644 index 0000000..8add617 --- /dev/null +++ b/src/components/account/tokenDropdown.tsx @@ -0,0 +1,84 @@ +import React from 'react'; +import { validateAndParseAddress } from 'starknet-6'; +import { + Box, + FormControl, + InputLabel, + MenuItem, + Select, + SelectChangeEvent, + Typography, +} from '@mui/material'; + +import { useSharedState } from '../context/context'; +import { useAccountContracts } from '../hooks/useAccountContracts'; +import { getTokenBalance } from '../../background/contracts'; +import { ETH_ADDRESS } from '../../background/constants'; +import { getBalanceStr } from '../utils/utils'; + +interface ITokenDropdownProps { + value: string; + onChange: (e: SelectChangeEvent) => void; +} + +export interface Token { + address: string; + balance: string; + symbol: string; +} + +export const TokenDropdown: React.FC = ({ value, onChange }) => { + const { data: accountContracts } = useAccountContracts(); + const { selectedAccount, currentBalance } = useSharedState(); + + const [tokenBalances, setTokenBalances] = React.useState>([ + { + address: ETH_ADDRESS, + symbol: 'ETH', + balance: getBalanceStr(currentBalance), + }, + ]); + + const contracts = React.useMemo( + () => accountContracts.get(selectedAccount?.address ?? '') ?? [], + [accountContracts, selectedAccount] + ); + + React.useEffect(() => { + if (!contracts?.length) return; + + const balancePromises = contracts.map(async (address) => { + const cleanAddress = validateAndParseAddress(address); + const resp = await getTokenBalance(cleanAddress); + if (!resp) return []; + + const balance = resp.balance / BigInt(10n ** 18n); + const balanceStr = balance.toString(); + return { + address, + symbol: resp.symbol, + balance: balanceStr, + }; + }); + Promise.all(balancePromises).then((results) => { + const validTokens = results.filter((token): token is Token => token !== null); + setTokenBalances((prev) => [...prev, ...validTokens]); + }); + }, [contracts]); + + return ( + + Token + + + ); +}; From 7833179c1eaf5f1c1b55200ea578e7c5c28a334a Mon Sep 17 00:00:00 2001 From: MirkoSisko Date: Fri, 24 Jan 2025 12:45:47 +0100 Subject: [PATCH 2/2] fix: disable the tokens with 0 balance --- src/components/account/tokenDropdown.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/account/tokenDropdown.tsx b/src/components/account/tokenDropdown.tsx index 8add617..7aeedc0 100644 --- a/src/components/account/tokenDropdown.tsx +++ b/src/components/account/tokenDropdown.tsx @@ -71,7 +71,7 @@ export const TokenDropdown: React.FC = ({ value, onChange } Token