diff --git a/components/dashboard/TopupAccountModal.tsx b/components/dashboard/TopupAccountModal.tsx index 94a5cf92..0d98a417 100644 --- a/components/dashboard/TopupAccountModal.tsx +++ b/components/dashboard/TopupAccountModal.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useState } from "react"; import { AnimatePresence, motion } from "framer-motion"; import { useAppDispatch, useAppSelector } from "@/store/store"; -import { chargeBridge, fetchBridgeSupportedTokens, selectOperatorSlice, setIsTopupAccountModalOpen, withRefreshToken } from "@/store/operatorSlice"; +import { chargeBridge, fetchBridgeSupportedTokens, selectOperatorSlice, setIsTopupAccountModalOpen, withRefreshToken, wrapToken } from "@/store/operatorSlice"; import copy from "@/assets/copy-black.svg"; import qr from "@/assets/qr.svg"; import leftArrow from "@/assets/left-arrow.svg"; @@ -33,6 +33,10 @@ type TopupFormValues = { amount: string; } +type WrapTokenFormValues = { + amount: string; +} + type CopyAddressProps = { setQrCode: (qrCode: Address) => void; address: Address; @@ -49,6 +53,10 @@ type BridgeTimeProps = { setBridgeEndTime: (bridgeEndTime: BridgeEndTime) => void; } +type FuseNetworkProps = { + setQrCode: (qrCode: Address) => void; +} + type OtherNetworkProps = { setQrCode: (qrCode: Address) => void; } @@ -63,15 +71,15 @@ const chains: Record = { function filterTokens(data: ChargeBridgeSupportedTokens, tokenSymbol = "FUSE") { const filteredData: ChargeBridgeSupportedTokens = {}; - + Object.keys(data).forEach(chainId => { - const filteredTokens = data[chainId].filter(token => token.symbol === tokenSymbol); - - if (filteredTokens.length > 0) { - filteredData[chainId] = filteredTokens; - } + const filteredTokens = data[chainId].filter(token => token.symbol === tokenSymbol); + + if (filteredTokens.length > 0) { + filteredData[chainId] = filteredTokens; + } }); - + return filteredData; } @@ -245,6 +253,62 @@ const BridgeTime = ({ bridgeEndTime, setBridgeEndTime }: BridgeTimeProps) => { ) } +const FuseNetwork = ({ setQrCode }: FuseNetworkProps) => { + const dispatch = useAppDispatch(); + const operatorSlice = useAppSelector(selectOperatorSlice); + + const formik = useFormik({ + initialValues: { + amount: "", + }, + validationSchema: Yup.object({ + amount: Yup.string().required('Required'), + }), + onSubmit: values => dispatch(wrapToken(values)), + }); + + return ( +
+ +
+
+ OR +
+
+
+ + +
+
+ ) +} + const OtherNetwork = ({ setQrCode }: OtherNetworkProps) => { const dispatch = useAppDispatch(); const operatorSlice = useAppSelector(selectOperatorSlice); @@ -401,10 +465,8 @@ const TopupAccountModal = (): JSX.Element => {

{selectedTab === Tab.FUSE && ( - )} {selectedTab === Tab.OTHER && ( diff --git a/lib/abi/WrappedToken.ts b/lib/abi/WrappedToken.ts new file mode 100644 index 00000000..2101f4d2 --- /dev/null +++ b/lib/abi/WrappedToken.ts @@ -0,0 +1,311 @@ +import { narrow } from 'abitype' + +export const WrappedTokenABI = narrow([ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "src", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "guy", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "wad", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "dst", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "wad", + "type": "uint256" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "src", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "dst", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "wad", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "src", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "wad", + "type": "uint256" + } + ], + "name": "Withdrawal", + "type": "event" + }, + { + "payable": true, + "stateMutability": "payable", + "type": "fallback" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "guy", + "type": "address" + }, + { + "internalType": "uint256", + "name": "wad", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "deposit", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "dst", + "type": "address" + }, + { + "internalType": "uint256", + "name": "wad", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "src", + "type": "address" + }, + { + "internalType": "address", + "name": "dst", + "type": "address" + }, + { + "internalType": "uint256", + "name": "wad", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "wad", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } +]) diff --git a/lib/erc20.ts b/lib/erc20.ts index 8f3a0dd2..06cfd2b2 100644 --- a/lib/erc20.ts +++ b/lib/erc20.ts @@ -1,9 +1,10 @@ import { ERC20ABI } from "@/lib/abi/ERC20"; -import { Address, createPublicClient, http, parseUnits } from "viem"; +import { Address, createPublicClient, http, parseEther, parseUnits } from "viem"; import { getWalletClient, waitForTransactionReceipt } from "wagmi/actions"; import { hex } from "./helpers"; import { config } from "./wagmi"; import { fuse } from "viem/chains"; +import { WrappedTokenABI } from "@/lib/abi/WrappedToken"; const publicClient = (rpcUrl: string) => { return createPublicClient({ @@ -83,3 +84,28 @@ export const getERC20Decimals = async ( }); return decimals; }; + +export const depositToken = async ( + contractAddress: Address, + amount: string, + chainId: number = fuse.id +) => { + const walletClient = await getWalletClient(config, { chainId }); + if (!walletClient) { + return; + } + const accounts = await walletClient.getAddresses(); + const account = accounts[0]; + const tx = await walletClient.writeContract({ + account, + address: contractAddress, + abi: WrappedTokenABI, + functionName: "deposit", + args: [], + value: parseEther(amount), + }); + await waitForTransactionReceipt(config, { + hash: tx, + }); + return tx; +} diff --git a/store/operatorSlice/index.ts b/store/operatorSlice/index.ts index 8a53e25b..acc53bc2 100644 --- a/store/operatorSlice/index.ts +++ b/store/operatorSlice/index.ts @@ -12,7 +12,7 @@ import { CONFIG, NEXT_PUBLIC_FUSE_API_BASE_URL, NEXT_PUBLIC_PAYMASTER_FUNDER_ADD import { PaymasterAbi } from "@/lib/abi/Paymaster"; import { getSponsorIdBalance } from "@/lib/contractInteract"; import * as amplitude from "@amplitude/analytics-browser"; -import { getERC20Balance } from "@/lib/erc20"; +import { depositToken, getERC20Balance } from "@/lib/erc20"; import { ERC20ABI } from "@/lib/abi/ERC20"; import { Account, parseEther, parseUnits } from "viem"; @@ -106,6 +106,7 @@ export interface OperatorStateType { bridgeSupportedTokensStatus: Status; chargeBridgeStatus: Status; chargeBridge: ChargeBridgeResponse; + wrapTokenStatus: Status; } const INIT_STATE: OperatorStateType = { @@ -153,6 +154,7 @@ const INIT_STATE: OperatorStateType = { bridgeSupportedTokensStatus: Status.IDLE, chargeBridgeStatus: Status.IDLE, chargeBridge: initChargeBridge, + wrapTokenStatus: Status.IDLE, }; export const checkOperator = createAsyncThunk( @@ -877,6 +879,24 @@ export const chargeBridge = createAsyncThunk( } ); +export const wrapToken = createAsyncThunk( + "OPERATOR/WRAP_TOKEN", + async ({ + amount + }: { + amount: string, + }) => { + try { + const subscriptionInfo = subscriptionInformation() + const tx = await depositToken(subscriptionInfo.tokenAddress, amount) + return tx; + } catch (error) { + console.log(error); + throw error; + } + } +); + const operatorSlice = createSlice({ name: "OPERATOR_STATE", initialState: INIT_STATE, @@ -1157,6 +1177,15 @@ const operatorSlice = createSlice({ .addCase(chargeBridge.rejected, (state) => { state.chargeBridgeStatus = Status.ERROR; }) + .addCase(wrapToken.pending, (state) => { + state.wrapTokenStatus = Status.PENDING; + }) + .addCase(wrapToken.fulfilled, (state) => { + state.wrapTokenStatus = Status.SUCCESS; + }) + .addCase(wrapToken.rejected, (state) => { + state.wrapTokenStatus = Status.ERROR; + }) }, });