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}
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