diff --git a/packages/app/package.json b/packages/app/package.json index bf217613..d5839bd7 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -20,15 +20,16 @@ "@celo-tools/celo-ethers-wrapper": "^0.4.0", "@ethersproject/shims": "^5.7.0", "@gooddollar/good-design": "^0.1.31", - "@gooddollar/goodcollective-sdk": "^1.*", + "@gooddollar/goodcollective-sdk": "^1.0.6", "@gooddollar/web3sdk-v2": "^0.2.2", "@react-native-aria/interactions": "0.2.3", "@react-native-async-storage/async-storage": "^1.18.2", "@react-native-firebase/analytics": "16.7.0", "@react-native-firebase/app": "16.7.0", "@superfluid-finance/sdk-core": "^0.3.2", + "@uniswap/router-sdk": "^1.7.1", "@uniswap/sdk-core": "^4.0.7", - "@uniswap/smart-order-router": "^3.16.23", + "@uniswap/smart-order-router": "^3.20.0", "@uniswap/v3-sdk": "^3.10.0", "@usedapp/core": "^1.2.10", "@wagmi/core": "^1.4.5", diff --git a/packages/app/src/components/AproveSwapModal.tsx b/packages/app/src/components/ApproveSwapModal.tsx similarity index 87% rename from packages/app/src/components/AproveSwapModal.tsx rename to packages/app/src/components/ApproveSwapModal.tsx index 26f295af..f1eb9402 100644 --- a/packages/app/src/components/AproveSwapModal.tsx +++ b/packages/app/src/components/ApproveSwapModal.tsx @@ -9,18 +9,22 @@ interface AproveSwapModalProps { setOpenModal: any; } -const AproveSwapModal = ({ openModal, setOpenModal }: AproveSwapModalProps) => { +const ApproveSwapModal = ({ openModal, setOpenModal }: AproveSwapModalProps) => { return ( - + { + setOpenModal(false); + }}> - APROVE TOKEN SWAP + APPROVE TOKEN SWAP To approve the exchange from your donation currency to this GoodCollective's currency, sign with your wallet. @@ -112,4 +116,4 @@ const styles = StyleSheet.create({ }, }); -export default AproveSwapModal; +export default ApproveSwapModal; diff --git a/packages/app/src/components/CompleteDonationModal.tsx b/packages/app/src/components/CompleteDonationModal.tsx index f19799c4..fb7dfd8b 100644 --- a/packages/app/src/components/CompleteDonationModal.tsx +++ b/packages/app/src/components/CompleteDonationModal.tsx @@ -22,7 +22,6 @@ const CompleteDonationModal = ({ openModal, setOpenModal }: CompleteDonationModa style={modalStyles.modalCloseIcon} onPress={() => { setOpenModal(false); - console.log(openModal); }}> diff --git a/packages/app/src/components/ConnectWallet.tsx b/packages/app/src/components/ConnectWallet.tsx deleted file mode 100644 index 4e862a1b..00000000 --- a/packages/app/src/components/ConnectWallet.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { Button } from 'native-base'; -import { useConnectWallet } from '@web3-onboard/react'; - -export const ConnectWallet = () => { - const [, connect] = useConnectWallet(); - const walletConnect = () => connect(); - return ; -}; diff --git a/packages/app/src/components/DonateComponent.tsx b/packages/app/src/components/DonateComponent.tsx index 54cd70a4..72dfe4a1 100644 --- a/packages/app/src/components/DonateComponent.tsx +++ b/packages/app/src/components/DonateComponent.tsx @@ -1,49 +1,154 @@ -import { useState } from 'react'; -import { Image, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native'; +import { useCallback, useMemo, useState } from 'react'; +import { Image, StyleSheet, Text, TextInput, View } from 'react-native'; import { InterRegular, InterSemiBold, InterSmall } from '../utils/webFonts'; import RoundedButton from './RoundedButton'; import CompleteDonationModal from './CompleteDonationModal'; import { Colors } from '../utils/colors'; import { Link, useMediaQuery } from 'native-base'; import Dropdown from './Dropdown'; -import { getButtonBGC, getButtonText, getButtonTextColor, getFrequencyTime, getTotalAmount } from '../utils'; -import { useGetTokenPrice, useContractCalls } from '../hooks'; +import { + getDonateButtonBackgroundColor, + getDonateButtonText, + getDonateButtonTextColor, + getFrequencyPlural, +} from '../utils'; +import { useContractCalls, useGetTokenPrice } from '../hooks'; import { useAccount, useNetwork } from 'wagmi'; import { IpfsCollective } from '../models/models'; -import { useGetBalance } from '../hooks/useGetBalance'; -import { currencyOptions, frequencyOptions, SupportedTokens } from '../models/constants'; +import { useGetDecimalBalance } from '../hooks/useGetDecimalBalance'; +import { acceptablePriceImpact, Frequency, frequencyOptions, SupportedNetwork } from '../models/constants'; import { InfoIconOrange } from '../assets'; import { useLocation } from 'react-router-native'; +import Decimal from 'decimal.js'; +import { formatFiatCurrency } from '../lib/formatFiatCurrency'; +import ErrorModal from './ErrorModal'; +import { SwapRouteState, useSwapRoute } from '../hooks/useSwapRoute'; +import { useApproveSwapTokenCallback } from '../hooks/useApproveSwapTokenCallback'; +import ApproveSwapModal from './ApproveSwapModal'; +import { waitForTransaction } from '@wagmi/core'; +import { TransactionReceipt } from 'viem'; +import { useToken, useTokenList } from '../hooks/useTokenList'; +import { formatDecimalStringInput } from '../lib/formatDecimalStringInput'; interface DonateComponentProps { - insufficientLiquidity: boolean; - priceImpact: boolean; collective: IpfsCollective; } -function DonateComponent({ insufficientLiquidity, priceImpact, collective }: DonateComponentProps) { - const [modalVisible, setModalVisible] = useState(false); - const [currency, setCurrency] = useState('G$'); - const [frequency, setFrequency] = useState('One-Time'); - const [duration, setDuration] = useState(1); - const [donationAmount, setDonationAmount] = useState(0); +function DonateComponent({ collective }: DonateComponentProps) { + const [isDesktopResolution] = useMediaQuery({ + minWidth: 612, + }); + const location = useLocation(); + const collectiveId = location.pathname.slice('/donate/'.length); - const { supportFlowWithSwap, supportFlow } = useContractCalls(); const { address, isConnected } = useAccount(); const { chain } = useNetwork(); - const balance = useGetBalance(currency as keyof SupportedTokens, address, chain?.id); - const isInsufficientBalance = balance ? donationAmount > balance : true; + const tokenList = useTokenList(); - const { price } = useGetTokenPrice(currency); - const usdValue = price ? donationAmount * price : undefined; + const currencyOptions: { value: string; label: string }[] = useMemo(() => { + return Object.keys(tokenList).map((key) => ({ + value: key, + label: key, + })); + }, [tokenList]); - const [isDesktopResolution] = useMediaQuery({ - minWidth: 612, - }); + const [completeDonationModalVisible, setCompleteDonationModalVisible] = useState(false); + const [errorMessage, setErrorMessage] = useState(undefined); + const [approveSwapModalVisible, setApproveSwapModalVisible] = useState(false); - const location = useLocation(); - const collectiveId = location.pathname.slice('/donate/'.length); + const [currency, setCurrency] = useState('G$'); + const [frequency, setFrequency] = useState(Frequency.OneTime); + const [duration, setDuration] = useState(1); + const [decimalDonationAmount, setDecimalDonationAmount] = useState(0); + + const { + path: swapPath, + rawMinimumAmountOut, + priceImpact, + status: swapRouteStatus, + } = useSwapRoute(currency, decimalDonationAmount, duration); + + const { + handleApproveToken, + isLoading: handleApproveTokenIsLoading, + isError: handleApproveTokenIsError, + } = useApproveSwapTokenCallback(currency, decimalDonationAmount, duration, (value: boolean) => + setApproveSwapModalVisible(value) + ); + + const { supportFlowWithSwap, supportFlow, supportSingleTransferAndCall } = useContractCalls( + collectiveId, + currency, + decimalDonationAmount, + duration, + frequency, + (error) => setErrorMessage(error), + (value: boolean) => setCompleteDonationModalVisible(value), + rawMinimumAmountOut, + swapPath + ); + + const handleDonate = useCallback(async () => { + while (handleApproveTokenIsLoading) { + await new Promise((r) => setTimeout(r, 50)); + } + if (handleApproveTokenIsError || handleApproveToken === undefined) { + setErrorMessage('An error occurred while generating a transaction to approve the token.'); + return; + } + const txHash = await handleApproveToken(); + if (txHash === undefined) { + return; + } + let txReceipt: TransactionReceipt | undefined; + try { + txReceipt = await waitForTransaction({ + chainId: chain?.id, + confirmations: 1, + hash: txHash, + timeout: 1000 * 60 * 5, + }); + } catch (error) { + setErrorMessage( + 'Something went wrong: Your token approval transaction was not confirmed within the timeout period.' + ); + } + if (txReceipt?.status === 'success') { + if (frequency === Frequency.OneTime) { + await supportSingleTransferAndCall(); + } else if (currency === 'G$') { + await supportFlow(); + } else { + await supportFlowWithSwap(); + } + } + }, [ + chain?.id, + currency, + frequency, + handleApproveToken, + handleApproveTokenIsError, + handleApproveTokenIsLoading, + supportFlow, + supportFlowWithSwap, + supportSingleTransferAndCall, + ]); + + const currencyDecimals = useToken(currency).decimals; + const donorCurrencyBalance = useGetDecimalBalance(currency, address, chain?.id); + + const totalDecimalDonation = duration * decimalDonationAmount; + const totalDonationFormatted = new Decimal(totalDecimalDonation) + .toDecimalPlaces(currencyDecimals, Decimal.ROUND_DOWN) + .toString(); + + const isInsufficientBalance = donorCurrencyBalance ? totalDecimalDonation > donorCurrencyBalance : true; + const isInsufficientLiquidity = currency !== 'G$' && swapRouteStatus !== SwapRouteState.READY; + const isUnacceptablePriceImpact = currency !== 'G$' && priceImpact ? priceImpact > acceptablePriceImpact : false; + + const { price } = useGetTokenPrice(currency); + const usdValue = price ? formatFiatCurrency(decimalDonationAmount * price) : undefined; return ( @@ -79,7 +184,7 @@ function DonateComponent({ insufficientLiquidity, priceImpact, collective }: Don placeholder={'0.00'} style={styles.subHeading} maxLength={7} - onChangeText={(value: string) => setDonationAmount(parseFloat(value))} + onChangeText={(value: string) => setDecimalDonationAmount(formatDecimalStringInput(value))} /> @@ -109,7 +214,7 @@ function DonateComponent({ insufficientLiquidity, priceImpact, collective }: Don placeholder={'0.00'} style={styles.subHeading} maxLength={7} - onChangeText={(value: string) => setDonationAmount(parseFloat(value))} + onChangeText={(value: string) => setDecimalDonationAmount(formatDecimalStringInput(value))} /> @@ -126,32 +231,28 @@ function DonateComponent({ insufficientLiquidity, priceImpact, collective }: Don How often do you want to donate this {!isDesktopResolution &&
} amount?
- setFrequency(value)} options={frequencyOptions} /> + setFrequency(value as Frequency)} + options={frequencyOptions} + />
{frequency !== 'One-Time' && ( - + For How Long: - + setDuration(Number(value))} /> - {getFrequencyTime(frequency)} + {getFrequencyPlural(frequency as Frequency)} )} @@ -159,29 +260,30 @@ function DonateComponent({ insufficientLiquidity, priceImpact, collective }: Don )} - + <> {!isDesktopResolution && ( <> setFrequency(value)} + onSelect={(value: string) => setFrequency(value as Frequency)} options={frequencyOptions} /> {frequency !== 'One-Time' && ( - - For How Long: + + For How Long: setDuration(value)} /> - {getFrequencyTime(frequency)} + {getFrequencyPlural(frequency)} )} @@ -211,7 +313,7 @@ function DonateComponent({ insufficientLiquidity, priceImpact, collective }: Don Donation Amount: - {currency} {donationAmount} + {currency} {decimalDonationAmount} {usdValue} USD @@ -222,17 +324,17 @@ function DonateComponent({ insufficientLiquidity, priceImpact, collective }: Don Donation Duration: - {duration} {getFrequencyTime(frequency)} + {duration} {getFrequencyPlural(frequency)} )} - {frequency !== 'One-Time' && ( + {frequency !== Frequency.OneTime && ( Total Amount: - {currency} {getTotalAmount(duration, donationAmount)} + {currency} {totalDonationFormatted} {usdValue} USD @@ -241,7 +343,7 @@ function DonateComponent({ insufficientLiquidity, priceImpact, collective }: Don - {insufficientLiquidity && ( + {isInsufficientLiquidity && ( @@ -297,7 +399,7 @@ function DonateComponent({ insufficientLiquidity, priceImpact, collective }: Don )} - {priceImpact && ( + {isUnacceptablePriceImpact && ( @@ -336,25 +438,42 @@ function DonateComponent({ insufficientLiquidity, priceImpact, collective }: Don )} - - { - if (currency === 'G$') { - supportFlow(collectiveId, donationAmount, address); - } else { - supportFlowWithSwap(); - } - }} - /> - + - + setErrorMessage(undefined)} + message={errorMessage ?? ''} + /> + + ); } @@ -415,6 +534,11 @@ const styles = StyleSheet.create({ flexDirection: 'row', gap: 8, }, + desktopActionBox: { + flex: 1, + flexDirection: 'column', + justifyContent: 'space-between', + }, headerLabel: { fontSize: 18, lineHeight: 27, @@ -438,28 +562,33 @@ const styles = StyleSheet.create({ ...InterRegular, }, frequencyDetails: { + height: 32, gap: 8, backgroundColor: Colors.purple[100], - paddingTop: 2, justifyContent: 'center', + alignItems: 'center', borderColor: Colors.gray[600], borderBottomWidth: 1, flex: 1, flexDirection: 'row', }, + desktopFrequencyDetails: { + maxHeight: 59, + }, durationInput: { fontSize: 18, lineHeight: 27, ...InterSemiBold, - // textAlign: 'right', width: '20%', color: Colors.purple[400], textAlign: 'center', }, durationLabel: { ...InterSmall, - textAlign: 'left', - width: 'auto', + fontSize: 18, + lineHeight: 27, + color: Colors.purple[400], + textAlignVertical: 'bottom', }, downIcon: { width: 24, @@ -535,7 +664,7 @@ const styles = StyleSheet.create({ ...InterSmall, }, italic: { fontStyle: 'italic' }, - frecuencyWrapper: { gap: 17, zIndex: -1 }, + frequencyWrapper: { gap: 17, zIndex: -1 }, donationAction: { width: 'auto', flexGrow: 1 }, donationCurrencyHeader: { flexDirection: 'row', width: 'auto', gap: 20 }, }); diff --git a/packages/app/src/components/DonorsList/DonorsList.tsx b/packages/app/src/components/DonorsList/DonorsList.tsx index 4ab4910a..893e1df6 100644 --- a/packages/app/src/components/DonorsList/DonorsList.tsx +++ b/packages/app/src/components/DonorsList/DonorsList.tsx @@ -2,7 +2,7 @@ import { StyleSheet, View } from 'react-native'; import { DonorCollective } from '../../models/models'; import { DonorsListItem } from './DonorsListItem'; import { useMemo } from 'react'; -import { ethers } from 'ethers'; +import Decimal from 'decimal.js'; interface DonorsListProps { donors: DonorCollective[]; @@ -13,8 +13,10 @@ interface DonorsListProps { function DonorsList({ donors, listStyle }: DonorsListProps) { const sortedDonors: DonorCollective[] = useMemo(() => { - return donors.sort((donor) => { - return parseFloat(ethers.utils.formatEther(donor.contribution ?? 0)); + return donors.sort((a, b) => { + const aDecimal = new Decimal(a.contribution ?? 0); + const bDecimal = new Decimal(b.contribution ?? 0); + return bDecimal.cmp(aDecimal); }); }, [donors]); diff --git a/packages/app/src/components/DonorsList/DonorsListItem.tsx b/packages/app/src/components/DonorsList/DonorsListItem.tsx index 0b317a37..d6720f42 100644 --- a/packages/app/src/components/DonorsList/DonorsListItem.tsx +++ b/packages/app/src/components/DonorsList/DonorsListItem.tsx @@ -4,6 +4,8 @@ import { InterRegular, InterSemiBold } from '../../utils/webFonts'; import { DonorCollective } from '../../models/models'; import useCrossNavigate from '../../routes/useCrossNavigate'; import Decimal from 'decimal.js'; +import { formatAddress } from '../../lib/formatAddress'; +import { ethers } from 'ethers'; interface DonorsListItemProps { donor: DonorCollective; @@ -14,8 +16,8 @@ export const DonorsListItem = (props: DonorsListItemProps) => { const { donor, rank } = props; const { navigate } = useCrossNavigate(); - const formattedDonations: string = new Decimal(donor.contribution ?? 0).toFixed(3); - const formattedAddress = donor.donor.slice(0, 6) + '...' + donor.donor.slice(-4); + const formattedDonations: string = new Decimal(ethers.utils.formatEther(donor.contribution) ?? 0).toFixed(3); + const formattedAddress = formatAddress(donor.donor, 5); const circleBackgroundColor = rank === 1 ? Colors.yellow[100] : rank === 2 ? Colors.gray[700] : Colors.orange[400]; const circleTextColor = rank === 1 ? Colors.yellow[200] : rank === 2 ? Colors.blue[200] : Colors.brown[100]; diff --git a/packages/app/src/components/Dropdown.tsx b/packages/app/src/components/Dropdown.tsx index 1681e1c6..f278927a 100644 --- a/packages/app/src/components/Dropdown.tsx +++ b/packages/app/src/components/Dropdown.tsx @@ -51,6 +51,7 @@ function Dropdown({ onSelect, value, options }: DropdownProps) { {options.map((option) => ( { onSelect(option.value); @@ -108,8 +109,10 @@ const styles = StyleSheet.create({ height: 24, }, dropdownContainer: { - height: 'auto', width: 'auto', + height: 'auto', + maxHeight: 400, + overflowY: 'scroll', backgroundColor: Colors.white, paddingTop: 10, paddingBottom: 10, @@ -132,6 +135,7 @@ const styles = StyleSheet.create({ paddingVertical: 15, minWidth: 105, width: '100%', + minHeight: 60, alignItems: 'center', }, dropdownSeparator: { diff --git a/packages/app/src/components/ErrorModal.tsx b/packages/app/src/components/ErrorModal.tsx index b8207d1a..f9d44ff0 100644 --- a/packages/app/src/components/ErrorModal.tsx +++ b/packages/app/src/components/ErrorModal.tsx @@ -1,16 +1,15 @@ import { Modal, StyleSheet, Text, View, Image, TouchableOpacity } from 'react-native'; import { InterRegular, InterSemiBold } from '../utils/webFonts'; -// import useCrossNavigate from '../routes/useCrossNavigate'; import { Colors } from '../utils/colors'; import { CloseIcon, ThankYouImg } from '../assets'; interface ErrorModalProps { openModal: boolean; setOpenModal: any; + message: string; } -const ErrorModal = ({ openModal, setOpenModal }: ErrorModalProps) => { - // const { navigate } = useCrossNavigate(); +const ErrorModal = ({ openModal, setOpenModal, message }: ErrorModalProps) => { return ( @@ -23,8 +22,8 @@ const ErrorModal = ({ openModal, setOpenModal }: ErrorModalProps) => { SOMETHING WENT WRONG - Please try againd later. - Reason: {'Error Code'} + Please try again later. + Reason: {message} woman setOpenModal(false)}> OK diff --git a/packages/app/src/components/Header/ConnectWalletMenu.tsx b/packages/app/src/components/Header/ConnectWalletMenu.tsx index c2772a26..4104dc58 100644 --- a/packages/app/src/components/Header/ConnectWalletMenu.tsx +++ b/packages/app/src/components/Header/ConnectWalletMenu.tsx @@ -37,7 +37,7 @@ export const ConnectWalletMenu = (props: ConnectWalletMenuProps) => { {connectors.map( (connector, i) => (connector.ready || isLoading) && ( - <> + { resizeMode="contain" style={[styles.walletConnectorLogo]} /> - + {connector.name} {isLoading && connector.id === pendingConnector?.id && ' (connecting)'} {i < connectors.length - 1 && } - + ) )} diff --git a/packages/app/src/components/Header/ConnectedAccountDisplay.tsx b/packages/app/src/components/Header/ConnectedAccountDisplay.tsx index 7b66c911..c2889cb3 100644 --- a/packages/app/src/components/Header/ConnectedAccountDisplay.tsx +++ b/packages/app/src/components/Header/ConnectedAccountDisplay.tsx @@ -1,11 +1,10 @@ import { Image, StyleSheet, Text, View } from 'react-native'; import { InterRegular } from '../../utils/webFonts'; -import { formatAmount } from '../../lib/formatAmount'; -import displayAddress from '../../lib/displayAddress'; +import { formatAddress } from '../../lib/formatAddress'; import { useEnsName, useNetwork } from 'wagmi'; import { Colors } from '../../utils/colors'; import { PlaceholderAvatar } from '../../assets'; -import { useGetBalance } from '../../hooks/useGetBalance'; +import { useGetDecimalBalance } from '../../hooks/useGetDecimalBalance'; interface ConnectedAccountDisplayProps { isDesktopResolution: boolean; @@ -18,7 +17,7 @@ export const ConnectedAccountDisplay = (props: ConnectedAccountDisplayProps) => const { chain } = useNetwork(); const chainName = chain?.name.replace(/\d+|\s/g, ''); - const tokenBalance = useGetBalance('G$', address, chain?.id); + const tokenBalance = useGetDecimalBalance('G$', address, chain?.id); const { data: ensName } = useEnsName({ address }); return ( @@ -40,13 +39,13 @@ export const ConnectedAccountDisplay = (props: ConnectedAccountDisplayProps) => width: 240, justifyContent: 'space-between', }}> - {formatAmount(tokenBalance as any)} + {tokenBalance} {ensName ? ( {ensName} ) : ( - {displayAddress(address)} + {formatAddress(address)} )} @@ -69,13 +68,13 @@ export const ConnectedAccountDisplay = (props: ConnectedAccountDisplayProps) => flex: 1, justifyContent: 'space-between', }}> - {formatAmount(tokenBalance as any)} + {tokenBalance} {ensName ? ( {ensName} ) : ( - {displayAddress(address)} + {formatAddress(address)} )} diff --git a/packages/app/src/components/RoundedButton.tsx b/packages/app/src/components/RoundedButton.tsx index ef680654..c74b9e1f 100644 --- a/packages/app/src/components/RoundedButton.tsx +++ b/packages/app/src/components/RoundedButton.tsx @@ -11,12 +11,23 @@ interface RoundedButtonProps { seeType: boolean; onPress?: () => void; maxWidth?: number | string; + disabled?: boolean; } -function RoundedButton({ title, backgroundColor, color, fontSize, seeType, onPress, maxWidth }: RoundedButtonProps) { +function RoundedButton({ + title, + backgroundColor, + color, + fontSize, + seeType, + onPress, + maxWidth, + disabled, +}: RoundedButtonProps) { if (!seeType) { return ( + { const { showActions, steward, profileImage, isVerified } = props; - const formattedAddress = steward.steward.slice(0, 6) + '...' + steward.steward.slice(-4); + const formattedAddress = formatAddress(steward.steward, 5); return ( diff --git a/packages/app/src/components/ViewCollective.tsx b/packages/app/src/components/ViewCollective.tsx index 05b95de9..ab230ca9 100644 --- a/packages/app/src/components/ViewCollective.tsx +++ b/packages/app/src/components/ViewCollective.tsx @@ -30,8 +30,8 @@ import { TwitterIcon, WebIcon, } from '../assets/'; -import { calculateAmounts } from '../lib/calculateAmounts'; import { useDonorCollectivesFlowingBalances } from '../hooks/useFlowingBalance'; +import { calculateGoodDollarAmounts } from '../lib/calculateGoodDollarAmounts'; interface ViewCollectiveProps { collective: Collective; @@ -80,11 +80,14 @@ function ViewCollective({ collective }: ViewCollectiveProps) { tokenPrice ); - const { formatted: formattedTotalRewards, usdValue: totalRewardsUsdValue } = calculateAmounts( + const { formatted: formattedTotalRewards, usdValue: totalRewardsUsdValue } = calculateGoodDollarAmounts( totalRewards, tokenPrice ); - const { formatted: formattedCurrentPool, usdValue: currentPoolUsdValue } = calculateAmounts(currentPool, tokenPrice); + const { formatted: formattedCurrentPool, usdValue: currentPoolUsdValue } = calculateGoodDollarAmounts( + currentPool, + tokenPrice + ); const renderDonorsButton = () => ( 0 && steward.collectives?.map((collective, i) => ( 0 && donor.collectives?.map((collective, i) => ( diff --git a/packages/app/src/contexts/WalletConnectionContext.tsx b/packages/app/src/contexts/WalletConnectionContext.tsx deleted file mode 100644 index a58dd28e..00000000 --- a/packages/app/src/contexts/WalletConnectionContext.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { createContext, useContext, useEffect, useState } from 'react'; -import { useEthers } from '@usedapp/core'; -import { useConnectWallet } from '@web3-onboard/react'; -import { shortenAddress } from '../utils'; -import { useSwitchNetwork } from '@gooddollar/web3sdk-v2'; -interface IWalletConnectionContext { - disconnectWallet: () => Promise; - connectWallet: () => Promise; - walletName: string; - walletAddress: string; -} - -const WalletConnectionContext = createContext({} as IWalletConnectionContext); - -const WalletConnectionProvider: React.FC<{ - children: React.ReactNode; -}> = ({ children }) => { - const { account } = useEthers(); - const { switchNetwork } = useSwitchNetwork(); - const [{ wallet }, connect, disconnect] = useConnectWallet(); - // const [, setChain] = useSetChain(); - - const [walletName, setWalletName] = useState(''); - const [walletAddress, setWalletAddress] = useState(''); - - const connectWallet = async () => { - try { - await connect(); - await switchNetwork(42220); - } catch (error) { - console.log('connectWallet Error - ', error); - } - }; - - const disconnectWallet = async () => { - if (!wallet) return; - await disconnect(wallet); - }; - - const getWalletName = (_account: string) => { - if (!_account) { - setWalletName('Luis.celo'); - return; - } - setWalletName(shortenAddress(_account)); - }; - - useEffect(() => { - setWalletAddress(account || ''); - if (account) getWalletName(shortenAddress(account)); - }, [account]); - - return ( - - {children} - - ); -}; - -// Custom hook to consume the WalletConnectionContext -export const useWalletConnection = () => useContext(WalletConnectionContext); - -export { WalletConnectionProvider }; diff --git a/packages/app/src/hooks/useApproveSwapTokenCallback.ts b/packages/app/src/hooks/useApproveSwapTokenCallback.ts new file mode 100644 index 00000000..2ee2d0f3 --- /dev/null +++ b/packages/app/src/hooks/useApproveSwapTokenCallback.ts @@ -0,0 +1,59 @@ +import { useAccount, useContractWrite, useNetwork, usePrepareContractWrite } from 'wagmi'; +import { useMemo } from 'react'; +import { calculateRawTotalDonation } from '../lib/calculateRawTotalDonation'; +import Decimal from 'decimal.js'; +import ERC20 from '../abi/ERC20.json'; +import { useToken } from './useTokenList'; +import { UNISWAP_V3_ROUTER_ADDRESS } from '../models/constants'; + +export function useApproveSwapTokenCallback( + currencyIn: string, + decimalAmountIn: number, + duration: number, + toggleApproveSwapModalVisible: (value: boolean) => void +): { + isLoading: boolean; + isSuccess: boolean; + isError: boolean; + handleApproveToken?: () => Promise<`0x${string}` | undefined>; +} { + const { address } = useAccount(); + const { chain } = useNetwork(); + const tokenIn = useToken(currencyIn); + + const rawAmountIn = useMemo( + () => calculateRawTotalDonation(decimalAmountIn, duration, tokenIn.decimals).toFixed(0, Decimal.ROUND_DOWN), + [decimalAmountIn, duration, tokenIn.decimals] + ); + + const { config } = usePrepareContractWrite({ + chainId: chain?.id, + address: tokenIn.address as `0x${string}`, + abi: ERC20, + account: address, + functionName: 'approve', + args: [UNISWAP_V3_ROUTER_ADDRESS, rawAmountIn], + }); + + const { isLoading, isSuccess, isError, writeAsync } = useContractWrite(config); + + const handleApproveToken = + writeAsync === undefined + ? undefined + : async () => { + toggleApproveSwapModalVisible(true); + const result = await writeAsync().catch((_) => { + // user rejected the transaction + return undefined; + }); + toggleApproveSwapModalVisible(false); + return result?.hash; + }; + + return { + isLoading, + isSuccess, + isError, + handleApproveToken, + }; +} diff --git a/packages/app/src/hooks/useContractCalls.tsx b/packages/app/src/hooks/useContractCalls.tsx deleted file mode 100644 index e3c69fbe..00000000 --- a/packages/app/src/hooks/useContractCalls.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import { GoodCollectiveSDK } from '@gooddollar/goodcollective-sdk'; -import { ethers } from 'ethers'; -import { useAccount, useWalletClient } from 'wagmi'; -import { useEthersSigner } from './wagmiF'; -import { useLocation } from 'react-router-native'; -import useCrossNavigate from '../routes/useCrossNavigate'; - -// import { erc20ABI } from 'wagmi'; -// import { getContract } from 'wagmi/actions'; - -export const useContractCalls = () => { - const { data: walletClient } = useWalletClient(); - const { address } = useAccount(); - const provider = new ethers.providers.JsonRpcProvider( - 'https://celo-mainnet.infura.io/v3/655061f57d2c42d6a6d98259bf196567' - ); - const signer = useEthersSigner(); - const { navigate } = useCrossNavigate(); - - const calculateFlow = (amount: any): number | null => { - const numAmount = Number(amount); - console.log(numAmount); - if (isNaN(numAmount)) { - alert('You can only calculate a flowRate based on a number'); - return null; - } - - const monthlyAmount = ethers.utils.parseEther(amount.toString()) as any as number; - return Math.floor(monthlyAmount / 3600 / 24 / (365 / 12)) as number; - }; - - const supportFlow = async (poolAddress: string, amountIn: any, user: any) => { - const flowRate = calculateFlow(amountIn); - - if (!flowRate) { - console.error('Failed to calculate flow rate.'); - return; - } - - try { - const sdk = new GoodCollectiveSDK('42220', provider, { - network: 'celo', - nftStorageKey: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9', - }); - - if (!address) return; - console.log(flowRate); - const tx = await sdk.deleteFlow(signer as any, poolAddress, flowRate as any); - await tx.wait(); - navigate('/profile/' + user); - return; - } catch (error) { - alert('TX Failed'); - console.error('An error occurred:', error); - } - }; - const supportFlowWithSwap = async () => { - try { - const sdk = new GoodCollectiveSDK('42220', provider, { - network: 'celo', - nftStorageKey: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9', - }); - if (!address) return; - await sdk.supportFlowWithSwap( - walletClient?.sendTransaction as any, - '0x62B8B11039FcfE5aB0C56E502b1C372A3d2a9c7A', - '100000000000000', - { - amount: 100, - minReturn: 100000000000000, - path: '0x', - swapFrom: '0xCAa7349CEA390F89641fe306D93591f87595dc1F', - deadline: (Date.now() + 1000000 / 1000).toFixed(0), - } - ); - } catch (error) { - console.error('An error occurred:', error); - } - }; - const supportSingleTransferAndCall = async () => {}; - const supportSingleBatch = async () => {}; - return { - supportFlowWithSwap, - supportFlow, - supportSingleTransferAndCall, - supportSingleBatch, - }; -}; diff --git a/packages/app/src/hooks/useContractCalls/index.ts b/packages/app/src/hooks/useContractCalls/index.ts new file mode 100644 index 00000000..e9703121 --- /dev/null +++ b/packages/app/src/hooks/useContractCalls/index.ts @@ -0,0 +1 @@ +export * from './useContractCalls'; diff --git a/packages/app/src/hooks/useContractCalls/useContractCalls.tsx b/packages/app/src/hooks/useContractCalls/useContractCalls.tsx new file mode 100644 index 00000000..4b9a6146 --- /dev/null +++ b/packages/app/src/hooks/useContractCalls/useContractCalls.tsx @@ -0,0 +1,69 @@ +import { Frequency } from '../../models/constants'; +import { useSupportFlow } from './useSupportFlow'; +import { useSupportFlowWithSwap } from './useSupportFlowWithSwap'; +import { useSupportSingleTransferAndCall } from './useSupportSingleTransferAndCall'; +import { useSupportSingleBatch } from './useSupportSingleBatch'; +import { useToken } from '../useTokenList'; + +interface ContractCalls { + supportFlowWithSwap: () => Promise; + supportFlow: () => Promise; + supportSingleTransferAndCall: () => Promise; + supportSingleBatch: () => Promise; +} + +export const useContractCalls = ( + collective: string, + currency: string, + decimalAmountIn: number, + duration: number, + frequency: Frequency, + onError: (error: string) => void, + toggleCompleteDonationModal: (value: boolean) => void, + minReturnFromSwap?: string, + swapPath?: string +): ContractCalls => { + const tokenIn = useToken(currency); + + const supportFlow = useSupportFlow( + collective, + tokenIn.decimals, + decimalAmountIn, + duration, + frequency, + onError, + toggleCompleteDonationModal + ); + const supportFlowWithSwap = useSupportFlowWithSwap( + collective, + tokenIn, + decimalAmountIn, + duration, + frequency, + onError, + toggleCompleteDonationModal, + minReturnFromSwap, + swapPath + ); + const supportSingleTransferAndCall = useSupportSingleTransferAndCall( + collective, + tokenIn.decimals, + decimalAmountIn, + onError, + toggleCompleteDonationModal + ); + const supportSingleBatch = useSupportSingleBatch( + collective, + tokenIn.decimals, + decimalAmountIn, + onError, + toggleCompleteDonationModal + ); + + return { + supportFlow, + supportFlowWithSwap, + supportSingleTransferAndCall, + supportSingleBatch, + }; +}; diff --git a/packages/app/src/hooks/useContractCalls/useSupportFlow.ts b/packages/app/src/hooks/useContractCalls/useSupportFlow.ts new file mode 100644 index 00000000..53addefc --- /dev/null +++ b/packages/app/src/hooks/useContractCalls/useSupportFlow.ts @@ -0,0 +1,70 @@ +import { useCallback } from 'react'; +import { Frequency, SupportedNetwork, SupportedNetworkNames } from '../../models/constants'; +import { calculateFlowRate } from '../../lib/calculateFlowRate'; +import { GoodCollectiveSDK } from '@gooddollar/goodcollective-sdk'; +import { useAccount, useNetwork } from 'wagmi'; +import { useEthersSigner } from '../wagmiF'; +import useCrossNavigate from '../../routes/useCrossNavigate'; + +export function useSupportFlow( + collective: string, + currencyDecimals: number, + decimalAmountIn: number, + duration: number, + frequency: Frequency, + onError: (error: string) => void, + toggleCompleteDonationModal: (value: boolean) => void +) { + const { address } = useAccount(); + const { chain } = useNetwork(); + const signer = useEthersSigner({ chainId: chain?.id }); + const { navigate } = useCrossNavigate(); + + return useCallback(async () => { + if (!address) { + onError('No address found. Please connect your wallet.'); + return; + } + if (!chain?.id || !(chain?.id in SupportedNetwork)) { + onError('Unsupported network. Please connect to Celo Mainnet or Celo Alfajores.'); + return; + } + if (!signer) { + onError('Failed to get signer.'); + return; + } + + const flowRate = calculateFlowRate(decimalAmountIn, duration, frequency, currencyDecimals); + if (!flowRate) { + onError('Failed to calculate flow rate.'); + return; + } + + const chainIdString = chain.id.toString() as `${SupportedNetwork}`; + const network = SupportedNetworkNames[chain.id as SupportedNetwork]; + + try { + const sdk = new GoodCollectiveSDK(chainIdString, signer.provider, { network }); + toggleCompleteDonationModal(true); + const tx = await sdk.supportFlow(signer, collective, flowRate); + await tx.wait(); + navigate(`/profile/${address}`); + return; + } catch (error) { + toggleCompleteDonationModal(false); + onError(`An unexpected error occurred: ${error}`); + } + }, [ + address, + chain?.id, + collective, + currencyDecimals, + decimalAmountIn, + duration, + frequency, + navigate, + onError, + signer, + toggleCompleteDonationModal, + ]); +} diff --git a/packages/app/src/hooks/useContractCalls/useSupportFlowWithSwap.ts b/packages/app/src/hooks/useContractCalls/useSupportFlowWithSwap.ts new file mode 100644 index 00000000..ae16d31a --- /dev/null +++ b/packages/app/src/hooks/useContractCalls/useSupportFlowWithSwap.ts @@ -0,0 +1,94 @@ +import { useCallback } from 'react'; +import { Frequency, SupportedNetwork, SupportedNetworkNames } from '../../models/constants'; +import { calculateFlowRate } from '../../lib/calculateFlowRate'; +import { calculateRawTotalDonation } from '../../lib/calculateRawTotalDonation'; +import Decimal from 'decimal.js'; +import { GoodCollectiveSDK } from '@gooddollar/goodcollective-sdk'; +import { useAccount, useNetwork } from 'wagmi'; +import { useEthersSigner } from '../wagmiF'; +import useCrossNavigate from '../../routes/useCrossNavigate'; +import { Token } from '@uniswap/sdk-core'; + +export function useSupportFlowWithSwap( + collective: string, + tokenIn: Token, + decimalAmountIn: number, + duration: number, + frequency: Frequency, + onError: (error: string) => void, + toggleCompleteDonationModal: (value: boolean) => void, + minReturnFromSwap?: string, + swapPath?: string +) { + const { address } = useAccount(); + const { chain } = useNetwork(); + const signer = useEthersSigner({ chainId: chain?.id }); + const { navigate } = useCrossNavigate(); + + return useCallback(async () => { + if (!address) { + onError('No address found. Please connect your wallet.'); + return; + } + if (!chain?.id || !(chain?.id in SupportedNetwork)) { + onError('Unsupported network. Please connect to Celo Mainnet or Celo Alfajores.'); + return; + } + if (!signer) { + onError('Failed to get signer.'); + return; + } + + if (!minReturnFromSwap || !swapPath) { + onError('Swap route not ready.'); + return; + } + + const flowRate = calculateFlowRate(decimalAmountIn, duration, frequency, tokenIn.decimals); + if (!flowRate) { + onError('Failed to calculate flow rate.'); + return; + } + + const chainIdString = chain.id.toString() as `${SupportedNetwork}`; + const network = SupportedNetworkNames[chain.id as SupportedNetwork]; + + // swap values + const amountIn = calculateRawTotalDonation(decimalAmountIn, duration, tokenIn.decimals).toFixed( + 0, + Decimal.ROUND_DOWN + ); + + try { + const sdk = new GoodCollectiveSDK(chainIdString, signer.provider, { network }); + toggleCompleteDonationModal(true); + const tx = await sdk.supportFlowWithSwap(signer, collective, flowRate, { + amount: amountIn, + minReturn: minReturnFromSwap, + path: swapPath, + swapFrom: tokenIn.address, + deadline: Math.floor(Date.now() / 1000 + 1800).toString(), + }); + await tx.wait(); + navigate(`/profile/${address}`); + return; + } catch (error) { + toggleCompleteDonationModal(false); + onError(`An unexpected error occurred: ${error}`); + } + }, [ + address, + chain?.id, + collective, + tokenIn, + decimalAmountIn, + duration, + frequency, + navigate, + onError, + signer, + minReturnFromSwap, + swapPath, + toggleCompleteDonationModal, + ]); +} diff --git a/packages/app/src/hooks/useContractCalls/useSupportSingleBatch.ts b/packages/app/src/hooks/useContractCalls/useSupportSingleBatch.ts new file mode 100644 index 00000000..527b001c --- /dev/null +++ b/packages/app/src/hooks/useContractCalls/useSupportSingleBatch.ts @@ -0,0 +1,66 @@ +import { useCallback } from 'react'; +import { SupportedNetwork, SupportedNetworkNames } from '../../models/constants'; +import { calculateRawTotalDonation } from '../../lib/calculateRawTotalDonation'; +import Decimal from 'decimal.js'; +import { GoodCollectiveSDK } from '@gooddollar/goodcollective-sdk'; +import { useAccount, useNetwork } from 'wagmi'; +import { useEthersSigner } from '../wagmiF'; +import useCrossNavigate from '../../routes/useCrossNavigate'; + +export function useSupportSingleBatch( + collective: string, + currencyDecimals: number, + decimalAmountIn: number, + onError: (error: string) => void, + toggleCompleteDonationModal: (value: boolean) => void +) { + const { address } = useAccount(); + const { chain } = useNetwork(); + const signer = useEthersSigner({ chainId: chain?.id }); + const { navigate } = useCrossNavigate(); + + return useCallback(async () => { + if (!address) { + onError('No address found. Please connect your wallet.'); + return; + } + if (!chain?.id || !(chain?.id in SupportedNetwork)) { + onError('Unsupported network. Please connect to Celo Mainnet or Celo Alfajores.'); + return; + } + if (!signer) { + onError('Failed to get signer.'); + return; + } + + const chainIdString = chain.id.toString() as `${SupportedNetwork}`; + const network = SupportedNetworkNames[chain.id as SupportedNetwork]; + + const donationAmount = calculateRawTotalDonation(decimalAmountIn, 1, currencyDecimals).toFixed( + 0, + Decimal.ROUND_DOWN + ); + + try { + const sdk = new GoodCollectiveSDK(chainIdString, signer.provider, { network }); + toggleCompleteDonationModal(true); + const tx = await sdk.supportSingleBatch(signer, collective, donationAmount); + await tx.wait(); + navigate(`/profile/${address}`); + return; + } catch (error) { + toggleCompleteDonationModal(false); + onError(`An unexpected error occurred: ${error}`); + } + }, [ + address, + chain?.id, + collective, + currencyDecimals, + decimalAmountIn, + navigate, + onError, + signer, + toggleCompleteDonationModal, + ]); +} diff --git a/packages/app/src/hooks/useContractCalls/useSupportSingleTransferAndCall.ts b/packages/app/src/hooks/useContractCalls/useSupportSingleTransferAndCall.ts new file mode 100644 index 00000000..65d9ae6e --- /dev/null +++ b/packages/app/src/hooks/useContractCalls/useSupportSingleTransferAndCall.ts @@ -0,0 +1,66 @@ +import { useCallback } from 'react'; +import { SupportedNetwork, SupportedNetworkNames } from '../../models/constants'; +import { calculateRawTotalDonation } from '../../lib/calculateRawTotalDonation'; +import Decimal from 'decimal.js'; +import { GoodCollectiveSDK } from '@gooddollar/goodcollective-sdk'; +import { useAccount, useNetwork } from 'wagmi'; +import { useEthersSigner } from '../wagmiF'; +import useCrossNavigate from '../../routes/useCrossNavigate'; + +export function useSupportSingleTransferAndCall( + collective: string, + currencyDecimals: number, + decimalAmountIn: number, + onError: (error: string) => void, + toggleCompleteDonationModal: (value: boolean) => void +) { + const { address } = useAccount(); + const { chain } = useNetwork(); + const signer = useEthersSigner({ chainId: chain?.id }); + const { navigate } = useCrossNavigate(); + + return useCallback(async () => { + if (!address) { + onError('No address found. Please connect your wallet.'); + return; + } + if (!chain?.id || !(chain?.id in SupportedNetwork)) { + onError('Unsupported network. Please connect to Celo Mainnet or Celo Alfajores.'); + return; + } + if (!signer) { + onError('Failed to get signer.'); + return; + } + + const chainIdString = chain.id.toString() as `${SupportedNetwork}`; + const network = SupportedNetworkNames[chain.id as SupportedNetwork]; + + const donationAmount = calculateRawTotalDonation(decimalAmountIn, 1, currencyDecimals).toFixed( + 0, + Decimal.ROUND_DOWN + ); + + try { + const sdk = new GoodCollectiveSDK(chainIdString, signer.provider, { network }); + toggleCompleteDonationModal(true); + const tx = await sdk.supportSingleTransferAndCall(signer, collective, donationAmount); + await tx.wait(); + navigate(`/profile/${address}`); + return; + } catch (error) { + toggleCompleteDonationModal(false); + onError(`An unexpected error occurred: ${error}`); + } + }, [ + address, + chain?.id, + collective, + currencyDecimals, + decimalAmountIn, + navigate, + onError, + signer, + toggleCompleteDonationModal, + ]); +} diff --git a/packages/app/src/hooks/useCreateStream.tsx b/packages/app/src/hooks/useCreateStream.tsx deleted file mode 100644 index a91b691f..00000000 --- a/packages/app/src/hooks/useCreateStream.tsx +++ /dev/null @@ -1,52 +0,0 @@ -// import { Framework } from '@superfluid-finance/sdk-core'; -// import { useConnect, useNetwork, useWalletClient } from 'wagmi'; - -// export function useCreateNewFlow() { -// const SF_RESOLVERS: { [key: string]: string } = { -// 44787: '0x6e9CaBE4172344Db81a1E1D735a6AD763700064A', -// 31337: '0x41549B6C39A529EA574f35b745b00f716869D2a0', -// }; -// const createNewFlow = async (flowRate: string, token: string, pool: string) => { -// // eslint-disable-next-line react-hooks/rules-of-hooks -// const { connector }: any = useConnect(); - -// const provider = await connector.getProvider(); -// // eslint-disable-next-line react-hooks/rules-of-hooks -// const { chain }: any = useNetwork(); -// // eslint-disable-next-line react-hooks/rules-of-hooks -// const { data }: any = useWalletClient(); - -// const opts = { -// chainId: Number(chain), -// provider: provider, -// resolverAddress: SF_RESOLVERS[chain], -// protocolReleaseVersion: chain === '31337' ? 'test' : undefined, -// }; - -// const sf = await Framework.create(opts); -// const st = sf?.loadSuperToken(token); -// try { -// if (data.signer) { -// console.log('flowrate', flowRate); - -// const createFlowOperation = sf.cfaV1.createFlow({ -// flowRate: flowRate, -// receiver: pool, -// superToken: st, -// }); - -// console.log('Creating your stream...'); - -// const result = await createFlowOperation.exec(data.signer); -// console.log(result); -// } -// } catch (error) { -// console.log( -// "Hmmm, your transaction threw an error. Make sure that this stream does not already exist, and that you've entered a valid Ethereum address!" -// ); -// console.error(error); -// } -// }; - -// return { createNewFlow }; -// } diff --git a/packages/app/src/hooks/useFlowingBalance.tsx b/packages/app/src/hooks/useFlowingBalance.tsx index 4af2bc32..5f2ad90c 100644 --- a/packages/app/src/hooks/useFlowingBalance.tsx +++ b/packages/app/src/hooks/useFlowingBalance.tsx @@ -1,7 +1,7 @@ import { useEffect, useMemo, useState } from 'react'; import { BigNumberish, ethers } from 'ethers'; -import { calculateAmounts, CalculatedAmounts } from '../lib/calculateAmounts'; import { DonorCollective } from '../models/models'; +import { calculateGoodDollarAmounts, CalculatedAmounts } from '../lib/calculateGoodDollarAmounts'; // based on https://github.com/superfluid-finance/superfluid-console/blob/master/src/components/FlowingBalance.tsx @@ -99,5 +99,5 @@ export function useSumOfFlowingBalances( }; }, [balance, balanceTimestampsMs, flowRateBigNumbers]); - return calculateAmounts(weiValue.toString(), tokenPrice); + return calculateGoodDollarAmounts(weiValue.toString(), tokenPrice); } diff --git a/packages/app/src/hooks/useGetBalance.ts b/packages/app/src/hooks/useGetDecimalBalance.ts similarity index 55% rename from packages/app/src/hooks/useGetBalance.ts rename to packages/app/src/hooks/useGetDecimalBalance.ts index 96faaacc..018d455f 100644 --- a/packages/app/src/hooks/useGetBalance.ts +++ b/packages/app/src/hooks/useGetDecimalBalance.ts @@ -1,24 +1,27 @@ -import { tokenMapping } from '../models/constants'; +import { SupportedNetwork } from '../models/constants'; import { useEffect, useState } from 'react'; import { fetchBalance } from 'wagmi/actions'; +import { useToken } from './useTokenList'; -export const useGetBalance = ( - currencySymbol: keyof typeof tokenMapping, +export const useGetDecimalBalance = ( + currencySymbol: string, accountAddress: `0x${string}` | undefined, - chainId: number | undefined + chainId: number = SupportedNetwork.celo ): number => { const [tokenBalance, setTokenBalance] = useState('0'); + const token = useToken(currencySymbol); + useEffect(() => { if (!accountAddress) return; fetchBalance({ address: accountAddress, chainId: chainId, - token: tokenMapping[currencySymbol], + token: token.address as `0x${string}`, }).then((res) => { setTokenBalance(res.formatted); }); - }, [currencySymbol, accountAddress, chainId]); + }, [currencySymbol, token.address, accountAddress, chainId]); return parseFloat(tokenBalance); }; diff --git a/packages/app/src/hooks/useGetTokenPrice.tsx b/packages/app/src/hooks/useGetTokenPrice.tsx index 0f0182ce..f57e1d68 100644 --- a/packages/app/src/hooks/useGetTokenPrice.tsx +++ b/packages/app/src/hooks/useGetTokenPrice.tsx @@ -1,34 +1,49 @@ import axios from 'axios'; import { useEffect, useState } from 'react'; -import { tokenMapping } from '../models/constants'; +import { coingeckoTokenMapping } from '../models/constants'; +import { useToken } from './useTokenList'; +import { Token } from '@uniswap/sdk-core'; export const useGetTokenPrice = (currency: string): { price?: number; isLoading: boolean } => { const [price, setPrice] = useState(undefined); const [isLoading, setIsLoading] = useState(true); + const token = useToken(currency); + useEffect(() => { setIsLoading(true); - for (const [token, tokenAddress] of Object.entries(tokenMapping)) { - if (currency === token) { - getTokenPrice(tokenAddress).then((res: number | undefined) => { - setPrice(res); - }); - break; - } - } + getTokenPrice(currency, token).then((res: number | undefined) => { + setPrice(res); + }); setIsLoading(false); - }, [currency]); + }, [currency, token]); return { price, isLoading }; }; -const getTokenPrice = (contractAddress: string): Promise => { - return axios - .get(`https://api.coingecko.com/api/v3/coins/celo/contract/${contractAddress}`, { - withCredentials: false, +const getTokenPrice = async (currency: string, token: Token): Promise => { + let tokenAddress = coingeckoTokenMapping[currency] ?? token.address; + const priceByContractUrl = `https://api.coingecko.com/api/v3/simple/token_price/celo?contract_addresses=${tokenAddress}&vs_currencies=usd`; + const priceByContract: number | undefined = await axios + .get(priceByContractUrl) + .then((res) => { + return res.data[tokenAddress.toLowerCase()]?.usd; }) + .catch((err) => { + console.error(err); + return undefined; + }); + + if (priceByContract !== undefined) { + return priceByContract; + } + + // fallback + const priceBySymbolUrl = `https://api.coingecko.com/api/v3/simple/price?ids=${currency}&vs_currencies=usd`; + return await axios + .get(priceBySymbolUrl) .then((res) => { - return res.data.market_data.current_price.usd; + return res.data[currency.toLowerCase()]?.usd; }) .catch((err) => { console.error(err); diff --git a/packages/app/src/hooks/useIPFS.tsx b/packages/app/src/hooks/useIPFS.tsx deleted file mode 100644 index 378b96a2..00000000 --- a/packages/app/src/hooks/useIPFS.tsx +++ /dev/null @@ -1,68 +0,0 @@ -// export default function useIPFS() { -// const FormData = require('form-data'); -// const axios = require('axios'); -// // const pinata = pinataSDK(process.env.NEXT_PUBLIC_PINATA_API_KEY, process.env.NEXT_PUBLIC_PINATA_SECRET_KEY) -// const { image, name, description, setIpfsCid } = useMetadataContext(); -// const { mint } = useFayreContracts(); - -// const uploadMetadata = async (mintObj: any) => { -// const data = new FormData(); -// let imageCID: string; -// console.log('hits'); - -// try { -// const url = `https://api.pinata.cloud/pinning/pinFileToIPFS`; -// console.log(image, 'line 24 ipfs'); -// data.append('file', image); -// const uplaodImage = await axios.post(url, data, { -// maxBodyLength: 'Infinity', -// headers: { -// pinata_api_key: '470f87aa78c6c163afb8', -// pinata_secret_api_key: 'a3d7d463e4ba53ceebea81aec98a02e259882cd79bf14c4f18cdc98e20a2ea44', -// }, -// }); -// return ( -// console.log('fires'), -// console.log(uplaodImage.data.IpfsHash as string), -// (imageCID = uplaodImage.data.IpfsHash as string), -// uploadData(mintObj, imageCID) -// ); -// } catch (err: any) { -// console.error(err, 'error uploading image'); -// } -// }; - -// const uploadData = async (mintObj: any, imageCID: string) => { -// const metadataObj = { -// description: description, -// image: imageCID, -// name: name, -// }; - -// try { -// const url = `https://api.pinata.cloud/pinning/pinJSONToIPFS`; - -// const uploadData = await axios.post(url, metadataObj, { -// maxBodyLength: 'Infinity', -// headers: { -// pinata_api_key: process.env.NEXT_PUBLIC_PINATA_PUBLIC_KEY, -// pinata_secret_api_key: process.env.NEXT_PUBLIC_PINATA_SECRET_KEY, -// }, -// }); -// return ( -// console.log(uploadData.data.IpfsHash as string), -// await mint( -// mintObj.tokenStandard, -// uploadData.data.IpfsHash as string, -// mintObj.amount, -// mintObj.royalties, -// mintObj.collectionName -// ) -// ); -// } catch (err) { -// console.error(err, 'error uploading metadata'); -// } -// }; - -// return { uploadMetadata }; -// } diff --git a/packages/app/src/hooks/useSwap.tsx b/packages/app/src/hooks/useSwap.tsx deleted file mode 100644 index 37e8896f..00000000 --- a/packages/app/src/hooks/useSwap.tsx +++ /dev/null @@ -1,38 +0,0 @@ -// import { AlphaRouter } from '@uniswap/smart-order-router'; -// import { Token, CurrencyAmount, TradeType, Percent, Currency } from '@uniswap/sdk-core'; -// import { useAccount, usePublicClient } from 'wagmi'; -// import * as hre from 'ethers'; - -// // eslint-disable-next-line react-hooks/rules-of-hooks -// const { chain } = useNetwork(); -// // eslint-disable-next-line react-hooks/rules-of-hooks -// const { connector }: any = useConnect(); - -// const provider_ = connector.getProvider(); - -// const router = new AlphaRouter({ chainId: 42220, provider: provider_ }); - -// const tokenIn = new Token(); - -// export default async (userAddress: string, token: Currency) => { -// const V3_ROUTER_ADDRESS = '0x5615CDAb10dc425a742d643d949a7F474C01abc4'; -// const publicClient = usePublicClient(); -// const { address, isConnected } = useAccount(); -// const router = new AlphaRouter({ chainId: 42220, provider: publicClient as any }); -// const inputAmount = CurrencyAmount.fromRawAmount(WETH, JSBI.BigInt(wei)); -// const route = await router.route(inputAmount, token, TradeType.EXACT_INPUT, { -// recipient: userAddress, -// slippageTolerance: new Percent(25, 100), -// deadline: Math.floor(Date.now() / 1000 + 1800), -// }); -// console.log(`Quote Exact In: ${route.quote.toFixed(10)}`); - -// const transaction = { -// data: route.methodParameters.calldata, -// to: V3_ROUTER_ADDRESS, -// value: hre.BigNumber.from(route.methodParameters.value), -// from: address, -// gasPrice: hre.BigNumber.from(route.gasPriceWei), -// gasLimit: hre.utils.hexlify(1000000), -// }; -// }; diff --git a/packages/app/src/hooks/useSwapRoute.tsx b/packages/app/src/hooks/useSwapRoute.tsx new file mode 100644 index 00000000..673bf222 --- /dev/null +++ b/packages/app/src/hooks/useSwapRoute.tsx @@ -0,0 +1,87 @@ +import { AlphaRouter, SwapRoute, SwapType, V3Route } from '@uniswap/smart-order-router'; +import { CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'; +import { useAccount, useNetwork } from 'wagmi'; +import { GDToken } from '../models/constants'; +import { useEthersSigner } from './wagmiF'; +import { calculateRawTotalDonation } from '../lib/calculateRawTotalDonation'; +import Decimal from 'decimal.js'; +import { useEffect, useState } from 'react'; +import { encodeRouteToPath } from '@uniswap/v3-sdk'; +import { useToken } from './useTokenList'; +import { Protocol } from '@uniswap/router-sdk'; + +export enum SwapRouteState { + LOADING, + READY, + NO_ROUTE, +} + +export function useSwapRoute( + currencyIn: string, + decimalAmountIn: number, + duration: number, + slippageTolerance: Percent = new Percent(50, 10_000) +): { + path?: string; + quote?: Decimal; + rawMinimumAmountOut?: string; + priceImpact?: number; + status: SwapRouteState; +} { + const { address } = useAccount(); + const { chain } = useNetwork(); + const signer = useEthersSigner({ chainId: chain?.id }); + + const tokenIn = useToken(currencyIn); + + const [route, setRoute] = useState(undefined); + + useEffect(() => { + if (!address || !chain?.id || !signer?.provider || tokenIn.symbol === 'G$') { + setRoute(undefined); + return; + } + + const router = new AlphaRouter({ + chainId: chain.id, + provider: signer.provider, + }); + + const rawAmountIn = calculateRawTotalDonation(decimalAmountIn, duration, tokenIn.decimals); + const inputAmount = CurrencyAmount.fromRawAmount(tokenIn, rawAmountIn.toFixed(0, Decimal.ROUND_DOWN)); + + router + .route( + inputAmount, + GDToken, + TradeType.EXACT_INPUT, + { + type: SwapType.SWAP_ROUTER_02, + recipient: address, + slippageTolerance: slippageTolerance, + deadline: Math.floor(Date.now() / 1000 + 1800), + }, + { + protocols: [Protocol.V3], + } + ) + .then((swapRoute) => { + setRoute(swapRoute ?? undefined); + }) + .catch((e) => { + console.error(e); + setRoute(undefined); + }); + }, [address, chain?.id, signer?.provider, tokenIn, decimalAmountIn, duration, slippageTolerance]); + + if (!route || !route.methodParameters) { + return { status: SwapRouteState.NO_ROUTE }; + } else { + // This typecast is safe because Uniswap v2 is not deployed on Celo + const path = encodeRouteToPath(route.route[0].route as V3Route, false); + const quote = new Decimal(route.quote.toFixed(18)); + const rawMinimumAmountOut = route.trade.minimumAmountOut(slippageTolerance).numerator.toString(); + const priceImpact = parseFloat(route.trade.priceImpact.toFixed(4)); + return { path, quote, rawMinimumAmountOut, priceImpact, status: SwapRouteState.READY }; + } +} diff --git a/packages/app/src/hooks/useTokenList.ts b/packages/app/src/hooks/useTokenList.ts index 5f8b9606..e018a93d 100644 --- a/packages/app/src/hooks/useTokenList.ts +++ b/packages/app/src/hooks/useTokenList.ts @@ -1,30 +1,22 @@ +import { useMemo } from 'react'; import { Token } from '@uniswap/sdk-core'; -import { useEffect, useState } from 'react'; -import axios from 'axios'; +import CeloTokenList from '../models/CeloTokenList.json'; +import { SupportedNetwork } from '../models/constants'; -interface TokenList { - tokens: { - name: string; - address: string; - symbol: string; - decimals: number; - chainId: number; - }[]; +export function useToken(symbol: string): Token { + return useTokenList()[symbol]; } -export function useTokenList(): Token[] { - const [tokens, setTokens] = useState([]); - - useEffect(() => { - axios - .get('https://raw.githubusercontent.com/celo-org/celo-token-list/main/celo.tokenlist.json') - .then((response) => { - const tokenData = response.data.tokens.map( - (token) => new Token(token.chainId, token.address, token.decimals, token.symbol, token.name) - ); - setTokens(tokenData); - }); +export function useTokenList(): Record { + return useMemo(() => { + const tokenList: Record = {}; + const sortedList = CeloTokenList.tokens.sort((a, b) => a.symbol.localeCompare(b.symbol)); + sortedList.forEach((token) => { + if (token.chainId !== SupportedNetwork.celo) { + return; + } + tokenList[token.symbol] = new Token(token.chainId, token.address, token.decimals, token.symbol); + }); + return tokenList; }, []); - - return tokens; } diff --git a/packages/app/src/lib/calculateFlowRate.ts b/packages/app/src/lib/calculateFlowRate.ts new file mode 100644 index 00000000..b2b17627 --- /dev/null +++ b/packages/app/src/lib/calculateFlowRate.ts @@ -0,0 +1,17 @@ +import { Frequency } from '../models/constants'; +import Decimal from 'decimal.js'; +import { totalDurationInSeconds } from './totalDurationInSeconds'; + +export const calculateFlowRate = ( + decimalAmount: number, + duration: number, + frequency: Frequency, + currencyDecimals: number +): string | undefined => { + if (frequency === Frequency.OneTime) { + return undefined; + } + const rawAmount = new Decimal(decimalAmount * duration).times(10 ** currencyDecimals); + const totalMilliseconds = totalDurationInSeconds(duration, frequency); + return rawAmount.div(totalMilliseconds).toFixed(0, Decimal.ROUND_DOWN); +}; diff --git a/packages/app/src/lib/calculateAmounts.ts b/packages/app/src/lib/calculateGoodDollarAmounts.ts similarity index 82% rename from packages/app/src/lib/calculateAmounts.ts rename to packages/app/src/lib/calculateGoodDollarAmounts.ts index 6196038e..f1814df6 100644 --- a/packages/app/src/lib/calculateAmounts.ts +++ b/packages/app/src/lib/calculateGoodDollarAmounts.ts @@ -3,7 +3,8 @@ import { ethers } from 'ethers'; export type CalculatedAmounts = { decimal?: Decimal; formatted?: string; usdValue?: number }; -export function calculateAmounts(onChainAmount?: string, tokenPrice?: number): CalculatedAmounts { +// assumes 18 decimals +export function calculateGoodDollarAmounts(onChainAmount?: string, tokenPrice?: number): CalculatedAmounts { if (onChainAmount === undefined) { return { decimal: undefined, diff --git a/packages/app/src/lib/calculateRawTotalDonation.ts b/packages/app/src/lib/calculateRawTotalDonation.ts new file mode 100644 index 00000000..ac1747d9 --- /dev/null +++ b/packages/app/src/lib/calculateRawTotalDonation.ts @@ -0,0 +1,9 @@ +import Decimal from 'decimal.js'; + +export const calculateRawTotalDonation = ( + decimalAmount: number, + duration: number, + currencyDecimals: number +): Decimal => { + return new Decimal(decimalAmount * duration).times(10 ** currencyDecimals); +}; diff --git a/packages/app/src/lib/displayAddress.ts b/packages/app/src/lib/displayAddress.ts deleted file mode 100644 index 6c8eb622..00000000 --- a/packages/app/src/lib/displayAddress.ts +++ /dev/null @@ -1,6 +0,0 @@ -const displayAddress = (address: string, length = 4) => { - if (!address) return ''; - return `${address.substring(0, length)}•••${address.substring(address.length - length, address.length)}`; -}; - -export default displayAddress; diff --git a/packages/app/src/lib/formatAddress.ts b/packages/app/src/lib/formatAddress.ts new file mode 100644 index 00000000..d4f206a8 --- /dev/null +++ b/packages/app/src/lib/formatAddress.ts @@ -0,0 +1,4 @@ +export const formatAddress = (address: string, length = 5) => { + if (!address) return ''; + return address.slice(0, length) + '...' + address.slice(-(length - 1)); +}; diff --git a/packages/app/src/lib/formatAmount.ts b/packages/app/src/lib/formatAmount.ts deleted file mode 100644 index 28e23507..00000000 --- a/packages/app/src/lib/formatAmount.ts +++ /dev/null @@ -1,15 +0,0 @@ -export const formatAmount = (eth: any): any => { - if (!eth) return '0'; - - const parsed = eth?.split('.'); - const beforeDecimal = parsed[0]; - let formatted; - const afterDecimal = parsed[1]; - - if (beforeDecimal === '0' && afterDecimal !== '0') { - return (formatted = `0.${afterDecimal?.slice(0, 4) || 0}`); - } else if (beforeDecimal && afterDecimal > 0) { - return (formatted = `${beforeDecimal}.${afterDecimal?.slice(0, 4) || 0}`); - } - return formatted; -}; diff --git a/packages/app/src/lib/formatDecimalStringInput.ts b/packages/app/src/lib/formatDecimalStringInput.ts new file mode 100644 index 00000000..4aad5eeb --- /dev/null +++ b/packages/app/src/lib/formatDecimalStringInput.ts @@ -0,0 +1,7 @@ +export function formatDecimalStringInput(value: string) { + const float = parseFloat(value); + if (isNaN(float)) { + return 0; + } + return float; +} diff --git a/packages/app/src/lib/formatGdollar.ts b/packages/app/src/lib/formatGdollar.ts deleted file mode 100644 index e437e42c..00000000 --- a/packages/app/src/lib/formatGdollar.ts +++ /dev/null @@ -1,15 +0,0 @@ -export const formatGdollar = (gDollar: any): any => { - if (!gDollar) return '0'; - const parsed = (gDollar / 10 ** 18) as any; - const parsed1 = parsed?.split('.') as any; - const beforeDecimal = parsed1[0]; - let formatted; - const afterDecimal = parsed1[1]; - - if (beforeDecimal === '0' && afterDecimal !== '0') { - return (formatted = `0.${afterDecimal?.slice(0, 4) || 0}`); - } else if (beforeDecimal && afterDecimal > 0) { - return (formatted = `${beforeDecimal}.${afterDecimal?.slice(0, 4) || 0}`); - } - return formatted; -}; diff --git a/packages/app/src/lib/totalDurationInSeconds.ts b/packages/app/src/lib/totalDurationInSeconds.ts new file mode 100644 index 00000000..24d535fe --- /dev/null +++ b/packages/app/src/lib/totalDurationInSeconds.ts @@ -0,0 +1,43 @@ +import { Frequency } from '../models/constants'; + +export const totalDurationInSeconds = (duration: number, frequency: Frequency): number => { + if (frequency === Frequency.OneTime) { + return 0; + } else if (frequency === Frequency.Daily) { + return duration * 24 * 60 * 60; + } else if (frequency === Frequency.Weekly) { + return duration * 7 * 24 * 60 * 60; + } + + const now = new Date(); + let nextDate: Date; + + if (frequency === Frequency.Monthly) { + const naiveMonth = now.getMonth() + duration; + const month = naiveMonth % 12; + const year = now.getFullYear() + Math.floor(naiveMonth / 12); + nextDate = new Date( + year, + month, + now.getDate(), + now.getHours(), + now.getMinutes(), + now.getSeconds(), + now.getMilliseconds() + ); + } else { + // frequency === Frequency.Yearly + nextDate = new Date( + now.getFullYear() + duration, + now.getMonth(), + now.getDate(), + now.getHours(), + now.getMinutes(), + now.getSeconds(), + now.getMilliseconds() + ); + } + + const timeInMilliseconds = nextDate.getTime() - now.getTime(); + return Math.floor(timeInMilliseconds / 1000); +}; diff --git a/packages/app/src/models/CeloTokenList.json b/packages/app/src/models/CeloTokenList.json new file mode 100644 index 00000000..a1a9c8e3 --- /dev/null +++ b/packages/app/src/models/CeloTokenList.json @@ -0,0 +1,645 @@ +{ + "name": "Celo Token List", + "version": { + "major": 2, + "minor": 3, + "patch": 0 + }, + "logoURI": "https://celo-org.github.io/celo-token-list/assets/celo_logo.svg", + "keywords": ["celo", "tokens", "refi"], + "timestamp": "2022-05-25T20:37:00.000+00:00", + "tokens": [ + { + "name": "Green CELO", + "address": "0x8a1639098644a229d08f441ea45a63ae050ee018", + "symbol": "gCELO", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/spiralsprotocol/spirals-brand/main/gCELO.svg" + }, + { + "name": "Green cUSD", + "address": "0xFB42E2e90fc79CfA6A6B4EBa4877d5Faf4e29287", + "symbol": "gcUSD", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/spiralsprotocol/spirals-brand/main/gcUSD.svg" + }, + { + "name": "cRecy", + "address": "0x34C11A932853Ae24E845Ad4B633E3cEf91afE583", + "symbol": "cRecy", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://user-images.githubusercontent.com/101748448/187026740-27f51d9d-e60d-48e9-b378-416c1eda0cb1.svg" + }, + { + "name": "Staked Celo", + "address": "0xC668583dcbDc9ae6FA3CE46462758188adfdfC24", + "symbol": "stCelo", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://celo-org.github.io/celo-token-list/assets/token-stcelo.svg" + }, + { + "name": "Nature Carbon Tonne", + "address": "0x02de4766c272abc10bc88c220d214a26960a7e92", + "symbol": "NCT", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://toucan.earth/img/icons/nct.svg" + }, + { + "name": "USDC (Portal from Ethereum)", + "address": "0x37f750B7cC259A2f741AF45294f6a16572CF5cAd", + "symbol": "USDCet", + "decimals": 6, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/certusone/wormhole-token-list/main/assets/USDCet_wh_small.png" + }, + { + "name": "DAI Stablecoin (Portal)", + "address": "0x97926a82930bb7B33178E3c2f4ED1BFDc91A9FBF", + "symbol": "DAI", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/certusone/wormhole-token-list/main/assets/DAI_wh_small.png" + }, + { + "name": "Portal WETH", + "address": "0x66803FB87aBd4aaC3cbB3fAd7C3aa01f6F3FB207", + "symbol": "WETH", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_ETH.png" + }, + { + "name": "wrapped.com ETH", + "address": "0x2DEf4285787d58a2f811AF24755A8150622f4361", + "symbol": "cETH", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_cETH.svg" + }, + { + "name": "Ubeswap", + "address": "0x00Be915B9dCf56a3CBE739D9B9c202ca692409EC", + "symbol": "UBE", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_UBE.png" + }, + { + "name": "Celo Moss Carbon Credit", + "address": "0x32A9FE697a32135BFd313a6Ac28792DaE4D9979d", + "symbol": "cMCO2", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_cMCO2.png" + }, + { + "name": "Celo", + "address": "0x471EcE3750Da237f93B8E339c536989b8978a438", + "symbol": "CELO", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://celo-org.github.io/celo-token-list/assets/celo_logo.svg" + }, + { + "name": "Celo Dollar", + "address": "0x765DE816845861e75A25fCA122bb6898B8B1282a", + "symbol": "cUSD", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_cUSD.png" + }, + { + "name": "Duniapay West African CFA franc", + "address": "0x832F03bCeE999a577cb592948983E35C048B5Aa4", + "symbol": "cXOF", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_cXOF.png" + }, + { + "name": "wrapped.com Bitcoin", + "address": "0xD629eb00dEced2a080B7EC630eF6aC117e614f1b", + "symbol": "cBTC", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_cBTC.png" + }, + { + "name": "Celo Euro", + "address": "0xD8763CBa276a3738E6DE85b4b3bF5FDed6D6cA73", + "symbol": "cEUR", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_cEUR.png" + }, + { + "name": "Beefy Finance", + "address": "0x639A647fbe20b6c8ac19E48E2de44ea792c62c5C", + "decimals": 18, + "symbol": "BIFI", + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/sushiswap/assets/master/blockchains/celo/assets/0x639A647fbe20b6c8ac19E48E2de44ea792c62c5C/logo.png" + }, + { + "name": "Optics v2 WMATIC via Polygon", + "address": "0x2E3487F967DF2Ebc2f236E16f8fCAeac7091324D", + "symbol": "WMATIC", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_WMATIC.png" + }, + { + "name": "Optics v2 SUSHI", + "address": "0x29dFce9c22003A4999930382Fd00f9Fd6133Acd1", + "symbol": "SUSHI", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_SUSHI.png" + }, + { + "name": "Optics v2 WETH", + "address": "0x122013fd7dF1C6F636a5bb8f03108E876548b455", + "symbol": "WETH", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_WETH.png" + }, + { + "name": "Optics v2 WBTC", + "address": "0xBAAB46E28388d2779e6E31Fd00cF0e5Ad95E327B", + "decimals": 8, + "symbol": "WBTC", + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_WBTC.png" + }, + { + "name": "Optics v2 USDC", + "address": "0xef4229c8c3250C675F21BCefa42f58EfbfF6002a", + "decimals": 6, + "symbol": "USDC", + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_USDC.png" + }, + { + "name": "Optics v2 USDT", + "address": "0x88eeC49252c8cbc039DCdB394c0c2BA2f1637EA0", + "decimals": 6, + "symbol": "USDT", + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_USDT.png" + }, + { + "name": "Optics v2 DAI", + "address": "0x90Ca507a5D4458a4C6C6249d186b6dCb02a5BCCd", + "symbol": "DAI", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_DAI.png" + }, + { + "name": "Mobius DAO Token", + "address": "0x73a210637f6F6B7005512677Ba6B3C96bb4AA44B", + "symbol": "MOBI", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_MOBI.png" + }, + { + "name": "impactMarket", + "address": "0x46c9757C5497c5B1f2eb73aE79b6B67D119B0B58", + "symbol": "PACT", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_PACT.png" + }, + { + "name": "Source", + "address": "0x74c0C58B99b68cF16A717279AC2d056A34ba2bFe", + "symbol": "SOURCE", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_SOURCE.png" + }, + { + "name": "Poof", + "address": "0x00400FcbF0816bebB94654259de7273f4A05c762", + "symbol": "POOF", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_POOF.png" + }, + { + "name": "Stabilite USD", + "address": "0x0a60c25Ef6021fC3B479914E6bcA7C03c18A97f1", + "symbol": "stabilUSD", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_stabilUSD.png" + }, + { + "name": "Allbridge SOL", + "address": "0x173234922eB27d5138c5e481be9dF5261fAeD450", + "symbol": "SOL", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_SOL.png" + }, + { + "name": "Moola", + "address": "0x17700282592D6917F6A73D0bF8AcCf4D578c131e", + "symbol": "MOO", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_MOO.png" + }, + { + "name": "Ariswap", + "address": "0x20677d4f3d0F08e735aB512393524A3CfCEb250C", + "symbol": "ARI", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_ARI.png" + }, + { + "name": "Anyswap FTM", + "address": "0x218c3c3D49d0E7B37aff0D8bB079de36Ae61A4c0", + "symbol": "FTM", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_FTM.png" + }, + { + "name": "Poof CELO", + "address": "0x301a61D01A63c8D670c2B8a43f37d12eF181F997", + "symbol": "pCELO", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_pCELO.png" + }, + { + "name": "CeloStarter", + "address": "0x452EF5a4bD00796e62E5e5758548e0dA6e8CCDF3", + "symbol": "cStar", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_cStar.png" + }, + { + "name": "Allbridge SBR", + "address": "0x47264aE1Fc0c8e6418ebe78630718E11a07346A8", + "symbol": "SBR", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_SBR.png" + }, + { + "name": "Allbridge", + "address": "0x6e512BFC33be36F2666754E996ff103AD1680Cc9", + "symbol": "ABR", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_ABR.png" + }, + { + "name": "Staked Allbridge", + "address": "0x788BA01f8E2b87c08B142DB46F82094e0bdCad4F", + "symbol": "xABR", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_xABR.png" + }, + { + "name": "Moola CELO", + "address": "0x7D00cd74FF385c955EA3d79e47BF06bD7386387D", + "symbol": "mCELO", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_mCELO.png" + }, + { + "name": "Symmetric", + "address": "0x8427bD503dd3169cCC9aFF7326c15258Bc305478", + "symbol": "SYMM", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_SYMM.png" + }, + { + "name": "Allbridge AVAX", + "address": "0x8E3670FD7B0935d3FE832711deBFE13BB689b690", + "symbol": "AVAX", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_AVAX.png" + }, + { + "name": "Moola cUSD", + "address": "0x918146359264C492BD6934071c6Bd31C854EDBc3", + "symbol": "mcUSD", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_mcUSD.png" + }, + { + "name": "Premio", + "address": "0x94140c2eA9D208D8476cA4E3045254169791C59e", + "symbol": "PREMIO", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_PREMIO.png" + }, + { + "name": "Moola cREAL", + "address": "0x9802d866fdE4563d088a6619F7CeF82C0B991A55", + "symbol": "mcREAL", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_mcREAL.png" + }, + { + "name": "Anyswap BNB", + "address": "0xA649325Aa7C5093d12D6F98EB4378deAe68CE23F", + "symbol": "BNB", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_BNB.png" + }, + { + "name": "KnoxEdge", + "address": "0xa81D9a2d29373777E4082d588958678a6Df5645c", + "symbol": "KNX", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_KNX.png" + }, + { + "name": "TrueFeedBack New", + "address": "0xbDd31EFfb9E9f7509fEaAc5B4091b31645A47e4b", + "symbol": "TFBX", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_TFBX.png" + }, + { + "name": "Moola cEUR", + "address": "0xE273Ad7ee11dCfAA87383aD5977EE1504aC07568", + "symbol": "mcEUR", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_mcEUR.png" + }, + { + "name": "Immortal", + "address": "0xE685d21b7B0FC7A248a6A8E03b8Db22d013Aa2eE", + "decimals": 9, + "symbol": "IMMO", + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_IMMO.png" + }, + { + "name": "Celo Real", + "address": "0xe8537a3d056DA446677B9E9d6c5dB704EaAb4787", + "symbol": "cREAL", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_cREAL.png" + }, + { + "name": "Poof USD", + "address": "0xEadf4A7168A82D30Ba0619e64d5BCf5B30B45226", + "symbol": "pUSD", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_pUSD.png" + }, + { + "name": "Poof v1 EUR", + "address": "0x56072D4832642dB29225dA12d6Fd1290E4744682", + "symbol": "pEURxV1", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_pEUR.png" + }, + { + "name": "Marzipan Finance", + "address": "0x9Ee153D4Fdf0E3222eFD092c442ebB21DFd346AC", + "symbol": "MZPN", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_MZPN.png" + }, + { + "name": "Poof v1 USD", + "address": "0xB4aa2986622249B1F45eb93F28Cfca2b2606d809", + "symbol": "pUSDxV1", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_pUSD.png" + }, + { + "name": "Duino-Coin on Celo", + "address": "0xDB452CC669D3Ae454226AbF232Fe211bAfF2a1F9", + "symbol": "celoDUCO", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_celoDUCO.png" + }, + { + "name": "Poof v1 CELO", + "address": "0xE74AbF23E1Fdf7ACbec2F3a30a772eF77f1601E1", + "symbol": "pCELOxV1", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_pCELO.png" + }, + { + "name": "Poof Governance Token", + "address": "0x00400FcbF0816bebB94654259de7273f4A05c762", + "symbol": "POOF", + "decimals": 18, + "chainId": 44787, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_POOF.png" + }, + { + "name": "Moola cEUR", + "address": "0x0D9B4311657003251d1eFa085e74f761185F271c", + "symbol": "mcEUR", + "decimals": 18, + "chainId": 44787, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_mcEUR.png" + }, + { + "name": "Celo Euro", + "address": "0x10c892A6EC43a53E45D0B916B4b7D383B1b78C0F", + "symbol": "cEUR", + "decimals": 18, + "chainId": 44787, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_cEUR.png" + }, + { + "name": "NetM Token", + "address": "0x123ED050805E0998EBEf43671327139224218e50", + "symbol": "NTMX", + "decimals": 18, + "chainId": 44787, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_NTMX.png" + }, + { + "name": "Moola", + "address": "0x17700282592D6917F6A73D0bF8AcCf4D578c131e", + "symbol": "MOO", + "decimals": 18, + "chainId": 44787, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_MOO.png" + }, + { + "name": "Moola cUSD", + "address": "0x3a0EA4e0806805527C750AB9b34382642448468D", + "symbol": "mcUSD", + "decimals": 18, + "chainId": 44787, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_mcUSD.png" + }, + { + "name": "Moola cREAL", + "address": "0x3D0EDA535ca4b15c739D46761d24E42e37664Ad7", + "symbol": "mcREAL", + "decimals": 18, + "chainId": 44787, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_mcREAL.png" + }, + { + "name": "Marzipan Finance", + "address": "0x4d8BF8347600f5207bfdad57363fBa802C9C2031", + "symbol": "MZPN", + "decimals": 18, + "chainId": 44787, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_MZPN.png" + }, + { + "name": "Moola CELO", + "address": "0x653cC2Cc0Be398614BAd5d5328336dc79281e246", + "symbol": "mCELO", + "decimals": 18, + "chainId": 44787, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_mCELO.png" + }, + { + "name": "Celo Dollar", + "address": "0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1", + "symbol": "cUSD", + "decimals": 18, + "chainId": 44787, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_cUSD.png" + }, + { + "name": "Celo", + "address": "0xF194afDf50B03e69Bd7D057c1Aa9e10c9954E4C9", + "symbol": "CELO", + "decimals": 18, + "chainId": 44787, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_CELO.png" + }, + { + "name": "AtlasX Carbon Credits", + "address": "0xc3377Ea71F1dc8e55Ba360724eff2d7aD62a8670", + "symbol": "ATLASX", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://myterrablobs.blob.core.windows.net/public/token-icon.png" + }, + { + "name": "PLASTIK Token", + "address": "0x27cd006548dF7C8c8e9fdc4A67fa05C2E3CA5CF9", + "symbol": "PLASTIK", + "decimals": 9, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_PLASTIK.png" + }, + { + "name": "Curve DAO Token", + "address": "0x173fd7434B8B50dF08e3298f173487ebDB35FD14", + "symbol": "CRV", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/curvefi/curve-assets/main/branding/logo.svg" + }, + { + "name": "Axelar Wrapped Bitcoin", + "address": "0x1a35EE4640b0A3B87705B0A4B45D227Ba60Ca2ad", + "symbol": "axlWBTC", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://celo-org.github.io/celo-token-list/assets/axelar_wbtc.svg" + }, + { + "name": "Wormhole Wrapped Bitcoin", + "address": "0xd71Ffd0940c920786eC4DbB5A12306669b5b81EF", + "symbol": "WBTC", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://celo-org.github.io/celo-token-list/assets/wormhole_wbtc.png" + }, + { + "name": "Good Dollar", + "address": "0x62B8B11039FcfE5aB0C56E502b1C372A3d2a9c7A", + "symbol": "G$", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/GoodDollar/GoodDAPP/master/src/assets/Splash/logo.svg" + }, + { + "name": "Axelar WETH", + "address": "0xb829b68f57cc546da7e5806a929e53be32a4625d", + "symbol": "axlEth", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://celo-org.github.io/celo-token-list/assets/axelar_eth.png" + }, + { + "name": "JumpToken", + "address": "0x1d18d0386f51ab03e7e84e71bda1681eba865f1f", + "symbol": "JMPT", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://celo-org.github.io/celo-token-list/assets/jumpToken.png" + }, + { + "name": "Glo Dollar", + "address": "0x4f604735c1cf31399c6e711d5962b2b3e0225ad3", + "symbol": "USDGLO", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://app.glodollar.org/glo-logo.svg" + }, + { + "name": "Curve DAO Token", + "address": "0x173fd7434B8B50dF08e3298f173487ebDB35FD14", + "symbol": "CRV", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/curvefi/curve-assets/main/branding/logo.svg" + }, + { + "name": "AtlasX Carbon Credits", + "address": "0xc3377Ea71F1dc8e55Ba360724eff2d7aD62a8670", + "symbol": "ATLASX", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://myterrablobs.blob.core.windows.net/public/token-icon.png" + }, + { + "name": "agEUR", + "address": "0xC16B81Af351BA9e64C1a069E3Ab18c244A1E3049", + "symbol": "agEUR", + "decimals": 18, + "chainId": 42220, + "logoURI": "https://raw.githubusercontent.com/AngleProtocol/angle-assets/main/0_tokens/agEUR/cross-chain/agEUR-celo.svg" + } + ] +} diff --git a/packages/app/src/models/constants.ts b/packages/app/src/models/constants.ts index d0e35e79..5a3b002d 100644 --- a/packages/app/src/models/constants.ts +++ b/packages/app/src/models/constants.ts @@ -1,34 +1,37 @@ -export type SupportedTokens = { - CELO: `0x${string}`; - cUSD: `0x${string}`; - WBTC: `0x${string}`; - G$: `0x${string}`; - // RECY: `0x${string}`; - WETH: `0x${string}`; +import { Token } from '@uniswap/sdk-core'; + +// 5% +export const acceptablePriceImpact = 5; + +export enum SupportedNetwork { + celo = 42220, +} + +export const SupportedNetworkNames: Record = { + [SupportedNetwork.celo]: 'celo', }; -export const tokenMapping: SupportedTokens = { - CELO: '0x471EcE3750Da237f93B8E339c536989b8978a438', - cUSD: '0x765de816845861e75a25fca122bb6898b8b1282a', +// Uniswap V3 Router on Celo +export const UNISWAP_V3_ROUTER_ADDRESS = '0x5615CDAb10dc425a742d643d949a7F474C01abc4'; + +export const GDToken: Token = new Token(SupportedNetwork.celo, '0x62B8B11039FcfE5aB0C56E502b1C372A3d2a9c7A', 18, 'G$'); + +// if a token is not in this list, the address from the Celo Token List is used +export const coingeckoTokenMapping: Record = { WBTC: '0xD629eb00dEced2a080B7EC630eF6aC117e614f1b', - G$: '0x62B8B11039FcfE5aB0C56E502b1C372A3d2a9c7A', - // RECY: '0x', - WETH: '0x66803FB87aBd4aaC3cbB3fAd7C3aa01f6F3FB207', + WETH: '0x2def4285787d58a2f811af24755a8150622f4361', }; -export const currencyOptions: { value: keyof SupportedTokens; label: keyof SupportedTokens }[] = [ - { value: 'G$', label: 'G$' }, - { value: 'CELO', label: 'CELO' }, - { value: 'cUSD', label: 'cUSD' }, - // { value: 'RECY', label: 'RECY' }, - { value: 'WBTC', label: 'WBTC' }, - { value: 'WETH', label: 'WETH' }, -]; - -export const frequencyOptions = [ - { value: 'One-Time', label: 'One-Time' }, - { value: 'Daily', label: 'Daily' }, - { value: 'Weekly', label: 'Weekly' }, - { value: 'Monthly', label: 'Monthly' }, - { value: 'Yearly', label: 'Yearly' }, -]; +export enum Frequency { + OneTime = 'One-Time', + Daily = 'Daily', + Weekly = 'Weekly', + Monthly = 'Monthly', + Yearly = 'Yearly', +} + +// constructed from Frequency +export const frequencyOptions: { value: Frequency; label: Frequency }[] = Object.values(Frequency).map((value) => ({ + value, + label: value, +})); diff --git a/packages/app/src/pages/DonatePage.tsx b/packages/app/src/pages/DonatePage.tsx index 68e8d66f..4d03fc72 100644 --- a/packages/app/src/pages/DonatePage.tsx +++ b/packages/app/src/pages/DonatePage.tsx @@ -19,11 +19,7 @@ function DonatePage() { return ( {isDesktopResolution && } - {!ipfsCollective ? ( -

Loading...

- ) : ( - - )} + {!ipfsCollective ?

Loading...

: }
); } diff --git a/packages/app/src/pages/ModalTestPage.tsx b/packages/app/src/pages/ModalTestPage.tsx index 670461c4..074b6659 100644 --- a/packages/app/src/pages/ModalTestPage.tsx +++ b/packages/app/src/pages/ModalTestPage.tsx @@ -1,10 +1,9 @@ import Layout from '../components/Layout'; -import DonateComponent from '../components/DonateComponent'; import SwitchModal from '../components/SwitchModal'; import CompleteDonationModal from '../components/CompleteDonationModal'; import ThankYouModal from '../components/ThankYouModal'; import ErrorModal from '../components/ErrorModal'; -import AproveSwapModal from '../components/AproveSwapModal'; +import ApproveSwapModal from '../components/ApproveSwapModal'; import StopDonationModal from '../components/StopDonationModal'; import { useState } from 'react'; @@ -15,8 +14,8 @@ function ModalTestPage() { - - + + ); diff --git a/packages/app/src/utils/index.tsx b/packages/app/src/utils/index.tsx index bdea5140..1824d811 100644 --- a/packages/app/src/utils/index.tsx +++ b/packages/app/src/utils/index.tsx @@ -1,26 +1,36 @@ import { Colors } from './colors'; +import { Frequency } from '../models/constants'; -export function shortenAddress(address: string, length = 3) { - if (!address) return ''; - const start = address.substring(0, length); - const end = address.substring(address.length - length); - return `${start}...${end}`; -} - -export function getButtonBGC(insufficientLiquidity: boolean, priceImpace: boolean, insufficientBalance: boolean) { - if (insufficientLiquidity || insufficientBalance) { +export function getDonateButtonBackgroundColor( + hasAddress: boolean, + isValidChainId: boolean, + insufficientLiquidity: boolean, + priceImpact: boolean, + insufficientBalance: boolean +) { + if (!hasAddress || !isValidChainId || insufficientLiquidity || insufficientBalance) { return Colors.gray[1000]; - } else if (priceImpace) { + } else if (priceImpact) { return Colors.orange[100]; } else { return Colors.green[100]; } } -export function getButtonText(insufficientLiquidity: boolean, priceImpace: boolean, insufficientBalance: boolean) { - if (insufficientLiquidity) { +export function getDonateButtonText( + hasAddress: boolean, + isValidChainId: boolean, + insufficientLiquidity: boolean, + priceImpact: boolean, + insufficientBalance: boolean +) { + if (!hasAddress) { + return 'Please connect wallet'; + } else if (!isValidChainId) { + return 'Unsupported network'; + } else if (insufficientLiquidity) { return 'Insufficient liquidity for this trade'; - } else if (priceImpace) { + } else if (priceImpact) { return 'Confirm & Swap Anyway'; } else if (insufficientBalance) { return 'Confirm & Swap Anyway'; @@ -28,17 +38,30 @@ export function getButtonText(insufficientLiquidity: boolean, priceImpace: boole return 'Confirm'; } } -export function getButtonTextColor(insufficientLiquidity: boolean, priceImpace: boolean, insufficientBalance: boolean) { - if (insufficientLiquidity || insufficientBalance) { +export function getDonateButtonTextColor( + hasAddress: boolean, + isValidChainId: boolean, + insufficientLiquidity: boolean, + priceImpact: boolean, + insufficientBalance: boolean +) { + if ( + !hasAddress || + !isValidChainId || + insufficientLiquidity || + insufficientBalance || + insufficientLiquidity || + insufficientBalance + ) { return Colors.gray[300]; - } else if (priceImpace) { + } else if (priceImpact) { return Colors.black; } else { return Colors.green[200]; } } -export function getFrequencyTime(frequency: string) { +export function getFrequencyPlural(frequency: Frequency) { switch (frequency) { case 'Daily': return 'Days'; @@ -50,7 +73,3 @@ export function getFrequencyTime(frequency: string) { return 'Years'; } } - -export function getTotalAmount(duration: number, amount: number) { - return duration * amount; -} diff --git a/packages/app/web/vite.config.ts b/packages/app/web/vite.config.ts index dcd24ece..94c7d59e 100644 --- a/packages/app/web/vite.config.ts +++ b/packages/app/web/vite.config.ts @@ -3,6 +3,7 @@ import react from '@vitejs/plugin-react'; import { nodePolyfills } from 'vite-plugin-node-polyfills'; import dynamicImports from 'vite-plugin-dynamic-import'; import viteTsconfigPaths from 'vite-tsconfig-paths'; +import * as path from 'path'; // https://vitejs.dev/config/ export default defineConfig({ @@ -12,6 +13,7 @@ export default defineConfig({ 'react-native': 'react-native-web', 'react-native-svg': 'react-native-svg-web', 'react-native-webview': 'react-native-web-webview', + jsbi: path.resolve(__dirname, '..', 'node_modules', 'jsbi', 'dist', 'jsbi-cjs.js'), }, dedupe: ['react', 'ethers', 'react-dom', 'native-base'], }, diff --git a/yarn.lock b/yarn.lock index ac6f1435..cb076d55 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4530,7 +4530,7 @@ __metadata: "@celo-tools/celo-ethers-wrapper": ^0.4.0 "@ethersproject/shims": ^5.7.0 "@gooddollar/good-design": ^0.1.31 - "@gooddollar/goodcollective-sdk": ^1.* + "@gooddollar/goodcollective-sdk": ^1.0.6 "@gooddollar/web3sdk-v2": ^0.2.2 "@react-native-aria/interactions": 0.2.3 "@react-native-async-storage/async-storage": ^1.18.2 @@ -4549,8 +4549,9 @@ __metadata: "@types/react-dom": ^18.2.5 "@types/react-native": ^0.72.2 "@types/react-test-renderer": ^18.0.0 + "@uniswap/router-sdk": ^1.7.1 "@uniswap/sdk-core": ^4.0.7 - "@uniswap/smart-order-router": ^3.16.23 + "@uniswap/smart-order-router": ^3.20.0 "@uniswap/v3-sdk": ^3.10.0 "@usedapp/core": ^1.2.10 "@vitejs/plugin-react": ^4.1.1 @@ -4649,7 +4650,7 @@ __metadata: languageName: unknown linkType: soft -"@gooddollar/goodcollective-sdk@^1.*, @gooddollar/goodcollective-sdk@workspace:packages/sdk-js": +"@gooddollar/goodcollective-sdk@^1.0.6, @gooddollar/goodcollective-sdk@workspace:packages/sdk-js": version: 0.0.0-use.local resolution: "@gooddollar/goodcollective-sdk@workspace:packages/sdk-js" dependencies: @@ -11601,7 +11602,7 @@ __metadata: languageName: node linkType: hard -"@uniswap/router-sdk@npm:^1.6.0": +"@uniswap/router-sdk@npm:^1.6.0, @uniswap/router-sdk@npm:^1.7.1": version: 1.7.1 resolution: "@uniswap/router-sdk@npm:1.7.1" dependencies: @@ -11663,9 +11664,9 @@ __metadata: languageName: node linkType: hard -"@uniswap/smart-order-router@npm:^3.16.23": - version: 3.17.2 - resolution: "@uniswap/smart-order-router@npm:3.17.2" +"@uniswap/smart-order-router@npm:^3.20.0": + version: 3.20.0 + resolution: "@uniswap/smart-order-router@npm:3.20.0" dependencies: "@uniswap/default-token-list": ^11.2.0 "@uniswap/permit2-sdk": ^1.2.0 @@ -11691,7 +11692,7 @@ __metadata: stats-lite: ^2.2.0 peerDependencies: jsbi: ^3.2.0 - checksum: 892fe402328337a5857caae15a1d6a3b63527424b00efe836bfada417cf7ea078c87dd1194d0f336f99fa6fcff643d4f089b13c6bfb642a502aa00afe6e29cfa + checksum: 714082c5ac4f6ffe17e7a6255cf89692e229d875e3db91ad461e3f6d137200710afc27e464ff7f7e98ff398f79ba05a08f6b165340489f12460f043a06bccd98 languageName: node linkType: hard