diff --git a/lang/ca.json b/lang/ca.json index cd977c7edb..27b4fbedbd 100644 --- a/lang/ca.json +++ b/lang/ca.json @@ -864,7 +864,7 @@ "label.recipient_addresses": "adreces destinatàries", "label.recipient_addresses_cant": "Les adreces dels destinataris no poden estar buides", "label.recurring_donation": "Donació Recurrent", - "label.recurring_donations_currently_only_available_on_optimism": "Les donacions recurrents actualment només estan disponibles en Optimism", + "label.recurring_donations_currently_only_available_on_optimism_base": "Les donacions recurrents només estan disponibles actualment a Optimism o Base.", "label.recurring_donation_card_subheader_1": "Transmeteu les vostres donacions al llarg del temps per proporcionar finançament continu.", "label.recurring_donation_card_subheader_2": "Decideix la quantitat de tokens a dipositar en el teu saldo de transmissió, o utilitza el/els teu(s) saldo(s) de transmissió existent(s). Estableix l'import de la donació mensual en tokens i comença a transmetre.", "label.recurring_donation_maximum": "Això supera el màxim que pots donar mensualment, recarrega el saldo del teu flux per donar més!", diff --git a/lang/en.json b/lang/en.json index 2349d08785..1168f7cc31 100644 --- a/lang/en.json +++ b/lang/en.json @@ -864,7 +864,7 @@ "label.recipient_addresses": "recipient addresses", "label.recipient_addresses_cant": "Recipient addresses can't be empty", "label.recurring_donation": "Recurring Donation", - "label.recurring_donations_currently_only_available_on_optimism": "Recurring donations are currently only available on Optimism", + "label.recurring_donations_currently_only_available_on_optimism_base": "Recurring donations are currently only available on Optimism or Base.", "label.recurring_donation_card_subheader_1": "Stream your donations over time to provide continuous funding.", "label.recurring_donation_card_subheader_2": "Decide the number of tokens to deposit in your stream balance, or utilize your existing stream balance(s). Set the monthly donation amount in tokens and begin streaming.", "label.recurring_donation_maximum": "This is over the maximum you can donate monthly, top-up your stream balance to donate more!", diff --git a/lang/es.json b/lang/es.json index 4be6f4ee4b..173e238938 100644 --- a/lang/es.json +++ b/lang/es.json @@ -864,7 +864,7 @@ "label.recipient_addresses": "direcciones de los destinatarios", "label.recipient_addresses_cant": "Las direcciones de los destinatarios no pueden estar vacías", "label.recurring_donation": "Donación Recurrente", - "label.recurring_donations_currently_only_available_on_optimism": "Las donaciones recurrentes actualmente solo están disponibles en Optimism", + "label.recurring_donations_currently_only_available_on_optimism_base": "Las donaciones recurrentes están disponibles actualmente solo en Optimism o Base.", "label.recurring_donation_card_subheader_1": "Transmite tus donaciones con el tiempo para proporcionar financiamiento continuo.", "label.recurring_donation_card_subheader_2": "Decide la cantidad de tokens a depositar en tu saldo de transmisión, o utiliza tu(s) saldo(s) de transmisión existente(s). Establece el monto de la donación mensual en tokens y comienza a transmitir.", "label.recurring_donation_maximum": "¡Esto supera el máximo que puedes donar mensualmente, recarga el saldo de tu transmisión para donar más!", diff --git a/pages/test2.tsx b/pages/test2.tsx index e607b757f8..fc6f6fb8e5 100644 --- a/pages/test2.tsx +++ b/pages/test2.tsx @@ -1,153 +1,477 @@ -import { useState } from 'react'; -import { useQuery, useQueryClient } from '@tanstack/react-query'; -import { useAccount } from 'wagmi'; -import { - PublicKey, - LAMPORTS_PER_SOL, - Transaction, - SystemProgram, -} from '@solana/web3.js'; -import BigNumber from 'bignumber.js'; -import { useConnection, useWallet } from '@solana/wallet-adapter-react'; -import FailedDonation, { - EDonationFailedType, -} from '@/components/modals/FailedDonation'; -import { getTotalGIVpower } from '@/helpers/givpower'; -import { formatWeiHelper } from '@/helpers/number'; -import { useFetchSubgraphDataForAllChains } from '@/hooks/useFetchSubgraphDataForAllChains'; +import React, { useEffect, useState } from 'react'; +import { ethers } from 'ethers'; +import { Framework, Operation } from '@superfluid-finance/sdk-core'; + +const GIVETH_HOUSE_ADDRESS = '0x567c4B141ED61923967cA25Ef4906C8781069a10'; + +const TOKEN_ADDRESSES = [ + { + name: 'USDCx', + address: '0xD04383398dD2426297da660F9CCA3d439AF9ce1b', + decimals: 18, + }, + { + name: 'USDC Coin', + address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', + decimals: 6, + superToken: { + address: '0xD04383398dD2426297da660F9CCA3d439AF9ce1b', + }, + }, +]; + +const BASE_CHAIN_ID = 8453; const YourApp = () => { - const [failedModalType, setFailedModalType] = - useState(); - const queryClient = useQueryClient(); - const { address, chain } = useAccount(); - const subgraphValues = useFetchSubgraphDataForAllChains(); - - const { data } = useQuery({ - queryKey: ['interactedBlockNumber', chain?.id], - queryFn: () => 0, - staleTime: Infinity, - }); - - console.log('data', data); - - // Solana wallet hooks - const { - publicKey, - disconnect: solanaWalletDisconnect, - signMessage: solanaSignMessage, - sendTransaction: solanaSendTransaction, - connecting: solanaIsConnecting, - connected: solanaIsConnected, - } = useWallet(); - - const { connection: solanaConnection } = useConnection(); - - const donateToSolana = async () => { - if (!publicKey) { - console.error('Wallet is not connected'); - return; - } + const [tokens, setTokens] = useState< + { name: string; address: string; balance: string }[] + >([]); + const [selectedToken, setSelectedToken] = useState(''); + const [destinationAddress, setDestinationAddress] = + useState(GIVETH_HOUSE_ADDRESS); + const [loading, setLoading] = useState(true); + const [amount, setAmount] = useState('0.001'); // Initial amount + const [notification, setNotification] = useState(''); - console.log('Connection endpoint:', solanaConnection.rpcEndpoint); + const determineTokenType = async ( + sf: { + loadNativeAssetSuperToken: (arg0: any) => any; + loadWrapperSuperToken: (arg0: any) => any; + }, + tokenAddress: any, + ) => { + let superToken; - const to = 'B6bfJUMPnpL2ddngPPe3M7QNpvrv7hiYYiGtg9iCJDMS'; - const donationValue = 0.001; + // Check if it's a native super token + try { + superToken = await sf.loadNativeAssetSuperToken(tokenAddress); + console.log('Native Super Token detected.'); + return { type: 'native', superToken }; + } catch (error) { + console.log('Not a Native Super Token.'); + } + + // Check if it's a wrapper super token + try { + superToken = await sf.loadWrapperSuperToken(tokenAddress); + console.log('Wrapper Super Token detected.'); + return { type: 'wrapper', superToken }; + } catch (error) { + console.log('Not a Wrapper Super Token.'); + } - console.log('publicKey', publicKey); - console.log('Public Key string:', publicKey.toString()); + // If both checks fail, it's a regular ERC-20 token + console.log('Regular ERC-20 token detected.'); + return { type: 'erc20', superToken: null }; + }; - // Ensure the wallet has enough funds by requesting an airdrop if necessary - let balance = await solanaConnection.getBalance(publicKey); - console.log('Initial balance:', balance); - if (balance < LAMPORTS_PER_SOL) { - console.log('Airdropping 1 SOL for testing...'); - const airdropSignature = await solanaConnection.requestAirdrop( - publicKey, - LAMPORTS_PER_SOL, + // Function to check if a flow exists + const checkIfFlowExists = async ( + sf: { + cfaV1: { + getFlow: (arg0: { + superToken: any; + sender: any; + receiver: any; + providerOrSigner: any; + }) => any; + }; + }, + superTokenAddress: any, + senderAddress: any, + receiverAddress: any, + signer: any, + ) => { + try { + const flowInfo = await sf.cfaV1.getFlow({ + superToken: superTokenAddress, + sender: senderAddress, + receiver: receiverAddress, + providerOrSigner: signer, + }); + console.log( + `Existing flow found. Current flow rate: ${flowInfo.flowRate}`, ); - await solanaConnection.confirmTransaction(airdropSignature); - balance = await solanaConnection.getBalance(publicKey); - console.log('New balance:', balance); + console.log({ flowInfo }); + return { exists: true, flowRate: flowInfo.flowRate }; + } catch (error) { + console.log('No existing flow found.'); + return { exists: false, flowRate: '0' }; } + }; - const lamports = new BigNumber(donationValue) - .times(LAMPORTS_PER_SOL) - .toFixed(); + const handleApproveAndExecute = async () => { + try { + // Connect to MetaMask + if (!window.ethereum) { + alert('MetaMask not detected'); + return; + } - const transaction = new Transaction().add( - SystemProgram.transfer({ - fromPubkey: publicKey!, - toPubkey: new PublicKey(to), - lamports: BigInt(lamports), - }), - ); + const provider = new ethers.providers.Web3Provider(window.ethereum); + await provider.send('eth_requestAccounts', []); + const signer = provider.getSigner(); + const sf = await Framework.create({ + chainId: BASE_CHAIN_ID, + provider, + }); - console.log('Transaction', transaction); + console.log({ sf }); - console.log( - 'Fee Payer:', - transaction.feePayer ? transaction.feePayer.toBase58() : 'None', - ); + const address = await signer.getAddress(); - transaction.feePayer = publicKey; + // Get token details (decimals, etc.) + const token = TOKEN_ADDRESSES.find( + t => t.address === selectedToken, + ); + if (!token) { + alert('Invalid token selected.'); + return; + } + const tokenDecimals = token.decimals; - const simulationResult = - await solanaConnection.simulateTransaction(transaction); - console.log('Simulation Result:', simulationResult); + // Define the amount to approve (X.XX USDCx) + const amountToApprove = ethers.utils.parseUnits( + amount, + tokenDecimals, + ); - if (simulationResult.value.err) { - console.error('Simulation error:', simulationResult.value.err); - return; - } + const flowRatePerSecond = amountToApprove.div(30 * 24 * 60 * 60); // Convert monthly to per-second rate + console.log({ flowRatePerSecond }); + + // Determine the token type + const { type, superToken } = await determineTokenType( + sf, + selectedToken, + ); + + console.log('Selected token:', selectedToken); + console.log('Super type:', type); + console.log('Super Token:', superToken); + + if (type === 'native' || type === 'wrapper') { + console.log( + `${type.charAt(0).toUpperCase() + type.slice(1)} Super Token detected`, + ); + + // Attempt to check allowance (skip if it fails) + let allowance; + try { + allowance = await superToken.allowance({ + owner: await signer.getAddress(), + spender: destinationAddress, + providerOrSigner: signer, + }); + await allowance.wait(); + console.log(`Current allowance: ${allowance.toString()}`); + } catch (error) { + console.log( + 'Allowance does not exist or cannot be fetched. Proceeding to approve...', + ); + } + + // Approve if needed + if (ethers.BigNumber.from(allowance).lt(amountToApprove)) { + const approveOperation = superToken.approve({ + receiver: destinationAddress, + amount: amountToApprove.toString(), + }); + + const approveTxResponse = await signer.sendTransaction( + await approveOperation.getPopulatedTransactionRequest( + signer, + ), + ); + await approveTxResponse.wait(); + console.log(`Approved ${amount} ${type} super tokens.`); + } + + // Check for existing flow + const flowStatus = await checkIfFlowExists( + sf, + superToken.address, + address, + destinationAddress, + signer, + ); + + if (flowStatus.exists && flowStatus.flowRate !== '0') { + // Add new flow rate to existing flow rate + const newFlowRate = ethers.BigNumber.from( + flowStatus.flowRate, + ).add(flowRatePerSecond); + + // Update the flow + const updateFlowOperation = superToken.updateFlow({ + sender: address, + receiver: destinationAddress, + flowRate: newFlowRate.toString(), // New total flow rate + }); + + console.log('Updating existing flow...'); + const updateFlowTxResponse = await updateFlowOperation.exec( + signer, + // 70, + ); + await updateFlowTxResponse.wait(); + console.log('Flow updated successfully.'); + setNotification('Flow updated successfully!'); + } else { + // Create a new flow if none exists + const createFlowOperation = superToken.createFlow({ + sender: address, + receiver: destinationAddress, + flowRate: flowRatePerSecond.toString(), // New flow rate + }); + + console.log('Creating new flow...'); + const createFlowTxResponse = await createFlowOperation.exec( + signer, + // 2, + ); + await createFlowTxResponse.wait(); + console.log('Flow created successfully.'); + setNotification('Flow created successfully!'); + } + } else if (type === 'erc20') { + /** + * + * + * USDC native tokens + * + * + * + */ + console.log('Approving underlying ERC-20 token for upgrade...'); + + const operations: Operation[] = []; + + const underlyingToken = await sf.loadSuperToken(selectedToken); + const newSuperToken = await sf.loadWrapperSuperToken( + token.superToken?.address || '', + ); + + console.log({ underlyingToken }); + + const erc20Contract = new ethers.Contract( + selectedToken, // Underlying ERC-20 token address + [ + 'function approve(address spender, uint256 amount) public returns (bool)', + ], + signer, + ); + + // Approve the Super Token contract to spend the ERC-20 token + const approveTx = await underlyingToken.approve({ + receiver: newSuperToken.address, // Address of the Super Token + amount: amountToApprove.toString(), + }); + const approveTRANS = await approveTx.exec(signer); + await approveTRANS.wait(); // Wait for the transaction to be mined + console.log( + 'Underlying ERC-20 token approved for upgrade.', + approveTx, + ); + + console.log( + 'Upgrading ERC-20 token to Super Token...', + approveTRANS, + ); - const hash = await solanaSendTransaction(transaction, solanaConnection); + const amountToApproveNew = ethers.utils.parseUnits(amount, 18); + const flowRatePerSecondNew = amountToApproveNew.div( + 30 * 24 * 60 * 60, + ); // Convert monthly to per-second rate - console.log('hash', hash); + console.log('Upgrading......'); // THIS FAILING FIRST TIME + const upgradeTx = await newSuperToken.upgrade({ + amount: amountToApproveNew.toString(), + }); + const approveUPGRD = await upgradeTx.exec(signer); + await approveUPGRD.wait(); // Wait for the transaction to be mined + // operations.push(upgradeTx); + console.log('Upgrade to Super Token complete.', upgradeTx); + + // Create or update the stream + const flowStatus = await checkIfFlowExists( + sf, + newSuperToken.address, + address, + destinationAddress, + signer, + ); + + if (flowStatus.exists && flowStatus.flowRate !== '0') { + console.log('Updating existing flow...'); + const updateFlowOperation = newSuperToken.updateFlow({ + sender: address, + receiver: destinationAddress, + flowRate: flowRatePerSecondNew.toString(), // New flow rate + }); + // const flowTxResponse = + // await updateFlowOperation.exec(signer); + // await flowTxResponse.wait(); + + operations.push(updateFlowOperation); + + const batchOp = sf.batchCall(operations); + const txUpdate = await batchOp.exec(signer, 2); + + console.log('Flow updated successfully.', txUpdate); + setNotification('Flow updated successfully!'); + } else { + console.log('Creating new flow...'); + const createFlowOperation = newSuperToken.createFlow({ + sender: address, + receiver: destinationAddress, + flowRate: flowRatePerSecondNew.toString(), // New flow rate + }); + const flowTxResponse = await createFlowOperation.exec( + signer, + 2, + ); + await flowTxResponse.wait(); + // operations.push(createFlowOperation); + + // const batchOp = sf.batchCall(operations); + // const txCreate = await batchOp.exec(signer, 700); + + console.log('Flow created successfully.', flowTxResponse); + setNotification('Flow created successfully!'); + } + } + } catch (error) { + console.error('Error during approval or execution:', error); + setNotification( + 'Transaction failed! Please check the console for details.', + ); + } }; - return ( -
- - -
- -
+ + const balance = await contract.balanceOf(address); + const decimals = await contract.decimals(); + + return { + name: token.name, + address: token.address, + balance: ethers.utils.formatUnits( + balance, + decimals, + ), + }; + }), + ); + + setTokens(balances); + setSelectedToken(balances[0]?.address || ''); + setLoading(false); + } catch (error) { + console.error('Error fetching tokens:', error); + alert('An error occurred while fetching tokens.'); + } + }; + + fetchTokens(); + }, []); + + return ( +
+

Approve and Execute {amount} USDC

+

+ Network ID: {BASE_CHAIN_ID} Optimism Network +

+

+ Transaction to wallet: {destinationAddress} + Giveth House +

- {data} - - {chain?.id && ( -
- {failedModalType && ( - setFailedModalType(undefined)} - type={failedModalType} +
+
+

Enter Amount

+ setAmount(e.target.value)} + style={{ padding: '10px', width: '200px' }} + /> +
+
+
+

Destination Address

+ setDestinationAddress(e.target.value)} + style={{ padding: '10px', width: '500px' }} /> +
+
+ + {notification && ( +
+ {notification} +
)}
); diff --git a/src/components/views/create/AlloProtocol/AlloProtocolModal.tsx b/src/components/views/create/AlloProtocol/AlloProtocolModal.tsx index ffd95248f0..532c2e14c7 100644 --- a/src/components/views/create/AlloProtocol/AlloProtocolModal.tsx +++ b/src/components/views/create/AlloProtocol/AlloProtocolModal.tsx @@ -122,64 +122,70 @@ const AlloProtocolModal: FC = ({ ? chain.id === config.OPTIMISM_NETWORK_NUMBER : false; + const isOnBase = chain ? chain.id === config.BASE_NETWORK_NUMBER : false; + const handleButtonClick = async () => { - if (!isOnOptimism) { - switchChain?.({ chainId: config.OPTIMISM_NETWORK_NUMBER }); - } else { - try { - setIsLoading(true); - const hash = await writeContract(wagmiConfig, { - address: config.OPTIMISM_CONFIG.anchorRegistryAddress, - functionName: 'createProfile', - abi: createProfileABI.abi, - chainId: config.OPTIMISM_NETWORK_NUMBER, - args: [ - generateRandomNonce(), //nonce - addedProjectState?.id!, - { - protocol: 1, - pointer: '', - }, - addedProjectState?.adminUser?.walletAddress, //admin user wallet address - [], - ], + try { + setIsLoading(true); + const hash = await writeContract(wagmiConfig, { + address: isOnOptimism + ? config.OPTIMISM_CONFIG.anchorRegistryAddress + : config.BASE_CONFIG.anchorRegistryAddress, + functionName: 'createProfile', + abi: createProfileABI.abi, + chainId: isOnOptimism + ? config.OPTIMISM_NETWORK_NUMBER + : config.BASE_NETWORK_NUMBER, + args: [ + generateRandomNonce(), //nonce + addedProjectState?.id!, + { + protocol: 1, + pointer: '', + }, + addedProjectState?.adminUser?.walletAddress, //admin user wallet address + [], + ], + }); + setTxResult(hash); + if (hash) { + const data = await waitForTransactionReceipt(wagmiConfig, { + hash: hash, + chainId: isOnOptimism + ? config.OPTIMISM_NETWORK_NUMBER + : config.BASE_NETWORK_NUMBER, }); - setTxResult(hash); - if (hash) { - const data = await waitForTransactionReceipt(wagmiConfig, { - hash: hash, - chainId: config.OPTIMISM_NETWORK_NUMBER, - }); - - const contractAddress = extractContractAddressFromString( - data.logs[0].data, + + const contractAddress = extractContractAddressFromString( + data.logs[0].data, + ); + //Call backend to update project + await client.mutate({ + mutation: CREATE_ANCHOR_CONTRACT_ADDRESS_QUERY, + variables: { + projectId: Number(addedProjectState.id), + networkId: isOnOptimism + ? config.OPTIMISM_NETWORK_NUMBER + : config.BASE_NETWORK_NUMBER, + address: contractAddress, + txHash: hash, + }, + }); + if (!isEditMode || (isEditMode && isDraft)) { + await router.push( + slugToSuccessView(addedProjectState.slug), + ); + } else { + await router.push( + slugToProjectView(addedProjectState.slug), ); - //Call backend to update project - await client.mutate({ - mutation: CREATE_ANCHOR_CONTRACT_ADDRESS_QUERY, - variables: { - projectId: Number(addedProjectState.id), - networkId: config.OPTIMISM_NETWORK_NUMBER, - address: contractAddress, - txHash: hash, - }, - }); - if (!isEditMode || (isEditMode && isDraft)) { - await router.push( - slugToSuccessView(addedProjectState.slug), - ); - } else { - await router.push( - slugToProjectView(addedProjectState.slug), - ); - } } - setShowModal(false); // Close the modal - } catch (error) { - console.error('Error Contract', error); - } finally { - setIsLoading(false); } + setShowModal(false); // Close the modal + } catch (error) { + console.error('Error Contract', error); + } finally { + setIsLoading(false); } }; diff --git a/src/components/views/donate/DonationCard.tsx b/src/components/views/donate/DonationCard.tsx index 46524157ee..9af4a87bf7 100644 --- a/src/components/views/donate/DonationCard.tsx +++ b/src/components/views/donate/DonationCard.tsx @@ -63,6 +63,13 @@ export const DonationCard: FC = ({ address.chainType === ChainType.EVM && address.networkId === config.OPTIMISM_NETWORK_NUMBER, ); + const hasBaseAddress = + addresses && + addresses.some( + address => + address.chainType === ChainType.EVM && + address.networkId === config.BASE_NETWORK_NUMBER, + ); const isEndaomentProject = project?.organization?.label === 'endaoment'; const isOwnerOnEVM = project?.adminUser?.walletAddress && @@ -173,7 +180,7 @@ export const DonationCard: FC = ({ })} {!disableRecurringDonations && - hasOpAddress && + (hasOpAddress || hasBaseAddress) && isOwnerOnEVM ? ( = ({ ? chain.id === config.OPTIMISM_NETWORK_NUMBER : false; + const isOnBase = chain ? chain.id === config.BASE_NETWORK_NUMBER : false; + const handleButtonClick = async () => { - if (!isOnOptimism) { - switchChain?.({ chainId: config.OPTIMISM_NETWORK_NUMBER }); - } else { - try { - setIsLoading(true); - if ( - !project?.adminUser?.walletAddress || - !isAddress(project?.adminUser?.walletAddress) - ) { - throw new Error('Invalid Project Admin Address'); - } - const hash = await writeContract(wagmiConfig, { - address: config.OPTIMISM_CONFIG.anchorRegistryAddress, - functionName: 'createProfile', - abi: createProfileABI.abi, - chainId: config.OPTIMISM_NETWORK_NUMBER, - args: [ - generateRandomNonce(), //nonce - project?.id!, - { - protocol: 1, - pointer: '', - }, - project?.adminUser?.walletAddress, //admin user wallet address - [], - ], + try { + setIsLoading(true); + if ( + !project?.adminUser?.walletAddress || + !isAddress(project?.adminUser?.walletAddress) + ) { + throw new Error('Invalid Project Admin Address'); + } + const hash = await writeContract(wagmiConfig, { + address: isOnOptimism + ? config.OPTIMISM_CONFIG.anchorRegistryAddress + : config.BASE_CONFIG.anchorRegistryAddress, + functionName: 'createProfile', + abi: createProfileABI.abi, + chainId: isOnOptimism + ? config.OPTIMISM_NETWORK_NUMBER + : config.BASE_NETWORK_NUMBER, + args: [ + generateRandomNonce(), //nonce + project?.id!, + { + protocol: 1, + pointer: '', + }, + project?.adminUser?.walletAddress, //admin user wallet address + [], + ], + }); + setTxResult(hash); + if (hash) { + const data = await waitForTransactionReceipt(wagmiConfig, { + hash: hash, + chainId: isOnOptimism + ? config.OPTIMISM_NETWORK_NUMBER + : config.BASE_NETWORK_NUMBER, }); - setTxResult(hash); - if (hash) { - const data = await waitForTransactionReceipt(wagmiConfig, { - hash: hash, - chainId: config.OPTIMISM_NETWORK_NUMBER, - }); - const contractAddress = extractContractAddressFromString( - data.logs[0].data, - ); - //Call backend to update project - await client.mutate({ - mutation: CREATE_ANCHOR_CONTRACT_ADDRESS_QUERY, - variables: { - projectId: Number(project.id), - networkId: config.OPTIMISM_NETWORK_NUMBER, - address: contractAddress, - txHash: hash, - }, - }); - await fetchProject(); - onModalCompletion(); - } - setShowModal(false); // Close the modal - } catch (error) { - showToastError(error); - } finally { - setIsLoading(false); + const contractAddress = extractContractAddressFromString( + data.logs[0].data, + ); + //Call backend to update project + await client.mutate({ + mutation: CREATE_ANCHOR_CONTRACT_ADDRESS_QUERY, + variables: { + projectId: Number(project.id), + networkId: isOnOptimism + ? config.OPTIMISM_NETWORK_NUMBER + : config.BASE_NETWORK_NUMBER, + address: contractAddress, + txHash: hash, + }, + }); + await fetchProject(); + onModalCompletion(); } + setShowModal(false); // Close the modal + } catch (error) { + showToastError(error); + } finally { + setIsLoading(false); } }; + const thereTitle = isOnOptimism ? 'Optimism' : isOnBase ? 'Base' : ''; + return ( = ({ id: 'label.there_will_be_one_extra_transaction_you_need_to_sign_to', })}{' '} - Optimism + {thereTitle} .


= ({ }) => { const [amount, setAmount] = useState(0n); - const { address } = useAccount(); + const { address, chain } = useAccount(); + const recurringNetworkID = chain?.id ?? 0; const { formatMessage } = useIntl(); const { @@ -77,13 +78,13 @@ export const DepositSuperToken: FC = ({ if (!address || !superToken || !token) return; try { setStep(EModifySuperTokenSteps.APPROVING); - await ensureCorrectNetwork(config.OPTIMISM_NETWORK_NUMBER); + await ensureCorrectNetwork(recurringNetworkID); const approve = await approveERC20tokenTransfer( amount, address, superToken.id, //superTokenAddress token.id, //tokenAddress - config.OPTIMISM_CONFIG.id, + recurringNetworkID, isSafeEnv, ); if (approve) { @@ -100,7 +101,7 @@ export const DepositSuperToken: FC = ({ const onDeposit = async () => { try { setStep(EModifySuperTokenSteps.DEPOSITING); - await ensureCorrectNetwork(config.OPTIMISM_NETWORK_NUMBER); + await ensureCorrectNetwork(recurringNetworkID); if (!address) { throw new Error('address not found1'); } @@ -116,7 +117,7 @@ export const DepositSuperToken: FC = ({ throw new Error('Provider or signer not found'); const _options = { - chainId: config.OPTIMISM_CONFIG.id, + chainId: recurringNetworkID, provider: provider, resolverAddress: isProduction ? undefined diff --git a/src/components/views/donate/Recurring/ModifySuperToken/ModifySuperTokenModal.tsx b/src/components/views/donate/Recurring/ModifySuperToken/ModifySuperTokenModal.tsx index 39bc665f41..d1b6774d3c 100644 --- a/src/components/views/donate/Recurring/ModifySuperToken/ModifySuperTokenModal.tsx +++ b/src/components/views/donate/Recurring/ModifySuperToken/ModifySuperTokenModal.tsx @@ -16,6 +16,7 @@ interface IModifySuperTokenModalProps extends IModal { selectedToken: ISuperToken | IToken; tokenStreams: ISuperfluidStream[]; refreshBalance: () => void; + recurringNetworkID: number; } const headerTitleGenerator = (step: EModifySuperTokenSteps) => { @@ -97,22 +98,30 @@ const ModifySuperTokenInnerModal: FC< IModifySuperTokenInnerModalProps > = props => { const [tab, setTab] = useState(EModifyTabs.DEPOSIT); - const [token, superToken] = useMemo( - () => - props.selectedToken.isSuperToken - ? [ - props.selectedToken.underlyingToken || - config.OPTIMISM_CONFIG.SUPER_FLUID_TOKENS.find( - token => token.id === props.selectedToken.id, - )?.underlyingToken, - props.selectedToken as ISuperToken, - ] - : [ - props.selectedToken, - findSuperTokenByTokenAddress(props.selectedToken.id), - ], - [props.selectedToken], - ); + const [token, superToken] = useMemo(() => { + let superTokens: any[] = []; + if (props.recurringNetworkID === config.OPTIMISM_NETWORK_NUMBER) { + superTokens = config.OPTIMISM_CONFIG.SUPER_FLUID_TOKENS; + } else if (props.recurringNetworkID === config.POLYGON_NETWORK_NUMBER) { + superTokens = config.BASE_CONFIG.SUPER_FLUID_TOKENS; + } + + return props.selectedToken.isSuperToken + ? [ + props.selectedToken.underlyingToken || + superTokens.find( + token => token.id === props.selectedToken.id, + )?.underlyingToken, + props.selectedToken as ISuperToken, + ] + : [ + props.selectedToken, + findSuperTokenByTokenAddress( + props.selectedToken.id, + props.recurringNetworkID, + ), + ]; + }, [props.selectedToken, props.recurringNetworkID]); return ( diff --git a/src/components/views/donate/Recurring/ModifySuperToken/WithDrawSuperToken.tsx b/src/components/views/donate/Recurring/ModifySuperToken/WithDrawSuperToken.tsx index f0f1e5cd9e..8c3d1501db 100644 --- a/src/components/views/donate/Recurring/ModifySuperToken/WithDrawSuperToken.tsx +++ b/src/components/views/donate/Recurring/ModifySuperToken/WithDrawSuperToken.tsx @@ -11,7 +11,7 @@ import { IModifySuperTokenInnerModalProps } from './ModifySuperTokenModal'; import { ISuperToken, IToken } from '@/types/superFluid'; import { actionButtonLabel, EModifySuperTokenSteps } from './common'; import { ModifyWrapper, Wrapper } from './common.sc'; -import config, { isProduction } from '@/configuration'; +import { isProduction } from '@/configuration'; import { showToastError } from '@/lib/helpers'; import { Item } from '../RecurringDonationModal/Item'; import { RunOutInfo } from '../RunOutInfo'; @@ -36,7 +36,8 @@ export const WithDrawSuperToken: FC = ({ closeModal, }) => { const [amount, setAmount] = useState(0n); - const { address } = useAccount(); + const { address, chain } = useAccount(); + const recurringNetworkID = chain?.id ?? 0; const { formatMessage } = useIntl(); const tokenPrice = useTokenPrice(token); const [isWarning, setIsWarning] = useState(false); @@ -60,7 +61,7 @@ export const WithDrawSuperToken: FC = ({ const onWithdraw = async () => { setStep(EModifySuperTokenSteps.WITHDRAWING); try { - await ensureCorrectNetwork(config.OPTIMISM_NETWORK_NUMBER); + await ensureCorrectNetwork(recurringNetworkID); if (!address) { throw new Error('address not found1'); } @@ -76,7 +77,7 @@ export const WithDrawSuperToken: FC = ({ throw new Error('Provider or signer not found'); const _options = { - chainId: config.OPTIMISM_CONFIG.id, + chainId: recurringNetworkID, provider: provider, resolverAddress: isProduction ? undefined diff --git a/src/components/views/donate/Recurring/RecurringDonationCard.tsx b/src/components/views/donate/Recurring/RecurringDonationCard.tsx index 85c64d0b94..0a29355b58 100644 --- a/src/components/views/donate/Recurring/RecurringDonationCard.tsx +++ b/src/components/views/donate/Recurring/RecurringDonationCard.tsx @@ -113,8 +113,7 @@ export const RecurringDonationCard = () => { const [showAlloProtocolModal, setShowAlloProtocolModal] = useState(false); const { formatMessage } = useIntl(); - const { address } = useAccount(); - const { chain } = useAccount(); + const { address, chain } = useAccount(); const isSignedIn = useAppSelector(state => state.user.isSignedIn); const { modalCallback: signInThenDonate } = useModalCallback(() => setShowRecurringDonationModal(true), @@ -164,8 +163,8 @@ export const RecurringDonationCard = () => { tokenStreams[selectedRecurringToken?.token.id.toLowerCase() || '']; const anchorContractAddress = useMemo( - () => findAnchorContractAddress(project.anchorContracts), - [project.anchorContracts], + () => findAnchorContractAddress(project.anchorContracts, chain?.id), + [project.anchorContracts, chain?.id], ); // otherStreamsPerSec is the total flow rate of all streams except the one to the project @@ -762,7 +761,9 @@ export const RecurringDonationCard = () => { {showSelectTokenModal && ( )} - {(!chain || chain.id !== config.OPTIMISM_NETWORK_NUMBER) && ( + {(!chain || + (chain.id !== config.OPTIMISM_NETWORK_NUMBER && + chain.id !== config.BASE_NETWORK_NUMBER)) && ( )} {showRecurringDonationModal && ( @@ -787,6 +788,7 @@ export const RecurringDonationCard = () => { setShowModal={setShowTopUpModal} selectedToken={selectedRecurringToken?.token!} refreshBalance={refetch} + recurringNetworkID={chain?.id || 0} /> )} {showAlloProtocolModal && ( diff --git a/src/components/views/donate/Recurring/RecurringDonationModal/RecurringDonationModal.tsx b/src/components/views/donate/Recurring/RecurringDonationModal/RecurringDonationModal.tsx index 89863d004f..0f29ed4a28 100644 --- a/src/components/views/donate/Recurring/RecurringDonationModal/RecurringDonationModal.tsx +++ b/src/components/views/donate/Recurring/RecurringDonationModal/RecurringDonationModal.tsx @@ -24,6 +24,7 @@ import config, { isProduction } from '@/configuration'; import { findSuperTokenByTokenAddress, findUserActiveStreamOnSelectedToken, + checkIfRecurringFlowExist, } from '@/helpers/donate'; import { ONE_MONTH_SECONDS } from '@/lib/constants/constants'; import { RunOutInfo } from '../RunOutInfo'; @@ -41,6 +42,7 @@ import { getEthersProvider, getEthersSigner } from '@/helpers/ethers'; import { ERecurringDonationStatus } from '@/apollo/types/types'; import { findAnchorContractAddress } from '@/helpers/superfluid'; import { ensureCorrectNetwork } from '@/helpers/network'; + interface IRecurringDonationModalProps extends IModal { donationToGiveth: number; amount: bigint; @@ -132,7 +134,8 @@ const RecurringDonationInnerModal: FC = ({ tokenStreams, setSuccessDonation, } = useDonateData(); - const { address } = useAccount(); + const { address, chain } = useAccount(); + const recurringNetworkID = chain?.id ?? 0; const tokenPrice = useTokenPrice(selectedRecurringToken?.token); const isSafeEnv = useIsSafeEnvironment(); const { formatMessage } = useIntl(); @@ -149,7 +152,7 @@ const RecurringDonationInnerModal: FC = ({ const onApprove = async () => { try { - await ensureCorrectNetwork(config.OPTIMISM_NETWORK_NUMBER); + await ensureCorrectNetwork(recurringNetworkID); console.log( 'amount', formatUnits( @@ -161,6 +164,7 @@ const RecurringDonationInnerModal: FC = ({ if (!address || !selectedRecurringToken) return; const superToken = findSuperTokenByTokenAddress( selectedRecurringToken.token.id, + recurringNetworkID, ); if (!superToken) throw new Error('SuperToken not found'); const approve = await approveERC20tokenTransfer( @@ -168,7 +172,7 @@ const RecurringDonationInnerModal: FC = ({ address, superToken.id, //superTokenAddress selectedRecurringToken?.token.id, //tokenAddress - config.OPTIMISM_CONFIG.id, + recurringNetworkID, isSafeEnv, ); if (approve) { @@ -183,10 +187,11 @@ const RecurringDonationInnerModal: FC = ({ const onDonate = async () => { try { - await ensureCorrectNetwork(config.OPTIMISM_NETWORK_NUMBER); + await ensureCorrectNetwork(recurringNetworkID); setStep(EDonationSteps.DONATING); const projectAnchorContract = findAnchorContractAddress( project?.anchorContracts, + recurringNetworkID, ); if (!projectAnchorContract) { throw new Error('Project anchor address not found'); @@ -201,7 +206,10 @@ const RecurringDonationInnerModal: FC = ({ throw new Error('Provider or signer not found'); let _superToken = selectedRecurringToken.token; if (!_superToken.isSuperToken) { - const sp = findSuperTokenByTokenAddress(_superToken.id); + const sp = findSuperTokenByTokenAddress( + _superToken.id, + recurringNetworkID, + ); if (!sp) { throw new Error('Super token not found'); } else { @@ -210,7 +218,7 @@ const RecurringDonationInnerModal: FC = ({ } const _options = { - chainId: config.OPTIMISM_CONFIG.id, + chainId: recurringNetworkID, provider: provider, resolverAddress: isProduction ? undefined @@ -250,13 +258,30 @@ const RecurringDonationInnerModal: FC = ({ .toBigInt(); } + // isUpdating is local variable to check if we are updating/modifying the flow + let willUpdateFlow = isUpdating; + + // if isUpdating is false we need to check if there is an existing flow in the network + if (willUpdateFlow === false) { + const existingFlow = await checkIfRecurringFlowExist( + sf, + _superToken.id, + address, + projectAnchorContract, + signer, + ); + if (existingFlow.exists && existingFlow.flowRate !== '0') { + willUpdateFlow = true; + } + } + // Upgrade the token to super token - if (!isUpdating && !selectedRecurringToken.token.isSuperToken) { + if (!willUpdateFlow && !selectedRecurringToken.token.isSuperToken) { const upgradeOperation = await superToken.upgrade({ amount: newAmount.toString(), }); - //Upgrading ETHx is a special case and can't be batched + // Upgrading ETHx is a special case and can't be batched if (_superToken.symbol === 'ETHx') { await upgradeOperation.exec(signer); } else { @@ -279,23 +304,25 @@ const RecurringDonationInnerModal: FC = ({ flowRate: _flowRate.toString(), }; - let projectFlowOp = isUpdating + let projectFlowOp = willUpdateFlow ? superToken.updateFlow(options) : superToken.createFlow(options); operations.push(projectFlowOp); - const isDonatingToGiveth = !isUpdating && donationToGiveth > 0; + const isDonatingToGiveth = !willUpdateFlow && donationToGiveth > 0; console.log( 'isDonatingToGiveth', isDonatingToGiveth, - isUpdating, + willUpdateFlow, donationToGiveth > 0, ); let givethOldStream; let givethFlowRate = 0n; if (isDonatingToGiveth) { const givethAnchorContract = - config.OPTIMISM_CONFIG.GIVETH_ANCHOR_CONTRACT_ADDRESS; + recurringNetworkID === config.OPTIMISM_NETWORK_NUMBER + ? config.OPTIMISM_CONFIG.GIVETH_ANCHOR_CONTRACT_ADDRESS + : config.BASE_CONFIG.GIVETH_ANCHOR_CONTRACT_ADDRESS; if (!givethAnchorContract) { throw new Error('Giveth wallet address not found'); @@ -313,6 +340,7 @@ const RecurringDonationInnerModal: FC = ({ _superToken, ); + // Update Giveth stream if it exists if (givethOldStream) { givethFlowRate = _newFlowRate + BigInt(givethOldStream.currentFlowRate); @@ -324,6 +352,7 @@ const RecurringDonationInnerModal: FC = ({ }); operations.push(givethFlowOp); } else { + // Create Giveth stream if it doesn't exist givethFlowRate = _newFlowRate; const givethFlowOp = superToken.createFlow({ sender: address, @@ -340,11 +369,11 @@ const RecurringDonationInnerModal: FC = ({ const projectDraftDonationInfo: ICreateDraftRecurringDonation = { projectId: +project.id, anonymous, - chainId: config.OPTIMISM_NETWORK_NUMBER, + chainId: recurringNetworkID, flowRate: _flowRate, superToken: _superToken, isBatch, - isForUpdate: isUpdating, + isForUpdate: willUpdateFlow, }; // Save Draft Donation @@ -355,11 +384,11 @@ const RecurringDonationInnerModal: FC = ({ const givethDraftDonationInfo: ICreateDraftRecurringDonation = { projectId: config.GIVETH_PROJECT_ID, anonymous, - chainId: config.OPTIMISM_NETWORK_NUMBER, + chainId: recurringNetworkID, flowRate: givethFlowRate, superToken: _superToken, isBatch, - isForUpdate: isUpdating, + isForUpdate: willUpdateFlow, }; let givethDraftDonationId = 0; if (isDonatingToGiveth) { @@ -368,7 +397,7 @@ const RecurringDonationInnerModal: FC = ({ ); } - await ensureCorrectNetwork(config.OPTIMISM_NETWORK_NUMBER); + await ensureCorrectNetwork(recurringNetworkID); if (isBatch) { const batchOp = sf.batchCall(operations); @@ -385,7 +414,7 @@ const RecurringDonationInnerModal: FC = ({ txHash: tx.hash, draftDonationId: projectDraftDonationId, }; - if (isUpdating) { + if (willUpdateFlow) { console.log('Start Update Project Donation Info'); projectDonationId = await updateRecurringDonation(projectDonationInfo); @@ -484,14 +513,14 @@ const RecurringDonationInnerModal: FC = ({ ], isRecurring: true, givBackEligible: true, - chainId: config.OPTIMISM_NETWORK_NUMBER, + chainId: recurringNetworkID, }); } else { setSuccessDonation({ txHash: [{ txHash: tx.hash, chainType: ChainType.EVM }], isRecurring: true, givBackEligible: true, - chainId: config.OPTIMISM_NETWORK_NUMBER, + chainId: recurringNetworkID, }); } } diff --git a/src/components/views/donate/Recurring/SelectTokenModal/SelectTokenModal.tsx b/src/components/views/donate/Recurring/SelectTokenModal/SelectTokenModal.tsx index cf5fb3c854..d111d7f59c 100644 --- a/src/components/views/donate/Recurring/SelectTokenModal/SelectTokenModal.tsx +++ b/src/components/views/donate/Recurring/SelectTokenModal/SelectTokenModal.tsx @@ -45,11 +45,16 @@ export interface IBalances { [key: string]: bigint; } -const superTokens = config.OPTIMISM_CONFIG.SUPER_FLUID_TOKENS; - const SelectTokenInnerModal: FC = ({ setShowModal, }) => { + const { chain } = useAccount(); + + const superTokens = + chain?.id === config.OPTIMISM_NETWORK_NUMBER + ? config.OPTIMISM_CONFIG.SUPER_FLUID_TOKENS + : config.BASE_CONFIG.SUPER_FLUID_TOKENS; + const [tokens, setTokens] = useState([]); const [underlyingTokens, setUnderlyingTokens] = useState([]); const [balances, setBalances] = useState({}); diff --git a/src/components/views/donate/WrongNetworkLayer.tsx b/src/components/views/donate/WrongNetworkLayer.tsx index 28c2b4e3a1..a103566297 100644 --- a/src/components/views/donate/WrongNetworkLayer.tsx +++ b/src/components/views/donate/WrongNetworkLayer.tsx @@ -1,13 +1,13 @@ import React from 'react'; import styled from 'styled-components'; import { - B, Caption, IconInfoFilled16, brandColors, neutralColors, Flex, FlexCenter, + Button, } from '@giveth/ui-design-system'; import { useSwitchChain } from 'wagmi'; import { useIntl } from 'react-intl'; @@ -27,26 +27,10 @@ export const WrongNetworkLayer = () => { {formatMessage({ - id: 'label.recurring_donations_currently_only_available_on_optimism', + id: 'label.recurring_donations_currently_only_available_on_optimism_base', })} - { - if (isOnEVM) { - switchChain && - switchChain({ - chainId: config.OPTIMISM_NETWORK_NUMBER, - }); - } else { - await handleSingOutAndSignInWithEVM(); - } - }} - > - {formatMessage({ - id: 'label.switch_network', - })} - {formatMessage( @@ -54,7 +38,44 @@ export const WrongNetworkLayer = () => { id: 'label.switch_to_network_to_continue_donating', }, { - network: {config.OPTIMISM_CONFIG.name}, + network: ( + <> + { + if (isOnEVM) { + switchChain && + switchChain({ + chainId: + config + .OPTIMISM_CONFIG + .id, + }); + } else { + await handleSingOutAndSignInWithEVM(); + } + }} + />{' '} + or{' '} + { + if (isOnEVM) { + switchChain && + switchChain({ + chainId: + config.BASE_CONFIG + .id, + }); + } else { + await handleSingOutAndSignInWithEVM(); + } + }} + /> + + ), }, )} @@ -94,11 +115,11 @@ const Title = styled(Flex)` color: ${brandColors.giv[500]}; `; -const SwitchButton = styled(B)` - cursor: pointer; - color: ${brandColors.pinky[500]}; - &:hover { - color: ${brandColors.pinky[600]}; +const ButtonLinkHolder = styled(Button)` + display: inline-block !important; + padding: 0; + & span:hover { + color: ${neutralColors.gray[400]}; } `; diff --git a/src/components/views/project/projectActionCard/AdminActions.tsx b/src/components/views/project/projectActionCard/AdminActions.tsx index 2460486d83..bafef0f83c 100644 --- a/src/components/views/project/projectActionCard/AdminActions.tsx +++ b/src/components/views/project/projectActionCard/AdminActions.tsx @@ -67,6 +67,7 @@ export const AdminActions = () => { const anchorContractAddress = findAnchorContractAddress( project.anchorContracts, + chain?.id, ); const options: IOption[] = [ diff --git a/src/components/views/userProfile/donationsTab/recurringTab/EndStreamModal.tsx b/src/components/views/userProfile/donationsTab/recurringTab/EndStreamModal.tsx index 2fd8130af4..a0505911f8 100644 --- a/src/components/views/userProfile/donationsTab/recurringTab/EndStreamModal.tsx +++ b/src/components/views/userProfile/donationsTab/recurringTab/EndStreamModal.tsx @@ -31,6 +31,7 @@ enum EEndStreamSteps { export interface IEndStreamModalProps extends IModal { donation: IWalletRecurringDonation; refetch: () => void; + recurringNetworkId: number; } export const EndStreamModal: FC = ({ ...props }) => { @@ -60,11 +61,13 @@ export const EndStreamModal: FC = ({ ...props }) => { interface IEndStreamInnerModalProps extends IEndStreamModalProps { closeModal: (refetch?: boolean) => void; + recurringNetworkId: number; } const EndStreamInnerModal: FC = ({ closeModal, donation, + recurringNetworkId, }) => { const [step, setStep] = useState(EEndStreamSteps.CONFIRM); const { formatMessage } = useIntl(); @@ -81,9 +84,15 @@ const EndStreamInnerModal: FC = ({ throw new Error('Please connect your wallet first'); } - const _superToken = config.OPTIMISM_CONFIG.SUPER_FLUID_TOKENS.find( - s => s.underlyingToken.symbol === donation.currency, - ); + const _superToken = + recurringNetworkId === config.OPTIMISM_NETWORK_NUMBER + ? config.OPTIMISM_CONFIG.SUPER_FLUID_TOKENS.find( + s => s.underlyingToken.symbol === donation.currency, + ) + : config.BASE_CONFIG.SUPER_FLUID_TOKENS.find( + s => s.underlyingToken.symbol === donation.currency, + ); + if (!_superToken) { throw new Error('SuperToken not found'); } diff --git a/src/components/views/userProfile/donationsTab/recurringTab/ModifyStreamModal/ModifyStreamInnerModal.tsx b/src/components/views/userProfile/donationsTab/recurringTab/ModifyStreamModal/ModifyStreamInnerModal.tsx index 0ee6388caa..1d8f9783e6 100644 --- a/src/components/views/userProfile/donationsTab/recurringTab/ModifyStreamModal/ModifyStreamInnerModal.tsx +++ b/src/components/views/userProfile/donationsTab/recurringTab/ModifyStreamModal/ModifyStreamInnerModal.tsx @@ -43,6 +43,7 @@ interface IModifyStreamInnerModalProps extends IModifyStreamModalProps { superToken: IToken; tokenStreams: ITokenStreams; setModifyInfo: Dispatch>; + recurringNetworkId: number; } interface IGeneralInfo { @@ -56,6 +57,7 @@ export const ModifyStreamInnerModal: FC = ({ setStep, tokenStreams, setModifyInfo, + recurringNetworkId, }) => { const [percentage, setPercentage] = useState(0); const [info, setInfo] = useState({ @@ -117,6 +119,7 @@ export const ModifyStreamInnerModal: FC = ({ }; const anchorContractAddress = findAnchorContractAddress( donation.project.anchorContracts, + recurringNetworkId, ); for (let i = 0; i < tokenStream.length; i++) { const ts = tokenStream[i]; diff --git a/src/components/views/userProfile/donationsTab/recurringTab/ModifyStreamModal/ModifyStreamModal.tsx b/src/components/views/userProfile/donationsTab/recurringTab/ModifyStreamModal/ModifyStreamModal.tsx index d245f04abc..09d333ce1a 100644 --- a/src/components/views/userProfile/donationsTab/recurringTab/ModifyStreamModal/ModifyStreamModal.tsx +++ b/src/components/views/userProfile/donationsTab/recurringTab/ModifyStreamModal/ModifyStreamModal.tsx @@ -28,6 +28,7 @@ export interface IModifyDonationInfo { export interface IModifyStreamModalProps extends IModal { donation: IWalletRecurringDonation; refetch: () => void; + recurringNetworkId: number; } export const ModifyStreamModal: FC = ({ @@ -39,13 +40,16 @@ export const ModifyStreamModal: FC = ({ const { formatMessage } = useIntl(); const { tokenStreams } = useUserStreams(); - const superToken = useMemo( - () => - config.OPTIMISM_CONFIG.SUPER_FLUID_TOKENS.find( + const superToken = useMemo(() => { + if (props.recurringNetworkId === config.OPTIMISM_NETWORK_NUMBER) { + return config.OPTIMISM_CONFIG.SUPER_FLUID_TOKENS.find( s => s.underlyingToken.symbol === props.donation.currency, - ), - [props.donation.currency], - ); + ); + } + return config.BASE_CONFIG.SUPER_FLUID_TOKENS.find( + s => s.underlyingToken.symbol === props.donation.currency, + ); + }, [props.recurringNetworkId, props.donation.currency]); const handleCloseModal = () => { if (step === EDonationSteps.SUCCESS) { @@ -71,6 +75,7 @@ export const ModifyStreamModal: FC = ({ tokenStreams={tokenStreams} superToken={superToken!} setModifyInfo={setModifyInfo} + recurringNetworkId={props.recurringNetworkId} /> ) : ( = ({ token={modifyInfo?.token!} closeModal={handleCloseModal} {...props} + recurringNetworkId={props.recurringNetworkId} /> )}
diff --git a/src/components/views/userProfile/donationsTab/recurringTab/ModifyStreamModal/UpdateStreamInnerModal.tsx b/src/components/views/userProfile/donationsTab/recurringTab/ModifyStreamModal/UpdateStreamInnerModal.tsx index 0305bcde03..a53f518f0e 100644 --- a/src/components/views/userProfile/donationsTab/recurringTab/ModifyStreamModal/UpdateStreamInnerModal.tsx +++ b/src/components/views/userProfile/donationsTab/recurringTab/ModifyStreamModal/UpdateStreamInnerModal.tsx @@ -10,7 +10,7 @@ import { Item } from '@/components/views/donate/Recurring/RecurringDonationModal import { IToken } from '@/types/superFluid'; import { RunOutInfo } from '@/components/views/donate/Recurring/RunOutInfo'; import { useTokenPrice } from '@/hooks/useTokenPrice'; -import config, { isProduction } from '@/configuration'; +import { isProduction } from '@/configuration'; import { getEthersProvider, getEthersSigner } from '@/helpers/ethers'; import { ONE_MONTH_SECONDS } from '@/lib/constants/constants'; import { showToastError } from '@/lib/helpers'; @@ -36,6 +36,7 @@ interface IModifyStreamInnerModalProps extends IModifyStreamModalProps { flowRatePerMonth: bigint; streamFlowRatePerMonth: bigint; closeModal: () => void; + recurringNetworkId: number; } export const UpdateStreamInnerModal: FC = ({ @@ -47,6 +48,7 @@ export const UpdateStreamInnerModal: FC = ({ flowRatePerMonth, streamFlowRatePerMonth, closeModal, + recurringNetworkId, }) => { const [tx, setTx] = useState(''); const { formatMessage } = useIntl(); @@ -57,9 +59,10 @@ export const UpdateStreamInnerModal: FC = ({ const onDonate = async () => { setStep(EDonationSteps.DONATING); try { - await ensureCorrectNetwork(config.OPTIMISM_NETWORK_NUMBER); + await ensureCorrectNetwork(recurringNetworkId); const projectAnchorContract = findAnchorContractAddress( donation.project.anchorContracts, + recurringNetworkId, ); if (!projectAnchorContract) { throw new Error('Project anchor address not found'); @@ -74,7 +77,7 @@ export const UpdateStreamInnerModal: FC = ({ throw new Error('Provider or signer not found'); const _options = { - chainId: config.OPTIMISM_CONFIG.id, + chainId: recurringNetworkId, provider: provider, resolverAddress: isProduction ? undefined @@ -104,7 +107,7 @@ export const UpdateStreamInnerModal: FC = ({ recurringDonationId: donation.id, projectId: +donation.project.id, anonymous: donation.anonymous, - chainId: config.OPTIMISM_NETWORK_NUMBER, + chainId: recurringNetworkId, flowRate: _flowRatePerSec, superToken: token, isForUpdate: true, @@ -118,7 +121,8 @@ export const UpdateStreamInnerModal: FC = ({ setTx(tx.hash); let projectDonationId = 0; - // saving project donation to backend + + // Saving project donation to backend try { const projectDonationInfo = { ...projectDraftDonationInfo, diff --git a/src/components/views/userProfile/donationsTab/recurringTab/RecurringDonationsTable.tsx b/src/components/views/userProfile/donationsTab/recurringTab/RecurringDonationsTable.tsx index 7cd82360f0..e012bf6ae8 100644 --- a/src/components/views/userProfile/donationsTab/recurringTab/RecurringDonationsTable.tsx +++ b/src/components/views/userProfile/donationsTab/recurringTab/RecurringDonationsTable.tsx @@ -157,6 +157,7 @@ const RecurringDonationTable: FC = ({ diff --git a/src/components/views/userProfile/donationsTab/recurringTab/StreamActionButton.tsx b/src/components/views/userProfile/donationsTab/recurringTab/StreamActionButton.tsx index 1c89345535..e8f3064235 100644 --- a/src/components/views/userProfile/donationsTab/recurringTab/StreamActionButton.tsx +++ b/src/components/views/userProfile/donationsTab/recurringTab/StreamActionButton.tsx @@ -18,17 +18,18 @@ import { } from '@/apollo/types/types'; import { EndStreamModal } from './EndStreamModal'; import { ArchiveStreamModal } from './ArchiveStreamModal'; -import config from '@/configuration'; import { slugToProjectDonate } from '@/lib/routeCreators'; interface IStreamActionButtonProps { donation: IWalletRecurringDonation; refetch: () => void; + recurringNetworkId: number; } export const StreamActionButton: FC = ({ donation, refetch, + recurringNetworkId, }) => { const [showModify, setShowModify] = useState(false); const [showEnd, setShowEnd] = useState(false); @@ -101,9 +102,9 @@ export const StreamActionButton: FC = ({ return options.length > 0 ? ( { - if (chainId !== config.OPTIMISM_NETWORK_NUMBER) { + if (recurringNetworkId !== chainId) { switchChain?.({ - chainId: config.OPTIMISM_NETWORK_NUMBER, + chainId: recurringNetworkId, }); } }} @@ -119,6 +120,7 @@ export const StreamActionButton: FC = ({ setShowModal={setShowModify} donation={donation} refetch={refetch} + recurringNetworkId={recurringNetworkId} /> )} {showEnd && ( @@ -126,6 +128,7 @@ export const StreamActionButton: FC = ({ setShowModal={setShowEnd} donation={donation} refetch={refetch} + recurringNetworkId={recurringNetworkId} /> )} {showArchive && ( diff --git a/src/components/views/userProfile/donationsTab/recurringTab/StreamRow.tsx b/src/components/views/userProfile/donationsTab/recurringTab/StreamRow.tsx index 7df4fbd631..bb5bf9661a 100644 --- a/src/components/views/userProfile/donationsTab/recurringTab/StreamRow.tsx +++ b/src/components/views/userProfile/donationsTab/recurringTab/StreamRow.tsx @@ -122,6 +122,7 @@ export const StreamRow: FC = ({ tokenStream }) => { setShowModal={setShowModifyModal} selectedToken={superToken} refreshBalance={refetch} + recurringNetworkID={chainId || 0} /> )} diff --git a/src/components/views/userProfile/projectsTab/ClaimWithdrawalModal.tsx b/src/components/views/userProfile/projectsTab/ClaimWithdrawalModal.tsx index a28cd8695d..216ff64273 100644 --- a/src/components/views/userProfile/projectsTab/ClaimWithdrawalModal.tsx +++ b/src/components/views/userProfile/projectsTab/ClaimWithdrawalModal.tsx @@ -10,6 +10,7 @@ import { Address, encodeFunctionData } from 'viem'; import { useState } from 'react'; import { useIntl } from 'react-intl'; import { writeContract, waitForTransactionReceipt } from '@wagmi/core'; +import { useAccount } from 'wagmi'; import { Modal } from '@/components/modals/Modal'; import { IModal } from '@/types/common'; import { useModalAnimation } from '@/hooks/useModalAnimation'; @@ -75,18 +76,29 @@ const ClaimWithdrawalModal = ({ useState(ClaimTransactionState.NOT_STARTED); const [txHash, setTxHash] = useState
(); const { formatMessage } = useIntl(); + const { chain } = useAccount(); + const recurringNetworkID = chain?.id ?? 0; const projectName = project.title || ''; - const optimismAddress = project.addresses?.find( - address => address.networkId === config.OPTIMISM_NETWORK_NUMBER, - )?.address; + let projectAddress = ''; + if (recurringNetworkID === config.OPTIMISM_NETWORK_NUMBER) { + projectAddress = + project.addresses?.find( + address => address.networkId === config.OPTIMISM_NETWORK_NUMBER, + )?.address || ''; + } else if (recurringNetworkID === config.BASE_NETWORK_NUMBER) { + projectAddress = + project.addresses?.find( + address => address.networkId === config.BASE_NETWORK_NUMBER, + )?.address || ''; + } const isETHx = selectedStream.token.symbol.toLowerCase() === 'ethx'; const handleConfirm = async () => { console.log('anchorContractAddress', anchorContractAddress); try { console.log('isETHx', isETHx); - await ensureCorrectNetwork(config.OPTIMISM_NETWORK_NUMBER); + await ensureCorrectNetwork(recurringNetworkID); const encodedDowngradeTo = isETHx ? encodeFunctionData({ abi: ISETH.abi, @@ -97,7 +109,7 @@ const ClaimWithdrawalModal = ({ abi: superTokenABI.abi, functionName: 'downgradeTo', args: [ - optimismAddress, + projectAddress, +selectedStream.balance.toString(), ], }); @@ -114,7 +126,7 @@ const ClaimWithdrawalModal = ({ abi: anchorContractABI.abi, address: anchorContractAddress, functionName: 'execute', - chainId: config.OPTIMISM_NETWORK_NUMBER, + chainId: recurringNetworkID, args: [selectedStream.token.id, '', encodedDowngradeTo], }); @@ -124,7 +136,7 @@ const ClaimWithdrawalModal = ({ wagmiConfig, { hash: tx, - chainId: config.OPTIMISM_NETWORK_NUMBER, + chainId: recurringNetworkID, }, ); @@ -141,9 +153,9 @@ const ClaimWithdrawalModal = ({ abi: anchorContractABI.abi, address: anchorContractAddress, functionName: 'execute', - chainId: config.OPTIMISM_NETWORK_NUMBER, + chainId: recurringNetworkID, args: [ - optimismAddress, + projectAddress, +selectedStream.balance.toString(), '', ], @@ -152,7 +164,7 @@ const ClaimWithdrawalModal = ({ if (transferEthTx) { await waitForTransactionReceipt(wagmiConfig, { hash: transferEthTx, - chainId: config.OPTIMISM_NETWORK_NUMBER, + chainId: recurringNetworkID, }); } setTransactionState(ClaimTransactionState.SUCCESS); @@ -207,7 +219,7 @@ const ClaimWithdrawalModal = ({ = ({ const anchorContractAddress = findAnchorContractAddress( project.anchorContracts, + chain?.id, ); const chainId = chain?.id; diff --git a/src/config/development.tsx b/src/config/development.tsx index 0cb620561b..f51bbbf90e 100644 --- a/src/config/development.tsx +++ b/src/config/development.tsx @@ -425,6 +425,28 @@ const config: EnvConfig = { }, anchorRegistryAddress: '0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3', chainLogo: (logoSize?: number) => , + GIVETH_ANCHOR_CONTRACT_ADDRESS: + '0x5430757bc19c87ec562e4660e56af6cac324b50a', + superFluidSubgraph: + process.env.NEXT_PUBLIC_SUBGRAPH_SUPER_FLUID || + 'https://subgraph-endpoints.superfluid.dev/base-mainnet/protocol-v1', + SUPER_FLUID_TOKENS: [ + { + underlyingToken: { + decimals: 18, + id: '0x6B0dacea6a72E759243c99Eaed840DEe9564C194', + name: 'fUSDC Fake Token', + symbol: 'USDC', + coingeckoId: 'usd-coin', + }, + decimals: 18, + id: '0x1650581F573eAd727B92073B5Ef8B4f5B94D1648', + name: 'Super fUSDC Fake Token', + symbol: 'fUSDCx', + isSuperToken: true, + coingeckoId: 'usd-coin', + }, + ], }, ZKEVM_CONFIG: { diff --git a/src/config/production.tsx b/src/config/production.tsx index 5a8deca43c..4e940d3aca 100644 --- a/src/config/production.tsx +++ b/src/config/production.tsx @@ -580,6 +580,7 @@ const config: EnvConfig = { coingeckoChainName: 'celo', chainLogo: (logoSize = 24) => , }, + ARBITRUM_CONFIG: { ...arbitrum, chainType: ChainType.EVM, @@ -590,6 +591,7 @@ const config: EnvConfig = { coingeckoChainName: 'arbitrum', chainLogo: (logoSize = 24) => , }, + BASE_CONFIG: { ...base, chainType: ChainType.EVM, @@ -600,6 +602,88 @@ const config: EnvConfig = { subgraphAddress: '', coingeckoChainName: 'base', chainLogo: (logoSize = 24) => , + GIVETH_ANCHOR_CONTRACT_ADDRESS: + '0x5430757bc19c87ec562e4660e56af6cac324b50a', + superFluidSubgraph: + process.env.NEXT_PUBLIC_SUBGRAPH_SUPER_FLUID || + 'https://subgraph-endpoints.superfluid.dev/base-mainnet/protocol-v1', + SUPER_FLUID_TOKENS: [ + { + underlyingToken: { + decimals: 6, + id: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', + name: 'USD Coin', + symbol: 'USDC', + coingeckoId: 'usd-coin', + }, + decimals: 18, + id: '0xD04383398dD2426297da660F9CCA3d439AF9ce1b', + name: 'Super USD Coin', + symbol: 'USDCx', + isSuperToken: true, + coingeckoId: 'usd-coin', + }, + { + underlyingToken: { + decimals: 18, + id: '0x0000000000000000000000000000000000000000', + name: 'Ethereum', + symbol: 'ETH', + coingeckoId: 'ethereum', + }, + decimals: 18, + id: '0x46fd5cfB4c12D87acD3a13e92BAa53240C661D93', + name: 'Super ETH', + symbol: 'ETHx', + isSuperToken: true, + coingeckoId: 'ethereum', + }, + { + underlyingToken: { + decimals: 18, + id: '0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf', + name: 'Coinbase Wrapped BTC', + symbol: 'cbBTC', + coingeckoId: 'bitcoin', + }, + decimals: 18, + id: '0xDFd428908909CB5E24F5e79E6aD6BDE10bdf2327', + name: 'Coinbase wrapped BTC', + symbol: 'cbBTCx', + isSuperToken: true, + coingeckoId: 'bitcoin', + }, + { + underlyingToken: { + decimals: 18, + id: '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb', + name: 'Dai Stablecoin', + symbol: 'DAI', + coingeckoId: 'dai', + }, + decimals: 18, + id: '0x708169c8C87563Ce904E0a7F3BFC1F3b0b767f41', + name: 'Coinbase wrapped BTC', + symbol: 'DAIx', + isSuperToken: true, + coingeckoId: 'dai', + }, + { + underlyingToken: { + decimals: 18, + id: '0x4ed4E862860beD51a9570b96d89aF5E1B0Efefed', + name: 'Degen', + symbol: 'DEGEN', + coingeckoId: 'degen-base', + }, + decimals: 18, + id: '0x1efF3Dd78F4A14aBfa9Fa66579bD3Ce9E1B30529', + name: 'Super Degen', + symbol: 'DEGENx', + isSuperToken: true, + coingeckoId: 'degen-base', + }, + ], }, ZKEVM_CONFIG: { diff --git a/src/helpers/donate.ts b/src/helpers/donate.ts index e45dc72ed4..a9f251d32a 100644 --- a/src/helpers/donate.ts +++ b/src/helpers/donate.ts @@ -3,8 +3,30 @@ import config from '@/configuration'; import { type ITokenStreams } from '@/context/donate.context'; import { ISuperfluidStream, IToken } from '@/types/superFluid'; -export const findSuperTokenByTokenAddress = (tokenAddress: Address) => { - return config.OPTIMISM_CONFIG.SUPER_FLUID_TOKENS.find( +/** + * Finds the corresponding SuperToken for a given token address within a specific network. + * We for now ONLY USE the Optimism network and the Base network. + * + * @param {Address} tokenAddress - The address of the underlying token to search for. + * @param {number} recurringNetworkID - The ID of the network where the search should occur. + * This can be either the Optimism network or the Base network. + * @returns {Object | undefined} - The matching SuperToken object if found, otherwise undefined. + * + * Logic: + * - If the recurring network ID matches the Optimism network, it searches within the + * Optimism configuration's SuperFluid tokens. + * - Otherwise, it defaults to searching within the Base configuration's SuperFluid tokens. + */ +export const findSuperTokenByTokenAddress = ( + tokenAddress: Address, + recurringNetworkID: number, +) => { + if (recurringNetworkID === config.OPTIMISM_NETWORK_NUMBER) { + return config.OPTIMISM_CONFIG.SUPER_FLUID_TOKENS.find( + token => token.underlyingToken.id === tokenAddress, + ); + } + return config.BASE_CONFIG.SUPER_FLUID_TOKENS.find( token => token.underlyingToken.id === tokenAddress, ); }; @@ -40,3 +62,33 @@ export const countActiveStreams = (tokenStreams: ISuperfluidStream[]) => { 0 ); }; + +// Function to check if a flow exists +export const checkIfRecurringFlowExist = async ( + sf: { + cfaV1: { + getFlow: (arg0: { + superToken: any; + sender: any; + receiver: any; + providerOrSigner: any; + }) => any; + }; + }, + superTokenAddress: any, + senderAddress: any, + receiverAddress: any, + signer: any, +) => { + try { + const flowInfo = await sf.cfaV1.getFlow({ + superToken: superTokenAddress, + sender: senderAddress, + receiver: receiverAddress, + providerOrSigner: signer, + }); + return { exists: true, flowRate: flowInfo.flowRate }; + } catch (error) { + return { exists: false, flowRate: '0' }; + } +}; diff --git a/src/helpers/superfluid.ts b/src/helpers/superfluid.ts index 0c1dc054f7..83b80f23f1 100644 --- a/src/helpers/superfluid.ts +++ b/src/helpers/superfluid.ts @@ -14,7 +14,10 @@ export const findTokenByAddress = (address?: Address) => { export const findAnchorContractAddress = ( anchorContracts?: IAnchorContractData[], + chainId?: number, ) => { - if (!anchorContracts) return undefined; - return anchorContracts.find(contract => contract.isActive)?.address; + if (!anchorContracts || !chainId) return undefined; + return anchorContracts.find( + contract => contract.isActive && contract.networkId == chainId, + )?.address; }; diff --git a/src/types/config.ts b/src/types/config.ts index 8277337d7b..7f641d3f5f 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -173,7 +173,10 @@ export interface OptimismNetworkConfig extends NetworkConfig { } export interface BaseNetworkConfig extends NetworkConfig { + SUPER_FLUID_TOKENS: Array; anchorRegistryAddress: Address; + GIVETH_ANCHOR_CONTRACT_ADDRESS: Address; + superFluidSubgraph: string; } interface MicroservicesConfig {