For new React apps, we recommend the JavaScript SDK with React Hooks (@dynamic-labs-sdk/react-hooks) instead of the legacy React SDK documented here. The JS SDK comes with many benefits such as a much smaller bundle size and other optimizations. Use the React quickstart (JavaScript SDK) to get started.
Note: If you’re using Next.js, replace REACT_APP_ with NEXT_PUBLIC_ and for Vite, use VITE_.
In src/App.js, replace the value for the environmentId with your own Dynamic environment ID.
Navigate to src/Main.js, and replace the current contents with the following:
import React, { useEffect, useState } from "react"import { createPublicClient, parseEther } from "viem";import { createBundlerClient } from "viem/account-abstraction";import { polygonAmoy } from "viem/chains";import { isEthereumWallet } from "@dynamic-labs/ethereum";import { useDynamicContext, DynamicWidget } from "@dynamic-labs/sdk-react-core";import { toCircleSmartAccount, toModularTransport, walletClientToLocalAccount,} from "@circle-fin/modular-wallets-core";const clientKey = process.env.REACT_APP_CIRCLE_CLIENT_KEY!;const clientUrl = process.env.REACT_APP_CIRCLE_CLIENT_URL!;// Create Circle transportsconst modularTransport = toModularTransport( `${clientUrl}/polygonAmoy`, clientKey);// Create a public clientconst client = createPublicClient({ chain: polygonAmoy, transport: modularTransport,});// Create a bundler clientconst bundlerClient = createBundlerClient({ chain: polygonAmoy, transport: modularTransport,});export const Main = () => { const { primaryWallet } = useDynamicContext(); const [account, setAccount] = useState<any>(undefined); const [hash, setHash] = useState<string | undefined>(undefined); const [userOpHash, setUserOpHash] = useState<string | undefined>(undefined); useEffect(() => { setSigner(); async function setSigner() { if (!primaryWallet) { // Reset the account if the wallet is not connected setAccount(undefined); return; } if (!isEthereumWallet(primaryWallet)) { throw new Error("This wallet is not an Ethereum wallet"); } const dynamicProvider = await primaryWallet.getWalletClient(); toCircleSmartAccount({ client, owner: walletClientToLocalAccount(dynamicProvider), // Transform the wallet client to a local account }).then(setAccount); } }, [primaryWallet]); const sendUserOperation = async (event: React.FormEvent<HTMLFormElement>) => { try { event.preventDefault(); if (!account) return; const formData = new FormData(event.currentTarget); const to = formData.get("to") as `0x${string}`; const value = formData.get("value") as string; const hash = await bundlerClient.sendUserOperation({ account, calls: [ { to, value: parseEther(value), }, ], }); setUserOpHash(hash); const { receipt } = await bundlerClient.waitForUserOperationReceipt({ hash, }); setHash(receipt.transactionHash); } catch (error) { if (error instanceof Error) { alert(error.message); console.error(error); } } }; if (!primaryWallet) return <DynamicWidget /> return ( <div> {primaryWallet && !account ? ( <p>Loading...</p> ) : ( <> <p>Address: {account?.address}</p> <h2>Send User Operation</h2> <form onSubmit={sendUserOperation}> <input name="to" placeholder="Address" /> <input name="value" placeholder="Amount (ETH)" /> <button type="submit">Send</button> {userOpHash && <p>User Operation Hash: {userOpHash}</p>} {hash && <p>Transaction Hash: {hash}</p>} </form> </> )} </div> )
Run the app by executing:
npm start# oryarn start# orpnpm start# orbun run start
Check out the UI; once you sign up/login, you should see the following:
Add a valid address to send to, and an amount that is within your wallet’s balance plus a reasonable gas fee. Click on the “Send” button, and you should see the transaction hash in the UI!
Problem:Execution reverted for an unknown reasonSolution: This is most likely due to the account not having enough ETH to pay for the transfer, make sure your smart wallet is funded!