diff --git a/src/constants/apps-consts.ts b/src/constants/apps-consts.ts index cf2e878a..db6164c6 100644 --- a/src/constants/apps-consts.ts +++ b/src/constants/apps-consts.ts @@ -64,6 +64,23 @@ export const BUSINESS_FIELDS = [ 'Climate Technology', ] +export const ERC20_ABI = [ + 'function name() view returns (string)', + 'function symbol() view returns (string)', + 'function decimals() view returns (uint8)', + 'function totalSupply() view returns (uint256)', + 'function balanceOf(address) view returns (uint256)', +] +export const ERC20_BALANCE_ABI = [ + { + constant: true, + inputs: [{ name: '_owner', type: 'address' }], + name: 'balanceOf', + outputs: [{ name: 'balance', type: 'uint256' }], + type: 'function', + }, +] + export const CONTRACTCMACCOUNTMANAGERADDRESSCOLUMBUS = '0xE5B2f76C778D082b07BDd7D51FFe83E3E055B47F' export const CONTRACTCMACCOUNTMANAGERADDRESSCAMINO = '0xf9FE1eaAB73a2902136FE7A83E0703338D3b9F1e' diff --git a/src/helpers/usePartnerConfig.tsx b/src/helpers/usePartnerConfig.tsx index 62d3c81b..af172170 100644 --- a/src/helpers/usePartnerConfig.tsx +++ b/src/helpers/usePartnerConfig.tsx @@ -371,7 +371,7 @@ export const usePartnerConfig = () => { ) const transferERC20 = useCallback( - async (address, value) => { + async (tokenAddress, to, value) => { if (!account) { console.error('Account is not initialized') return @@ -387,16 +387,12 @@ export const usePartnerConfig = () => { }, ] - // Create a contract instance - const contract = new ethers.Contract(address, abi, provider) - - // Call the balanceOf function - const balance = await contract.balanceOf(contractCMAccountAddress) - const tx = await accountWriteContract.transferERC20(address, wallet.address, value) + const tx = await accountWriteContract.transferERC20( + tokenAddress, + ethers.getAddress(to), + value, + ) await tx.wait() - const afterBalance = await contract.balanceOf(contractCMAccountAddress) - const afterFormattedBalance = ethers.formatUnits(afterBalance, 18) - return afterFormattedBalance } catch (error) { const decodedError = accountWriteContract.interface.parseError(error.data) console.error('Message:', error.message) diff --git a/src/redux/services/partners.ts b/src/redux/services/partners.ts index 076e66fe..1ea146a9 100644 --- a/src/redux/services/partners.ts +++ b/src/redux/services/partners.ts @@ -5,6 +5,7 @@ import { PartnerDataType, PartnersResponseType } from '../../@types/partners' import { CONTRACTCMACCOUNTMANAGERADDRESSCAMINO, CONTRACTCMACCOUNTMANAGERADDRESSCOLUMBUS, + ERC20_ABI, } from '../../constants/apps-consts' import CMAccount from '../../helpers/CMAccountManagerModule#CMAccount.json' import CMAccountManager from '../../helpers/ManagerProxyModule#CMAccountManager.json' @@ -16,7 +17,12 @@ const BASE_URLS = { } function createPartnerContract(address: string, provider: ethers.Provider) { - return new ethers.Contract(address, CMAccount, provider) + try { + const contract = new ethers.Contract(address, CMAccount, provider) + return contract + } catch (e) { + return null + } } const getListOfBots = async contract => { @@ -36,13 +42,22 @@ const getListOfBots = async contract => { } } -const getSupportedCurrencies = async contract => { +const getSupportedCurrencies = async (contract, provider) => { try { const offChainPaymentSupported = await contract.offChainPaymentSupported() - const isCam = (await contract.getSupportedTokens()).find( - elem => elem === ethers.ZeroAddress, - ) - return { offChainPaymentSupported, isCam: !!isCam } + const supportedTokens = await contract.getSupportedTokens() + let tokens = [] + for (const token of supportedTokens) { + if (token !== ethers.ZeroAddress) { + const tokenContract = new ethers.Contract(token, ERC20_ABI, provider) + const name = await tokenContract.name() + const symbol = await tokenContract.symbol() + const decimals = await tokenContract.decimals() + tokens.push({ name, symbol, decimals }) + } + } + const isCam = supportedTokens.find(elem => elem === ethers.ZeroAddress) + return { offChainPaymentSupported, isCam: !!isCam, tokens } } catch (error) { throw error } @@ -52,15 +67,18 @@ async function fetchContractServices(contractAddress: string, provider: ethers.P const contract = createPartnerContract(contractAddress, provider) try { - const [supportedServices, wantedServices, bots, supportedCurrencies] = await Promise.all([ - contract.getSupportedServices(), - contract.getWantedServices(), - getListOfBots(contract), - getSupportedCurrencies(contract), - ]) - return { supportedServices, wantedServices, bots, supportedCurrencies } + if (contract) { + const [supportedServices, wantedServices, bots, supportedCurrencies] = + await Promise.all([ + contract.getSupportedServices(), + contract.getWantedServices(), + getListOfBots(contract), + getSupportedCurrencies(contract, provider), + ]) + return { supportedServices, wantedServices, bots, supportedCurrencies } + } + return { supportedServices: [], wantedServices: [] } } catch (error) { - console.error(`Error fetching services for ${contractAddress}:`, error) return { supportedServices: [], wantedServices: [] } } } diff --git a/src/views/partners/MyMessenger.tsx b/src/views/partners/MyMessenger.tsx index a099d982..3e593d05 100644 --- a/src/views/partners/MyMessenger.tsx +++ b/src/views/partners/MyMessenger.tsx @@ -23,6 +23,7 @@ import store from 'wallet/store' import Alert from '../../components/Alert' import DialogAnimate from '../../components/Animate/DialogAnimate' import MainButton from '../../components/MainButton' +import { ERC20_BALANCE_ABI } from '../../constants/apps-consts' import { usePartnerConfigurationContext } from '../../helpers/partnerConfigurationContext' import { usePartnerConfig } from '../../helpers/usePartnerConfig' import { useSmartContract } from '../../helpers/useSmartContract' @@ -163,7 +164,7 @@ const AddressInput = ({ address, onAddressChange, onMyAddressClick }) => { ) } -const CamWithdraw = ({ setOpen }) => { +const CamWithdraw = ({ setOpen, token, fetchTokenBalances }) => { const { wallet, contractCMAccountAddress } = useSmartContract() const [address, setAddress] = useState('') const [amount, setAmount] = useState('') @@ -171,16 +172,17 @@ const CamWithdraw = ({ setOpen }) => { const [isValidAddress, setIsValidAddress] = useState(false) const [loading, setLoading] = useState(false) const [amountError, setAmountError] = useState('') - const { withDraw } = usePartnerConfig() + const { withDraw, transferERC20 } = usePartnerConfig() const { getBalanceOfAnAddress, balanceOfAnAddress: balance } = useWalletBalance() const maxAmount = useMemo(() => { + if (token) return parseFloat(token.balance) const balanceParsed = parseFloat(balance) if (isNaN(balanceParsed)) { return '0.00' } return Math.max(balanceParsed - 100, 0).toFixed(2) - }, [balance]) + }, [balance, token]) const handleAddressChange = useCallback(newAddress => { setAddress(newAddress) @@ -225,8 +227,13 @@ const CamWithdraw = ({ setOpen }) => { async function handleWithdraw() { setLoading(true) - await withDraw(address, ethers.parseEther(amount)) - getBalanceOfAnAddress(contractCMAccountAddress) + if (!token) { + await withDraw(address, ethers.parseEther(amount)) + getBalanceOfAnAddress(contractCMAccountAddress) + } else { + await transferERC20(token.address, address, ethers.parseEther(amount)) + await fetchTokenBalances() + } setAmount('') setConfirm(false) setAddress('') @@ -320,6 +327,7 @@ const CamWithdraw = ({ setOpen }) => { const MyMessenger = () => { const { state, dispatch } = usePartnerConfigurationContext() const [open, setOpen] = useState(false) + const [selectedToken, setSelectedToken] = useState(null) const [isOffChainPaymentSupported, setIsOffChainPaymentSupported] = useState(false) const [isCAMSupported, setCAMSupported] = useState(false) const [isEditMode, setIsEditMode] = useState(false) @@ -330,7 +338,8 @@ const MyMessenger = () => { const { balanceOfAnAddress, getBalanceOfAnAddress } = useWalletBalance() const [isLoading, setIsLoading] = useState(false) const [bots, setBots] = useState([]) - const { contractCMAccountAddress, wallet } = useSmartContract() + const [tokens, setTokens] = useState([]) + const { contractCMAccountAddress, wallet, provider } = useSmartContract() const { getSupportedTokens, getOffChainPaymentSupported, @@ -338,7 +347,6 @@ const MyMessenger = () => { addSupportedToken, removeSupportedToken, getListOfBots, - transferERC20, } = usePartnerConfig() const { data: partner, refetch } = useFetchPartnerDataQuery({ companyName: '', @@ -349,18 +357,16 @@ const MyMessenger = () => { if (activeNetwork) refetch() }, [activeNetwork]) const appDispatch = useAppDispatch() - const handleOpenModal = () => { + const handleOpenModal = token => { setOpen(true) } async function checkIfOffChainPaymentSupported() { let res = await getOffChainPaymentSupported() setIsOffChainPaymentSupported(res) } - async function checkIfCamSupported() { - let res = await getSupportedTokens() - setCAMSupported(!!res.find(elem => elem === ethers.ZeroAddress)) - } + const handleCloseModal = () => { + setSelectedToken(null) setOpen(false) } @@ -385,31 +391,21 @@ const MyMessenger = () => { else await removeSupportedToken(ethers.ZeroAddress) } if (tempSupportedTokens) { - const result = [] + const excludeSet = new Set(supportedTokens) - for (const address of supportedTokens) { - const foundToken = tempSupportedTokens.find( - token => - token.address.toLowerCase() === address.toLowerCase() && - token.supported, - ) + for (const item of tempSupportedTokens) { + const shouldBeSupported = !excludeSet.has(item.address) - if (foundToken) { - result.push({ - address: foundToken.address, - name: foundToken.name, - symbol: foundToken.symbol, - added: true, - }) - } else { - result.push({ - address: address, - added: false, - }) + try { + if (shouldBeSupported && item.supported) { + await addSupportedToken(item.address) + } else if (!shouldBeSupported && !item.supported) { + await removeSupportedToken(item.address) + } + } catch (error) { + console.error(`Error updating token ${item.address}:`, error) } } - - return result } appDispatch( updateNotificationStatus({ @@ -422,7 +418,7 @@ const MyMessenger = () => { console.error('Error: ', error) } finally { await checkIfOffChainPaymentSupported() - await checkIfCamSupported() + await fetchSupportedTokens() setIsLoading(false) } } @@ -432,37 +428,44 @@ const MyMessenger = () => { } async function fetchSupportedTokens() { const res = await getSupportedTokens() + setCAMSupported(!!res.find(elem => elem === ethers.ZeroAddress)) setSupportedTokens(res) } - const tokens = useMemo(() => { - if ( - store.getters['Assets/networkErc20Tokens'] && - store.getters['Assets/networkErc20Tokens'].length > 0 - ) { - let tokens = store.getters['Assets/networkErc20Tokens'].map(elem => { - return { - address: elem.contract._address, - balance: elem.balanceBig.toLocaleString(), - name: elem.data.name, - symbol: elem.data.symbol, - decimal: elem.data.decimal, - supported: supportedTokens.find(token => token === elem.contract._address), - } - }) - setTempSupportedTokens([...tokens]) - return tokens + const fetchTokenBalances = async () => { + const networkErc20Tokens = store.getters['Assets/networkErc20Tokens'] || [] + if (networkErc20Tokens.length > 0) { + const fetchedTokens = await Promise.all( + networkErc20Tokens.map(async elem => { + const contract = new ethers.Contract( + elem.contract._address, + ERC20_BALANCE_ABI, + provider, + ) + const balance = await contract.balanceOf(contractCMAccountAddress) + return { + address: elem.contract._address, + balance: ethers.formatUnits(balance, elem.data.decimal), + name: elem.data.name, + symbol: elem.data.symbol, + decimal: elem.data.decimal, + supported: supportedTokens.includes(elem.contract._address), + } + }), + ) + setTokens(fetchedTokens) } - return [] - }, [supportedTokens]) + } + useEffect(() => { + fetchTokenBalances() + }, [contractCMAccountAddress, supportedTokens]) useEffect(() => { - checkIfCamSupported() - checkIfOffChainPaymentSupported() getBalanceOfAnAddress(contractCMAccountAddress) - }, [getBalanceOfAnAddress]) + }, [contractCMAccountAddress]) useEffect(() => { + checkIfOffChainPaymentSupported() fetchBots() fetchSupportedTokens() }, []) @@ -650,6 +653,7 @@ const MyMessenger = () => { { getBalanceOfAnAddress(contractCMAccountAddress) + fetchTokenBalances() }} sx={{ cursor: 'pointer', @@ -734,7 +738,15 @@ const MyMessenger = () => { } /> {!isEditMode && ( - )} @@ -799,7 +811,8 @@ const MyMessenger = () => {