diff --git a/package.json b/package.json index 9b5bb02..ccfd48c 100644 --- a/package.json +++ b/package.json @@ -3,18 +3,15 @@ "version": "1.0.0", "private": true, "dependencies": { - "@biconomy/account": "3.1.1", - "@biconomy/bundler": "3.1.1", - "@biconomy/common": "3.1.1", - "@biconomy/core-types": "3.1.1", - "@biconomy/modules": "3.1.1", - "@biconomy/paymaster": "3.1.1", + "@alchemy/aa-core": "1.2.2", + "@biconomy/account": "4.0.0", "@emotion/react": "^11.10.6", "@emotion/styled": "^11.10.6", "@mui/icons-material": "^5.11.11", "@mui/material": "^5.11.12", "@mui/styles": "^5.11.12", - "@rainbow-me/rainbowkit": "^1.2.0", + "@rainbow-me/rainbowkit": "^2.0.0", + "@tanstack/react-query": "^5.22.2", "@types/node": "^16.7.13", "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", @@ -25,9 +22,9 @@ "react-dom": "^18.2.0", "react-scripts": "5.0.1", "react-toastify": "^9.0.8", - "typescript": "^4.4.2", - "viem": "^1.18.3", - "wagmi": "^1.4.5", + "typescript": "5.2.0", + "viem": "^2.7.3", + "wagmi": "^2.5.5", "web-vitals": "^2.1.0" }, "scripts": { @@ -71,4 +68,4 @@ "tls": "^0.0.1", "url": "^0.11.0" } -} \ No newline at end of file +} diff --git a/src/components/AA/BatchLiquidity.tsx b/src/components/AA/BatchLiquidity.tsx index adaa739..0bc0768 100644 --- a/src/components/AA/BatchLiquidity.tsx +++ b/src/components/AA/BatchLiquidity.tsx @@ -1,63 +1,54 @@ import React, { useState } from "react"; -import { ethers } from "ethers"; import { makeStyles } from "@mui/styles"; -import { PaymasterMode } from "@biconomy/paymaster"; +import { Hex, encodeFunctionData, parseEther, parseUnits } from "viem"; import Button from "../Button"; -import { useEthersSigner } from "../../contexts/ethers"; import { useSmartAccountContext } from "../../contexts/SmartAccountContext"; import { configInfo as config, showSuccessMessage, showErrorMessage, } from "../../utils"; - -const iFace = new ethers.utils.Interface(config.usdc.abi); +import { PaymasterMode } from "@biconomy/account"; const BatchLiquidity: React.FC = () => { const classes = useStyles(); - const signer = useEthersSigner(); const { smartAccount, scwAddress } = useSmartAccountContext(); const [loading, setLoading] = useState(false); const makeTx = async () => { - if (!scwAddress || !smartAccount || !signer) return; + if (!scwAddress || !smartAccount) return; try { setLoading(true); - const txs = []; - const approveCallData = iFace.encodeFunctionData("approve", [ - config.hyphenLP.address, - ethers.BigNumber.from("1000000"), - ]); + const approveCallData = encodeFunctionData({ + abi: config.usdc.abi, + functionName: "approve", + args: [config.hyphenLP.address, parseEther("1")], + }); const tx1 = { - to: config.usdc.address, + to: config.usdc.address as Hex, + value: BigInt(0), data: approveCallData, }; - txs.push(tx1); - const hyphenContract = new ethers.Contract( - config.hyphenLP.address, - config.hyphenLP.abi, - signer - ); - const addLiquidityData = hyphenContract.interface.encodeFunctionData("addTokenLiquidity", [config.usdc.address, - ethers.BigNumber.from("1000000")]) + const addLiquidityData = encodeFunctionData({ + abi: config.hyphenLP.abi, + functionName: "addTokenLiquidity", + args: [config.usdc.address, parseUnits("0.001", 6)], + }); const tx2 = { - to: config.hyphenLP.address, + to: config.hyphenLP.address as Hex, + value: BigInt(0), data: addLiquidityData, }; - txs.push(tx2); - let userOp = await smartAccount.buildUserOp(txs, { - paymasterServiceData: { - mode: PaymasterMode.SPONSORED, - }, - }); - const userOpResponse = await smartAccount.sendUserOp(userOp); - console.log("userOpHash", userOpResponse); - const { transactionHash } = await userOpResponse.waitForTxHash(); + let { waitForTxHash } = await smartAccount.sendTransaction([tx1, tx2], { paymasterServiceData: { mode: PaymasterMode.SPONSORED } }); + const { transactionHash } = await waitForTxHash(); console.log("txHash", transactionHash); - showSuccessMessage(`Added batch liquidity ${transactionHash}`, transactionHash); + showSuccessMessage( + `Added batch liquidity ${transactionHash}`, + transactionHash + ); setLoading(false); } catch (err: any) { console.error(err); diff --git a/src/components/AA/BatchMintNft.tsx b/src/components/AA/BatchMintNft.tsx deleted file mode 100644 index 81e45af..0000000 --- a/src/components/AA/BatchMintNft.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import React, { useCallback, useEffect, useState } from "react"; -import { ethers } from "ethers"; -import { makeStyles } from "@mui/styles"; -import { PaymasterMode } from "@biconomy/paymaster"; - -import Button from "../Button"; -import { useEthersSigner } from "../../contexts/ethers"; -import { useSmartAccountContext } from "../../contexts/SmartAccountContext"; -import { - configInfo as config, - showSuccessMessage, - showErrorMessage, -} from "../../utils"; - -const BatchMintNft: React.FC = () => { - const classes = useStyles(); - const signer = useEthersSigner(); - const { smartAccount, scwAddress } = useSmartAccountContext(); - const [nftCount, setNftCount] = useState(null); - const [loading, setLoading] = useState(false); - - const getNftCount = useCallback(async () => { - if (!scwAddress || !signer) return; - const nftContract = new ethers.Contract( - config.nft.address, - config.nft.abi, - signer - ); - const count = await nftContract.balanceOf(scwAddress); - console.log("count", Number(count)); - setNftCount(Number(count)); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { - getNftCount(); - }, [getNftCount, signer]); - - const mintNft = async () => { - if (!scwAddress || !smartAccount || !signer) return; - try { - setLoading(true); - const nftContract = new ethers.Contract( - config.nft.address, - config.nft.abi, - signer - ); - console.log("smartAccount.address ", scwAddress); - const safeMintTx = await nftContract.populateTransaction.safeMint( - scwAddress - ); - console.log(safeMintTx.data); - const tx1 = { - to: config.nft.address, - data: safeMintTx.data, - }; - - let userOp = await smartAccount.buildUserOp([tx1, tx1], { - paymasterServiceData: { - mode: PaymasterMode.SPONSORED, - }, - }); - - const userOpResponse = await smartAccount.sendUserOp(userOp); - console.log("userOpHash", userOpResponse); - const { transactionHash } = await userOpResponse.waitForTxHash(); - console.log("txHash", transactionHash); - showSuccessMessage(`Minted Nft ${transactionHash}`, transactionHash); - setLoading(false); - await new Promise((resolve) => setTimeout(resolve, 2000)); - getNftCount(); - } catch (err: any) { - console.error(err); - setLoading(false); - showErrorMessage(err.message || "Error in sending the transaction"); - } - }; - - return ( -
-

- Use Cases {"->"} Gasless {"->"} Batch Nft Mint -

- -

Batch Nft Mint

- -

- This magic bundle will batch two signle safeMint into one transaction -

- -

- Nft Contract Address: {config.nft.address}{" "} - - (same of goerli, mumbai, polygon) - -

-

- Nft Balance in SCW:{" "} - {nftCount === null ? ( -

fetching...

- ) : ( - nftCount - )} -

- -

Transaction Batched

- - -
- ); -}; - -const useStyles = makeStyles(() => ({ - main: { - margin: "auto", - padding: "10px 40px", - color: "#EEEEEE", - }, - subTitle: { - color: "#FFB999", - fontSize: 36, - margin: 0, - }, - h3Title: { - margin: 10, - }, -})); - -export default BatchMintNft; diff --git a/src/components/AA/MintErc20.tsx b/src/components/AA/MintErc20.tsx deleted file mode 100644 index 0c4ad36..0000000 --- a/src/components/AA/MintErc20.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import React, { useEffect, useState, useCallback } from "react"; -import { ethers } from "ethers"; -import { makeStyles } from "@mui/styles"; -import { PaymasterMode } from "@biconomy/paymaster"; - -import Button from "../Button"; -import { useEthersSigner } from "../../contexts/ethers"; -import { useSmartAccountContext } from "../../contexts/SmartAccountContext"; -import { - configInfo as config, - showErrorMessage, - showSuccessMessage, -} from "../../utils"; - -const MintErc20: React.FC = () => { - const classes = useStyles(); - const signer = useEthersSigner(); - const { smartAccount, scwAddress } = useSmartAccountContext(); - const [balance, setBalance] = useState(0); - const [loading, setLoading] = useState(false); - - const getBalance = useCallback(async () => { - if (!scwAddress || !signer) return; - const erc20Contract = new ethers.Contract( - config.terc20.address, - config.terc20.abi, - signer - ); - const count = await erc20Contract.balanceOf(scwAddress); - console.log("count", Number(count)); - setBalance(Number(count)); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { - getBalance(); - }, [getBalance, signer]); - - const makeTx = async () => { - if (!scwAddress || !signer || !smartAccount) return; - try { - setLoading(true); - const erc20Contract = new ethers.Contract( - config.terc20.address, - config.terc20.abi, - signer - ); - const amountGwei = ethers.utils.parseEther("100"); - const data = erc20Contract.interface.encodeFunctionData("mint", [ - scwAddress, - amountGwei, - ]); - const tx = { - to: config.terc20.address, - data: data, - }; - let userOp = await smartAccount.buildUserOp([tx], { - paymasterServiceData: { - mode: PaymasterMode.SPONSORED, - }, - }); - - const userOpResponse = await smartAccount.sendUserOp(userOp); - console.log("userOpHash", userOpResponse); - const { transactionHash } = await userOpResponse.waitForTxHash(); - console.log("txHash", transactionHash); - showSuccessMessage(`Minted ERC20 ${transactionHash}`, transactionHash); - setLoading(false); - await new Promise((resolve) => setTimeout(resolve, 2000)); - getBalance(); - } catch (err: any) { - console.error(err); - setLoading(false); - showErrorMessage(err.message || "Error in sending the transaction"); - } - }; - - return ( -
-

- Use Cases {"->"} Gasless {"->"} Mint ERC-20 -

- -

Mint ERC20 Gasless Flow

- -

This is single transaction to mint an test ERC-20 contract.

- -

- Test ERC20 Token: {config.terc20.address}{" "} - - (same of goerli, mumbai, polygon) - -

-

- ERC20 Balance in SCW:{" "} - {balance === null ? ( -

fetching...

- ) : ( - ethers.utils.formatEther(balance.toString()) - )} -

- -
- ); -}; - -const useStyles = makeStyles(() => ({ - main: { - padding: "10px 40px", - display: "flex", - flexDirection: "column", - alignItems: "start", - justifyContent: "center", - }, - subTitle: { - color: "#FFB999", - fontSize: 36, - margin: 0, - }, - h3Title: { - color: "#e6e6e6", - }, - input: { - maxWidth: 350, - width: "100%", - padding: "12px 10px", - margin: "8px 0", - color: "#e6e6e6", - boxSizing: "border-box", - outlineColor: "#181818", - backgroundColor: "#282A3A", - border: "none", - marginBottom: 20, - }, -})); - -export default MintErc20; diff --git a/src/components/AA/MintNft.tsx b/src/components/AA/MintNft.tsx index 36cef5a..8eca44a 100644 --- a/src/components/AA/MintNft.tsx +++ b/src/components/AA/MintNft.tsx @@ -1,69 +1,56 @@ import React, { useCallback, useEffect, useState } from "react"; -import { ethers } from "ethers"; import { makeStyles } from "@mui/styles"; -import { PaymasterMode } from "@biconomy/paymaster"; - +import { usePublicClient } from "wagmi"; +import { Hex, encodeFunctionData, getContract } from "viem"; import Button from "../Button"; -import { useEthersSigner } from "../../contexts/ethers"; import { useSmartAccountContext } from "../../contexts/SmartAccountContext"; import { configInfo as config, showErrorMessage, showSuccessMessage, } from "../../utils"; +import { PaymasterMode } from "@biconomy/account"; const MintNft: React.FC = () => { const classes = useStyles(); - const signer = useEthersSigner(); + const publicClient = usePublicClient(); const { smartAccount, scwAddress } = useSmartAccountContext(); const [nftCount, setNftCount] = useState(null); const [loading, setLoading] = useState(false); const getNftCount = useCallback(async () => { - if (!scwAddress || !signer) return; - const nftContract = new ethers.Contract( - config.nft.address, - config.nft.abi, - signer - ); - const count = await nftContract.balanceOf(scwAddress); - console.log("count", Number(count)); + if (!scwAddress || !publicClient) return; + const nftContract = getContract({ + address: config.nft.address as Hex, + abi: config.nft.abi, + client: publicClient, + }); + const count = await nftContract.read.balanceOf([scwAddress]); + console.log("count", count); setNftCount(Number(count)); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [publicClient, scwAddress]); useEffect(() => { getNftCount(); - }, [getNftCount, signer]); + }, [getNftCount, publicClient]); const mintNft = async () => { - if (!scwAddress || !smartAccount || !signer) return; + if (!scwAddress || !smartAccount || !publicClient) return; try { setLoading(true); - const nftContract = new ethers.Contract( - config.nft.address, - config.nft.abi, - signer - ); - console.log("smartAccount.address ", scwAddress); - const safeMintTx = await nftContract.populateTransaction.safeMint( - scwAddress - ); - console.log(safeMintTx.data); + const mintData = encodeFunctionData({ + abi: config.nft.abi, + functionName: "safeMint", + args: [scwAddress as Hex], + }); const tx1 = { - to: config.nft.address, - data: safeMintTx.data, + to: config.nft.address as Hex, + value: BigInt(0), + data: mintData, }; - let userOp = await smartAccount.buildUserOp([tx1], { - paymasterServiceData: { - mode: PaymasterMode.SPONSORED, - }, - }); - - const userOpResponse = await smartAccount.sendUserOp(userOp); - console.log("userOpHash", userOpResponse); - const { transactionHash } = await userOpResponse.waitForTxHash(); + let { waitForTxHash } = await smartAccount.sendTransaction([tx1], { paymasterServiceData: { mode: PaymasterMode.SPONSORED } }); + const { transactionHash } = await waitForTxHash(); console.log("txHash", transactionHash); showSuccessMessage(`Minted Nft ${transactionHash}`, transactionHash); setLoading(false); diff --git a/src/components/Balance/index.tsx b/src/components/Balance/index.tsx deleted file mode 100644 index 273c845..0000000 --- a/src/components/Balance/index.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import React, { useCallback, useEffect } from "react"; -import { makeStyles } from "@mui/styles"; - -import { useSmartAccountContext } from "../../contexts/SmartAccountContext"; -import { formatBalance } from "../../utils"; - -const Assets: React.FC = () => { - const classes = useStyles(); - const { getSmartAccountBalance, isFetchingBalance, balance } = - useSmartAccountContext(); - console.log("🚀 ~ file: index.tsx:10 ~ balance:", balance); - - const getSmartAccountBalanceFunc = useCallback(async () => { - await getSmartAccountBalance(); - }, [getSmartAccountBalance]); - - useEffect(() => { - getSmartAccountBalanceFunc(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - if (isFetchingBalance || balance.alltokenBalances.length === 0) { - return ( -
- -
- ); - } - - return ( -
-

Smart Account Balance

- {/* */} -
-
-
-

Tokens

-
- {balance.alltokenBalances.map((token, ind) => ( -
-
- { - currentTarget.src = - "https://cdn.icon-icons.com/icons2/3947/PNG/512/cash_currency_money_finance_exchange_coin_bitcoin_icon_251415.png"; - }} - alt="" - /> -

{token.contract_ticker_symbol}

-
-

{formatBalance(token.balance, token.contract_decimals)}

-
- ))} -
-
-
-

My Balance

-
- -

- ${balance.totalBalanceInUsd} -

-
-
-
- ); -}; - -const useStyles = makeStyles(() => ({ - main: { - maxWidth: 1600, - padding: "10px 40px", - width: "100%", - height: "100%", - display: "flex", - flexDirection: "column", - gap: 20, - }, - subTitle: { - color: "#FFB999", - fontSize: 36, - margin: 0, - }, - container: { - display: "flex", - justifyContent: "space-between", - gap: 10, - height: "100%", - width: "100%", - }, - element: { - width: "100%", - maxHeight: 600, - height: 400, - overflowY: "auto", - border: "1px solid #5B3320", - backgroundColor: "#151520", - borderRadius: 12, - }, - balance: { - display: "flex", - justifyContent: "space-between", - fontSize: 25, - padding: "0 10px", - borderBottom: "1px solid #2C3333", - }, - tokenTitle: { - display: "flex", - flexFlow: "reverse", - alignItems: "center", - }, - img: { - width: 35, - height: 35, - border: "1px solid #2C3333", - borderRadius: "50%", - marginRight: 10, - }, - containerLoader: { - display: "flex", - flexDirection: "column", - width: "100%", - height: "100%", - justifyContent: "center", - alignItems: "center", - }, - animateBlink: { - animation: "$blink 4s linear infinite", - }, - "@keyframes blink": { - "0%": { - opacity: "0", - }, - "25%": { - opacity: "100", - }, - "50%": { - opacity: "0", - }, - "75%": { - opacity: "100", - }, - "100%": { - opacity: "0", - }, - }, -})); - -export default Assets; diff --git a/src/components/Faucet/index.tsx b/src/components/Faucet/index.tsx index cc383a8..716a229 100644 --- a/src/components/Faucet/index.tsx +++ b/src/components/Faucet/index.tsx @@ -1,9 +1,8 @@ import React, { useState } from "react"; -import { ethers } from "ethers"; import { makeStyles } from "@mui/styles"; +import { Hex, encodeFunctionData } from "viem"; import Button from "../Button"; -import { useEthersSigner } from "../../contexts/ethers"; import { useSmartAccountContext } from "../../contexts/SmartAccountContext"; import { configInfo as config, @@ -11,40 +10,30 @@ import { showInfoMessage, showSuccessMessage, } from "../../utils"; -import { PaymasterMode } from "@biconomy/paymaster"; const Faucet: React.FC = () => { const classes = useStyles(); - const signer = useEthersSigner(); const { smartAccount, scwAddress } = useSmartAccountContext(); const [address, setAddress] = useState(scwAddress); const makeTx = async () => { - if (!smartAccount || !signer || !scwAddress) { + if (!smartAccount || !scwAddress) { showErrorMessage("Please connect your wallet"); return; } showInfoMessage("Initiating Faucet..."); try { - const faucetContract = new ethers.Contract( - config.faucet.address, - config.faucet.abi, - signer - ); - const faucetTxData = await faucetContract.populateTransaction.drip( - address - ); + const faucetTxData = encodeFunctionData({ + abi: config.faucet.abi, + functionName: "drip", + args: [address as Hex], + }); const tx1 = { - to: config.faucet.address, - data: faucetTxData.data, + to: config.faucet.address as Hex, + value: BigInt(0), + data: faucetTxData, }; - let userOp = await smartAccount.buildUserOp([tx1], { - paymasterServiceData: { - mode: PaymasterMode.SPONSORED, - }, - }); - - const userOpResponse = await smartAccount.sendUserOp(userOp); + let userOpResponse = await smartAccount.sendTransaction(tx1); console.log("userOpHash", userOpResponse); const { transactionHash } = await userOpResponse.waitForTxHash(); console.log("txHash", transactionHash); diff --git a/src/components/Forward/BatchLiquidity.tsx b/src/components/Forward/BatchLiquidity.tsx index 0193170..34a61db 100644 --- a/src/components/Forward/BatchLiquidity.tsx +++ b/src/components/Forward/BatchLiquidity.tsx @@ -1,26 +1,22 @@ import React, { useEffect, useState } from "react"; -import { ethers } from "ethers"; import { makeStyles } from "@mui/styles"; import { CircularProgress } from "@mui/material"; import { - IHybridPaymaster, PaymasterFeeQuote, PaymasterMode, - SponsorUserOperationDto, -} from "@biconomy/paymaster"; +} from "@biconomy/account"; import Button from "../Button"; -import { useEthersSigner } from "../../contexts/ethers"; import { useSmartAccountContext } from "../../contexts/SmartAccountContext"; import { configInfo as config, showSuccessMessage, showErrorMessage, } from "../../utils"; +import { Hex, encodeFunctionData, parseEther, parseUnits } from "viem"; const BatchLiquidity: React.FC = () => { const classes = useStyles(); - const signer = useEthersSigner(); const { smartAccount, scwAddress } = useSmartAccountContext(); const [isLoading, setIsLoading] = useState(true); const [isLoadingFee, setIsLoadingFee] = useState(false); @@ -28,7 +24,7 @@ const BatchLiquidity: React.FC = () => { const [spender, setSpender] = useState(""); const [feeQuotesArr, setFeeQuotesArr] = useState([]); const [selectedQuote, setSelectedQuote] = useState(); - const [estimatedUserOp, setEstimatedUserOp] = useState({}); + const [tx, setTx] = useState([]); // pre calculate the fee useEffect(() => { @@ -36,55 +32,30 @@ const BatchLiquidity: React.FC = () => { setIsLoading(true); setIsLoadingFee(true); setFeeQuotesArr([]); - if (!smartAccount || !scwAddress || !signer) return; - const txs = []; - const usdcContract = new ethers.Contract( - config.usdc.address, - config.usdc.abi, - signer - ); - const hyphenContract = new ethers.Contract( - config.hyphenLP.address, - config.hyphenLP.abi, - signer - ); - const approveUSDCTx = await usdcContract.populateTransaction.approve( - config.hyphenLP.address, - ethers.BigNumber.from("1000000") - ); + if (!smartAccount || !scwAddress) return; + const approveCallData = encodeFunctionData({ + abi: config.usdc.abi, + functionName: "approve", + args: [config.hyphenLP.address, parseEther("1")], + }); const tx1 = { - to: config.usdc.address, - data: approveUSDCTx.data, + to: config.usdc.address as Hex, + data: approveCallData, }; - txs.push(tx1); - const addLiquidityData = hyphenContract.interface.encodeFunctionData("addTokenLiquidity", [config.usdc.address, - ethers.BigNumber.from("1000000")]) // 1 USDC (mumbai USDC has 6 decimals) + const addLiquidityData = encodeFunctionData({ + abi: config.hyphenLP.abi, + functionName: "addTokenLiquidity", + args: [config.usdc.address, parseUnits("0.001", 6)], + }); const tx2 = { - to: config.hyphenLP.address, + to: config.hyphenLP.address as Hex, data: addLiquidityData, }; - txs.push(tx2); - console.log("Tx array created", txs); - let partialUserOp = await smartAccount.buildUserOp(txs, { - paymasterServiceData: { - mode: PaymasterMode.ERC20, - }, - }); - setEstimatedUserOp(partialUserOp); - - const biconomyPaymaster = - smartAccount.paymaster as IHybridPaymaster; - const feeQuotesResponse = - await biconomyPaymaster.getPaymasterFeeQuotesOrData(partialUserOp, { - // here we are explicitly telling by mode ERC20 that we want to pay in ERC20 tokens and expect fee quotes - mode: PaymasterMode.ERC20, - // one can pass tokenList empty array. and it would return fee quotes for all tokens supported by the Biconomy paymaster - tokenList: [config.usdc.address, config.usdt.address], - // preferredToken is optional. If you want to pay in a specific token, you can pass its address here and get fee quotes for that token only - // preferredToken: config.preferredToken, - }); + console.log("Tx array created", [tx1, tx2]); + setTx([tx1, tx2]); + const feeQuotesResponse = await smartAccount.getTokenFees([tx1, tx2], {paymasterServiceData: {mode: PaymasterMode.ERC20}}); setSpender(feeQuotesResponse.tokenPaymasterAddress || ""); const feeQuotes = feeQuotesResponse.feeQuotes as PaymasterFeeQuote[]; setFeeQuotesArr(feeQuotes); @@ -94,10 +65,10 @@ const BatchLiquidity: React.FC = () => { }; fetchFeeOption(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [signer]); + }, [scwAddress]); const makeTx = async () => { - if (!smartAccount || !scwAddress || !signer) return; + if (!smartAccount || !scwAddress) return; if (!selectedQuote) { showErrorMessage("Please select a fee quote"); return; @@ -106,47 +77,18 @@ const BatchLiquidity: React.FC = () => { setIsLoading(true); console.log("selected quote", selectedQuote); // const finalUserOp = { ...estimatedUserOp } as any; - const finalUserOp = await smartAccount.buildTokenPaymasterUserOp( - estimatedUserOp, + const userOpResponse = await smartAccount.sendTransaction( + tx, { - feeQuote: selectedQuote, - spender: spender, - maxApproval: false, + paymasterServiceData: { + feeQuote: selectedQuote, + spender: spender as Hex, + mode: PaymasterMode.ERC20, + maxApproval: false, + } } ); - const biconomyPaymaster = - smartAccount.paymaster as IHybridPaymaster; - const paymasterAndDataWithLimits = - await biconomyPaymaster.getPaymasterAndData(finalUserOp, { - mode: PaymasterMode.ERC20, // - mandatory // now we know chosen fee token and requesting paymaster and data for it - feeTokenAddress: selectedQuote?.tokenAddress, - // - optional by default false - // This flag tells the paymaster service to calculate gas limits for the userOp - // since at this point callData is updated callGasLimit may change and based on paymaster to be used verification gas limit may change - calculateGasLimits: true, - }); - - // below code is only needed if you sent the glaf calculateGasLimits = true - if ( - paymasterAndDataWithLimits?.callGasLimit && - paymasterAndDataWithLimits?.verificationGasLimit && - paymasterAndDataWithLimits?.preVerificationGas - ) { - // Returned gas limits must be replaced in your op as you update paymasterAndData. - // Because these are the limits paymaster service signed on to generate paymasterAndData - // If you receive AA34 error check here.. - - finalUserOp.callGasLimit = paymasterAndDataWithLimits.callGasLimit; - finalUserOp.verificationGasLimit = - paymasterAndDataWithLimits.verificationGasLimit; - finalUserOp.preVerificationGas = - paymasterAndDataWithLimits.preVerificationGas; - } - // update finalUserOp with paymasterAndData and send it to smart account - finalUserOp.paymasterAndData = - paymasterAndDataWithLimits.paymasterAndData; - const userOpResponse = await smartAccount.sendUserOp(finalUserOp); console.log("userOpHash", userOpResponse); const { transactionHash } = await userOpResponse.waitForTxHash(); console.log("txHash", transactionHash); diff --git a/src/components/Forward/MintNft.tsx b/src/components/Forward/MintNft.tsx index 8905b37..e919d94 100644 --- a/src/components/Forward/MintNft.tsx +++ b/src/components/Forward/MintNft.tsx @@ -1,27 +1,25 @@ import React, { useEffect, useState } from "react"; -import { ethers } from "ethers"; import { makeStyles } from "@mui/styles"; import CircularProgress from "@mui/material/CircularProgress"; import { - IHybridPaymaster, PaymasterFeeQuote, PaymasterMode, - SponsorUserOperationDto, -} from "@biconomy/paymaster"; +} from "@biconomy/account"; import Button from "../Button"; -import { useEthersSigner } from "../../contexts/ethers"; import { useSmartAccountContext } from "../../contexts/SmartAccountContext"; import { configInfo as config, showErrorMessage, showSuccessMessage, } from "../../utils"; +import { Hex, encodeFunctionData, getContract } from "viem"; +import { usePublicClient } from "wagmi"; const MintNftForward: React.FC = () => { const classes = useStyles(); - const signer = useEthersSigner(); - const { scwAddress, smartAccount } = useSmartAccountContext(); + const publicClient = usePublicClient(); + const { smartAccount, scwAddress } = useSmartAccountContext(); const [nftCount, setNftCount] = useState(null); const [isLoading, setIsLoading] = useState(false); const [isLoadingFee, setIsLoadingFee] = useState(false); @@ -29,59 +27,42 @@ const MintNftForward: React.FC = () => { const [spender, setSpender] = useState(""); const [feeQuotesArr, setFeeQuotesArr] = useState([]); const [selectedQuote, setSelectedQuote] = useState(); - const [estimatedUserOp, setEstimatedUserOp] = useState({}); + const [tx, setTx] = useState(); useEffect(() => { const getNftCount = async () => { - if (!scwAddress || !signer) return; - const nftContract = new ethers.Contract( - config.nft.address, - config.nft.abi, - signer - ); - const count = await nftContract.balanceOf(scwAddress); + if (!scwAddress || !publicClient) return; + const nftContract = getContract({ + address: config.nft.address as Hex, + abi: config.nft.abi, + client: publicClient, + }); + const count = await nftContract.read.balanceOf([scwAddress as Hex]); console.log("count", Number(count)); setNftCount(Number(count)); }; getNftCount(); getFee(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [scwAddress, signer]); + }, [scwAddress, publicClient]); const getFee = async () => { - if (!smartAccount || !scwAddress || !signer) return; + if (!smartAccount || !scwAddress || !publicClient) return; setIsLoadingFee(true); - const nftContract = new ethers.Contract( - config.nft.address, - config.nft.abi, - signer - ); - console.log("smartAccount.address ", scwAddress); - const safeMintTx = await nftContract.populateTransaction.safeMint( - scwAddress - ); - console.log(safeMintTx.data); + const mintData = encodeFunctionData({ + abi: config.nft.abi, + functionName: "safeMint", + args: [scwAddress as Hex], + }); const tx1 = { to: config.nft.address, - data: safeMintTx.data, + value: 0, + data: mintData, }; - let partialUserOp = await smartAccount.buildUserOp([tx1], { - paymasterServiceData: { - mode: PaymasterMode.ERC20, - }, + setTx(tx1 as any); + const feeQuotesResponse = await smartAccount.getTokenFees([tx1], { + paymasterServiceData: { mode: PaymasterMode.ERC20 }, }); - setEstimatedUserOp(partialUserOp); - const biconomyPaymaster = - smartAccount.paymaster as IHybridPaymaster; - const feeQuotesResponse = - await biconomyPaymaster.getPaymasterFeeQuotesOrData(partialUserOp, { - // here we are explicitly telling by mode ERC20 that we want to pay in ERC20 tokens and expect fee quotes - mode: PaymasterMode.ERC20, - // one can pass tokenList empty array. and it would return fee quotes for all tokens supported by the Biconomy paymaster - tokenList: [config.usdc.address, config.usdt.address], - // preferredToken is optional. If you want to pay in a specific token, you can pass its address here and get fee quotes for that token only - // preferredToken: config.preferredToken, - }); setSpender(feeQuotesResponse.tokenPaymasterAddress || ""); const feeQuotes = feeQuotesResponse.feeQuotes as PaymasterFeeQuote[]; setFeeQuotesArr(feeQuotes); @@ -90,7 +71,7 @@ const MintNftForward: React.FC = () => { }; const makeTx = async () => { - if (!smartAccount || !scwAddress || !signer) return; + if (!smartAccount || !scwAddress || !publicClient) return; if (!selectedQuote) { showErrorMessage("Please select a fee quote"); return; @@ -99,48 +80,18 @@ const MintNftForward: React.FC = () => { setIsLoading(true); console.log("selected quote", selectedQuote); // const finalUserOp = { ...estimatedUserOp } as any; - const finalUserOp = await smartAccount.buildTokenPaymasterUserOp( - estimatedUserOp, + const userOpResponse = await smartAccount.sendTransaction( + tx!, { - feeQuote: selectedQuote, - spender: spender, - maxApproval: false, + paymasterServiceData: { + feeQuote: selectedQuote, + mode: PaymasterMode.ERC20, + spender: spender as Hex, + maxApproval: false, + } } ); - const biconomyPaymaster = - smartAccount.paymaster as IHybridPaymaster; - const paymasterAndDataWithLimits = - await biconomyPaymaster.getPaymasterAndData(finalUserOp, { - mode: PaymasterMode.ERC20, // - mandatory // now we know chosen fee token and requesting paymaster and data for it - feeTokenAddress: selectedQuote?.tokenAddress, - // - optional by default false - // This flag tells the paymaster service to calculate gas limits for the userOp - // since at this point callData is updated callGasLimit may change and based on paymaster to be used verification gas limit may change - calculateGasLimits: true, - }); - console.log("paymasterAndDataWithLimits", paymasterAndDataWithLimits); - // below code is only needed if you sent the glaf calculateGasLimits = true - if ( - paymasterAndDataWithLimits?.callGasLimit && - paymasterAndDataWithLimits?.verificationGasLimit && - paymasterAndDataWithLimits?.preVerificationGas - ) { - // Returned gas limits must be replaced in your op as you update paymasterAndData. - // Because these are the limits paymaster service signed on to generate paymasterAndData - // If you receive AA34 error check here.. - - finalUserOp.callGasLimit = paymasterAndDataWithLimits.callGasLimit; - finalUserOp.verificationGasLimit = - paymasterAndDataWithLimits.verificationGasLimit; - finalUserOp.preVerificationGas = - paymasterAndDataWithLimits.preVerificationGas; - } - // update finalUserOp with paymasterAndData and send it to smart account - finalUserOp.paymasterAndData = - paymasterAndDataWithLimits.paymasterAndData; - console.log("finalUserOp", finalUserOp); - const userOpResponse = await smartAccount.sendUserOp(finalUserOp); console.log("userOpHash", userOpResponse); const { transactionHash } = await userOpResponse.waitForTxHash(); console.log("txHash", transactionHash); diff --git a/src/components/Modules/ABI_SVM.tsx b/src/components/Modules/ABI_SVM.tsx new file mode 100644 index 0000000..acd2a89 --- /dev/null +++ b/src/components/Modules/ABI_SVM.tsx @@ -0,0 +1,64 @@ + +import { useState } from 'react'; +import { Contract, ethers } from 'ethers' +import abis from "../../utils/configs/contractsInfo.json"; +import { useSmartAccountContext } from '../../contexts/SmartAccountContext'; +import { useAccount } from 'wagmi'; +import CreateABISVM from './CreateABISVM'; +import { ABI_SVM } from '../../utils/constants'; + + +export default function ABISVM() { + const { address } = useAccount(); + const { smartAccount, scwAddress } = useSmartAccountContext(); + const [loading, setLoading] = useState(false); + const [provider, setProvider] = useState(null) + + const [mockStake, setMockStake] = useState(); + + const connect = async () => { + // @ts-ignore + const { ethereum } = window; + try { + setLoading(true) + const provider = new ethers.providers.Web3Provider(ethereum) + await provider.send("eth_requestAccounts", []); + setProvider(provider) + + setMockStake(mockStake); + setLoading(false) + } catch (error) { + console.error(error); + } + }; + + console.log(smartAccount); + console.log(provider); + + return ( + <> +
+

ABI SVM Demo

+ {!loading && !address && } + {loading &&

Loading Smart Account...

} + {scwAddress &&

Smart Account: {scwAddress}

} + + { + smartAccount && ( + + ) + } +
+ + ) +} \ No newline at end of file diff --git a/src/components/Modules/CreateABISVM.tsx b/src/components/Modules/CreateABISVM.tsx new file mode 100644 index 0000000..34a5a6e --- /dev/null +++ b/src/components/Modules/CreateABISVM.tsx @@ -0,0 +1,197 @@ +import React, { useEffect, useState } from "react"; +import { ethers } from "ethers"; +import { BiconomySmartAccountV2, createSessionKeyManagerModule } from "@biconomy/account" +import { toast, ToastContainer } from 'react-toastify'; +import 'react-toastify/dist/ReactToastify.css'; +import {getABISVMSessionKeyData} from "../../utils/index"; +import { hexDataSlice, id, parseEther } from "ethers/lib/utils"; +import { Hex } from "viem"; +import UseABISVM from "./UseABISVM"; +import Button from "../Button"; +import { useAccount } from "wagmi"; +import { useSmartAccountContext } from "../../contexts/SmartAccountContext"; +import { ABI_SVM, managerModuleAddr } from "../../utils/constants"; + +interface props { + smartAccount: BiconomySmartAccountV2; + address: string; + provider: ethers.providers.Provider; + nftContract: ethers.Contract; + abiSVMAddress: string; +} + +const CreateABISVM: React.FC = () => { + + const [isSessionKeyModuleEnabled, setIsSessionKeyModuleEnabled] = useState (false); + const [isSessionActive, setIsSessionActive] = useState (false); + const [sessionIDs, setSessionIDs] = useState([]); + + const { address } = useAccount(); + const { smartAccount, scwAddress } = useSmartAccountContext(); + + useEffect(() => { + let checkSessionModuleEnabled = async () => { + if(!address || !smartAccount) { + setIsSessionKeyModuleEnabled(false); + return + } + try { + const isEnabled = await smartAccount.isModuleEnabled(managerModuleAddr) + console.log("isSessionKeyModuleEnabled", isEnabled); + setIsSessionKeyModuleEnabled(isEnabled); + return; + } catch(err: any) { + console.error(err) + setIsSessionKeyModuleEnabled(false); + return; + } + } + checkSessionModuleEnabled() + },[isSessionKeyModuleEnabled, address, smartAccount]) + + const createSession = async (enableSessionKeyModule: boolean) => { + const toastMessage = 'Creating Sessions for ' + address; + toast.info(toastMessage, { + position: "top-right", + autoClose: 15000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "dark", + }); + if (!address || !smartAccount) { + alert("Please connect wallet first") + } + try { + // -----> setMerkle tree tx flow + // create dapp side session key + const sessionSigner = ethers.Wallet.createRandom(); + const sessionKeyEOA = await sessionSigner.getAddress(); + console.log("sessionKeyEOA", sessionKeyEOA); + // BREWARE JUST FOR DEMO: update local storage with session key + window.localStorage.setItem("sessionPKey", sessionSigner.privateKey); + + // generate sessionModule + const sessionModule = await createSessionKeyManagerModule({ + moduleAddress: managerModuleAddr, + smartAccountAddress: address as Hex, + }); + + /** + * Create Session Key Datas + */ + + const functionSelector = hexDataSlice(id("safeMint(address)"), 0, 4); + + const sessionKeyData = await getABISVMSessionKeyData(sessionKeyEOA, { + destContract: "0xdd526eba63ef200ed95f0f0fb8993fe3e20a23d0", + functionSelector: functionSelector, + valueLimit: parseEther("0"), + rules: [ + { + offset: 0, // offset 0 means we are checking first parameter of safeMint (recipient address) + condition: 0, // 0 = Condition.EQUAL + referenceValue: ethers.utils.hexZeroPad("0xd3C85Fdd3695Aee3f0A12B3376aCD8DC54020549", 32) // recipient address + }, + ], + }); + + /** + * Create Data for the Session Enabling Transaction + * We pass an array of session data objects to the createSessionData method + */ + const sessionTxData = await sessionModule.createSessionData([ + { + validUntil: 0, + validAfter: 0, + sessionValidationModule: ABI_SVM, + sessionPublicKey: sessionKeyEOA as Hex, + sessionKeyData: sessionKeyData as Hex, + } + ]); + //console.log("sessionTxData", sessionTxData); + setSessionIDs([...sessionTxData.sessionIDInfo]); + + // tx to set session key + const setSessionTrx = { + to: managerModuleAddr, // session manager module address + data: sessionTxData.data, + }; + + const transactionArray = []; + + if (enableSessionKeyModule) { + // -----> enableModule session manager module + const enableModuleTrx = await smartAccount!.getEnableModuleData( + managerModuleAddr + ); + transactionArray.push(enableModuleTrx); + } + + transactionArray.push(setSessionTrx) + + let userOpResponse = await smartAccount!.sendTransaction(transactionArray); + + const transactionDetails = await userOpResponse.wait(); + console.log("txHash", transactionDetails.receipt.transactionHash); + console.log("Sessions Enabled"); + setIsSessionActive(true) + toast.success(`Success! Sessions created succesfully`, { + position: "top-right", + autoClose: 6000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "dark", + }); + } catch(err: any) { + console.error(err) + } + } + + return ( +
+ + {isSessionKeyModuleEnabled&&!isSessionActive ? ( +
+ ) + + } + + export default CreateABISVM; \ No newline at end of file diff --git a/src/components/Modules/CreateSession.tsx b/src/components/Modules/CreateSession.tsx index 3fbc349..f3a70e9 100644 --- a/src/components/Modules/CreateSession.tsx +++ b/src/components/Modules/CreateSession.tsx @@ -1,22 +1,24 @@ import React, { useEffect, useState } from "react"; -import { ethers } from "ethers"; import { makeStyles } from "@mui/styles"; -import { SessionKeyManagerModule } from "@biconomy/modules"; -import Button from "../Button"; import { useAccount } from "wagmi"; +import { Hex, encodeAbiParameters, parseAbiParameters, parseUnits } from "viem"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { createSessionKeyManagerModule } from "@biconomy/account"; +import Button from "../Button"; import { useSmartAccountContext } from "../../contexts/SmartAccountContext"; -import { configInfo as config, showErrorMessage, showInfoMessage } from "../../utils"; -import { defaultAbiCoder } from "ethers/lib/utils"; -import { getActionForErrorMessage } from "../../utils/error-utils"; -import { DEFAULT_SESSION_KEY_MANAGER_MODULE } from "@biconomy/modules"; import { ERC20_SESSION_VALIDATION_MODULE } from "../../utils/chainConfig"; -import { useEthersSigner } from "../../contexts/ethers"; +import { getActionForErrorMessage } from "../../utils/error-utils"; +import { + configInfo as config, + showErrorMessage, + showInfoMessage, +} from "../../utils"; +import { managerModuleAddr } from "../../utils/constants"; const CreateSession: React.FC = () => { const classes = useStyles(); const { address } = useAccount(); - const signer = useEthersSigner(); - const { smartAccount, scwAddress } = useSmartAccountContext(); + const { scwAddress, smartAccount } = useSmartAccountContext(); const [loading, setLoading] = useState(false); const [isSessionKeyModuleEnabled, setIsSessionKeyModuleEnabled] = useState(false); @@ -29,7 +31,7 @@ const CreateSession: React.FC = () => { } try { let biconomySmartAccount = smartAccount; - const sessionKeyManagerModuleAddr = DEFAULT_SESSION_KEY_MANAGER_MODULE; + const sessionKeyManagerModuleAddr = managerModuleAddr; // Checks if Session Key Manager module is enabled on the smart account. // Before using session keys this module must be enabled. // If not, createSession transaction will also enable this module along with storing session info on-chain. @@ -56,55 +58,43 @@ const CreateSession: React.FC = () => { } try { let biconomySmartAccount = smartAccount; - const sessionKeyManagerModuleAddr = DEFAULT_SESSION_KEY_MANAGER_MODULE; + const sessionKeyManagerModuleAddr = managerModuleAddr; const erc20SessionValidationModuleAddr = ERC20_SESSION_VALIDATION_MODULE; // -----> setMerkle tree tx flow // create dapp side session key - const sessionSigner = ethers.Wallet.createRandom(); - const sessionKeyEOA = await sessionSigner.getAddress(); + const sessionPKey = generatePrivateKey(); + const sessionSigner = privateKeyToAccount(sessionPKey); + const sessionKeyEOA = sessionSigner.address; console.log("sessionKeyEOA", sessionKeyEOA); // Optional: JUST FOR DEMO: update local storage with session key // If you have session key-pair on the client side you can keep using those without making part of any storage - window.localStorage.setItem("sessionPKey", sessionSigner.privateKey); + window.localStorage.setItem("sessionPKey", sessionPKey); // Create an instance of Session Key Manager module from modules package // This module is responsible for below tasks/helpers: // a. Maintain session leaf storage in defined storage client (Biconomy by default using browser local storage which works for front-end apps) // b. Generate dummy signature for userOp estimations - // c. Provides helpers to sign userOpHash with session key in the right format and generate proof for particular leaf - const sessionManagerModule = await SessionKeyManagerModule.create({ + // c. Provides helpers to sign userOpHash with session key in the right format and generate proof for particular leaf + const sessionManagerModule = await createSessionKeyManagerModule({ moduleAddress: sessionKeyManagerModuleAddr, smartAccountAddress: scwAddress, }); - const tokenContract = new ethers.Contract( - config.usdc.address, - config.usdc.abi, - signer - ); - let decimals = 18; - - try { - decimals = await tokenContract.decimals(); - } catch (error) { - throw new Error("invalid token address supplied"); - } - // Cretae session key data // Session key data is always corrsponding to the Session Validation Module being used // It always requires the public address of the session key // Rest of the details depends on the actual permissions - // Here, our ERC20 Session Validation Module verifies ERC20 address, receiver and max amount - // - const sessionKeyData = defaultAbiCoder.encode( - ["address", "address", "address", "uint256"], + // Here, our ERC20 Session Validation Module verifies ERC20 address, receiver and max amount + // + const sessionKeyData = encodeAbiParameters( + parseAbiParameters("address, address, address, uint256"), [ sessionKeyEOA, - config.usdc.address, // erc20 token address + config.usdc.address as Hex, // erc20 token address "0x42138576848E839827585A3539305774D36B9602", // receiver address // You must send to same receiver when making use of the session - ethers.utils.parseUnits("50".toString(), decimals).toHexString(), // 50 usdc amount + parseUnits("50", 6), // 50 usdc amount ] ); @@ -112,11 +102,11 @@ const CreateSession: React.FC = () => { // This transaction needs a user signature and for gas sponsorship or ERC20 paymaster can be used. const sessionTxData = await sessionManagerModule.createSessionData([ { - validUntil: 0, // 0 value means extremes - validAfter: 0, // 0 value means extremes + validUntil: 0, // 0 value means extremes + validAfter: 0, // 0 value means extremes sessionValidationModule: erc20SessionValidationModuleAddr, - sessionPublicKey: sessionKeyEOA, - sessionKeyData: sessionKeyData, + sessionPublicKey: sessionKeyEOA as Hex, + sessionKeyData: sessionKeyData as Hex, }, // can optionally enable multiple leaves(sessions) altogether ]); @@ -124,8 +114,9 @@ const CreateSession: React.FC = () => { // tx to set session key const tx2 = { - to: sessionKeyManagerModuleAddr, // session manager module address - data: sessionTxData.data, + to: sessionKeyManagerModuleAddr as Hex, // session manager module address + value: BigInt(0), + data: sessionTxData.data as Hex, }; let transactionArray = []; @@ -134,23 +125,20 @@ const CreateSession: React.FC = () => { const tx1 = await biconomySmartAccount.getEnableModuleData( sessionKeyManagerModuleAddr ); - transactionArray.push(tx1); + transactionArray.push({ + to: tx1.to as Hex, + value: BigInt(0), + data: tx1.data as Hex, + }); } transactionArray.push(tx2); - // Building the user operation - // If you're going to use sponsorship paymaster details can be provided at this step - let partialUserOp = await biconomySmartAccount.buildUserOp( - transactionArray, - { - skipBundlerGasEstimation: false, - } - ); - - // This will send user operation to potentially enable session key manager module and set the session - const userOpResponse = await biconomySmartAccount.sendUserOp( - partialUserOp + let userOpResponse = await smartAccount.sendTransaction( + transactionArray ); + console.log("userOpHash", userOpResponse); + const { transactionHash } = await userOpResponse.waitForTxHash(); + console.log("txHash", transactionHash); console.log(`userOp Hash: ${userOpResponse.userOpHash}`); const transactionDetails = await userOpResponse.wait(); @@ -192,8 +180,9 @@ const CreateSession: React.FC = () => { ) : (

- This is a single transaction to enable the sesion key manager module and - make a session active on-chain using ERC20 session validation module. + This is a single transaction to enable the sesion key manager module + and make a session active on-chain using ERC20 session validation + module.

+ } + + ) + } + + export default UseABISVM; diff --git a/src/components/Modules/UseSessionsBatch.tsx b/src/components/Modules/UseSessionsBatch.tsx index 075549e..4de849f 100644 --- a/src/components/Modules/UseSessionsBatch.tsx +++ b/src/components/Modules/UseSessionsBatch.tsx @@ -7,22 +7,17 @@ import { } from "@biconomy/modules"; import Button from "../Button"; import { useAccount } from "wagmi"; -import { useEthersSigner } from "../../contexts/ethers"; import { useSmartAccountContext } from "../../contexts/SmartAccountContext"; import { configInfo as config, showSuccessMessage, showErrorMessage, } from "../../utils"; -import { - DEFAULT_BATCHED_SESSION_ROUTER_MODULE, - DEFAULT_SESSION_KEY_MANAGER_MODULE, -} from "@biconomy/modules"; +import { erc20ModuleAddr, managerModuleAddr, mockSessionModuleAddr, routerModuleAddr } from "../../utils/constants"; const ERC20RouterTransfer: React.FC = () => { const classes = useStyles(); const { address } = useAccount(); - const signer = useEthersSigner(); const { smartAccount, scwAddress } = useSmartAccountContext(); const [loading, setLoading] = useState(false); @@ -34,11 +29,6 @@ const ERC20RouterTransfer: React.FC = () => { try { setLoading(true); let biconomySmartAccount = smartAccount; - const managerModuleAddr = DEFAULT_SESSION_KEY_MANAGER_MODULE; - const erc20ModuleAddr = "0x000000D50C68705bd6897B2d17c7de32FB519fDA"; - const routerModuleAddr = DEFAULT_BATCHED_SESSION_ROUTER_MODULE; - const mockSessionModuleAddr = - "0x7Ba4a7338D7A90dfA465cF975Cc6691812C3772E"; // get session key from local storage const sessionKeyPrivKey = window.localStorage.getItem("sessionPKey"); @@ -47,7 +37,9 @@ const ERC20RouterTransfer: React.FC = () => { showErrorMessage("Session key not found"); return; } - const sessionSigner = new ethers.Wallet(sessionKeyPrivKey); + + const provider = new ethers.providers.JsonRpcProvider("https://endpoints.omniatech.io/v1/matic/mumbai/public"); + const sessionSigner = new ethers.Wallet(sessionKeyPrivKey, provider); console.log("sessionSigner", sessionSigner); // generate sessionModule @@ -65,57 +57,49 @@ const ERC20RouterTransfer: React.FC = () => { biconomySmartAccount = biconomySmartAccount.setActiveValidationModule(sessionRouterModule); + const nftContract = new ethers.Contract( + config.nft.address, + config.nft.abi, + provider + ); + // er20 transfer data generation const tokenContract = new ethers.Contract( config.usdc.address, config.usdc.abi, - signer + sessionSigner ); - let decimals = 18; - try { - decimals = await tokenContract.decimals(); - } catch (error) { - throw new Error("invalid token address supplied"); - } - const amountGwei = ethers.utils.parseUnits("5".toString(), decimals); - const data = ( + + + const amountGwei = ethers.utils.parseUnits("0.1".toString(), 6); // MAKE SURE SCW HAS ENOUGH USDC, otherwise user op will fail + const erc20TransferData = ( await tokenContract.populateTransaction.transfer( "0x42138576848E839827585A3539305774D36B9602", // receiver address amountGwei ) ).data; - const data2 = ( - await tokenContract.populateTransaction.transfer( + const nftMintData = ( + await nftContract.populateTransaction.safeMint( "0x5a86A87b3ea8080Ff0B99820159755a4422050e6", // receiver address 2 - amountGwei ) ).data; + // generate tx data to erc20 transfer const tx1 = { - to: "0xdA5289fCAAF71d52a80A254da614a192b693e977", //erc20 token address - data: data, - value: "0", + to: config.usdc.address, //erc20 token address + data: erc20TransferData!, }; + // generate tx data to nft mint const tx2 = { - to: "0xdA5289fCAAF71d52a80A254da614a192b693e977", //erc20 token address - data: data2, - value: "0", + to: config.nft.address, //erc20 token address + data: nftMintData!, }; - // build user op - // build user op - let userOp = await biconomySmartAccount.buildUserOp([tx1, tx2], { - overrides: { - // signature: "0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000456b395c4e107e0302553b90d1ef4a32e9000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000db3d753a1da5a6074a9f74f39a0a779d3300000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000080000000000000000000000000bfe121a6dcf92c49f6c2ebd4f306ba0ba0ab6f1c000000000000000000000000da5289fcaaf71d52a80a254da614a192b693e97700000000000000000000000042138576848e839827585a3539305774d36b96020000000000000000000000000000000000000000000000000000000002faf08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041feefc797ef9e9d8a6a41266a85ddf5f85c8f2a3d2654b10b415d348b150dabe82d34002240162ed7f6b7ffbc40162b10e62c3e35175975e43659654697caebfe1c00000000000000000000000000000000000000000000000000000000000000" - callGasLimit: 400000, // only if undeployed account - verificationGasLimit: 900000, - }, - skipBundlerGasEstimation: true, + let userOpResponse = await biconomySmartAccount.sendTransaction([tx1, tx2], { params: { batchSessionParams: [ { sessionSigner: sessionSigner, - // sessionID: "67e910ef2c", // only require session id filter when multiple leafs have same SVM sessionValidationModule: erc20ModuleAddr, }, { @@ -126,25 +110,18 @@ const ERC20RouterTransfer: React.FC = () => { }, }); - // send user op - const userOpResponse = await biconomySmartAccount.sendUserOp(userOp, { - batchSessionParams: [ - { - sessionSigner: sessionSigner, - sessionValidationModule: erc20ModuleAddr, - }, - { - sessionSigner: sessionSigner, - sessionValidationModule: mockSessionModuleAddr, - }, - ], - }); - console.log("userOpHash", userOpResponse); const { transactionHash } = await userOpResponse.waitForTxHash(); - console.log("txHash", transactionHash); - showSuccessMessage(`ERC20 Transfer ${transactionHash}`, transactionHash); - setLoading(false); + const { success } = await userOpResponse.wait(); + if(success === "false") { + setLoading(false); + console.log("txHash", transactionHash); + showErrorMessage("User op execution failed"); + } else { + console.log("txHash", transactionHash); + showSuccessMessage(`ERC20 Transfer ${transactionHash}`, transactionHash); + setLoading(false); + } } catch (err: any) { console.error(err); setLoading(false); @@ -158,14 +135,16 @@ const ERC20RouterTransfer: React.FC = () => { Use Cases {"->"} Gasless {"->"} ERC20 Transfer

-

ERC20 Transfer via Session Key

+

ERC20 Transfer via Batched Session Key Module

- This is an example gasless transaction to transfer ERC20 tokens. + This is an example gasless transaction to transfer ERC20 tokens and mint NFT using the Batched Session Key Router Module.

+ This transaction will transfer 0.1 USDC and also mint an NFT. +