| 1 | import { useState } from 'react'; |
| 2 | import { |
| 3 | createWalletClient, |
| 4 | custom, |
| 5 | erc20Abi, |
| 6 | http, |
| 7 | type WalletClient, |
| 8 | type Hex, |
| 9 | formatUnits |
| 10 | } from 'viem'; |
| 11 | import { baseSepolia } from 'viem/chains'; |
| 12 | import { |
| 13 | createMeeClient, |
| 14 | toMultichainNexusAccount, |
| 15 | getMeeScanLink, |
| 16 | getMEEVersion, |
| 17 | MEEVersion, |
| 18 | type MeeClient, |
| 19 | type MultichainSmartAccount |
| 20 | } from '@biconomy/abstractjs'; |
| 21 | import { useReadContract } from 'wagmi'; |
| 22 | |
| 23 | export default function App() { |
| 24 | const [account, setAccount] = useState<string | null>(null); |
| 25 | const [walletClient, setWalletClient] = useState<WalletClient | null>(null); |
| 26 | const [meeClient, setMeeClient] = useState<MeeClient | null>(null); |
| 27 | const [orchestrator, setOrchestrator] = useState<MultichainSmartAccount | null>(null); |
| 28 | const [status, setStatus] = useState<string | null>(null); |
| 29 | const [meeScanLink, setMeeScanLink] = useState<string | null>(null); |
| 30 | const [recipients, setRecipients] = useState<string[]>(['']); |
| 31 | |
| 32 | const usdcAddress = '0x036CbD53842c5426634e7929541eC2318f3dCF7e'; |
| 33 | |
| 34 | const { data: balance } = useReadContract({ |
| 35 | abi: erc20Abi, |
| 36 | address: usdcAddress, |
| 37 | chainId: baseSepolia.id, |
| 38 | functionName: 'balanceOf', |
| 39 | args: account ? [account as Hex] : undefined, |
| 40 | query: { enabled: !!account } |
| 41 | }); |
| 42 | |
| 43 | const connectAndInit = async () => { |
| 44 | if (typeof (window as any).ethereum === 'undefined') { |
| 45 | alert('MetaMask not detected'); |
| 46 | return; |
| 47 | } |
| 48 | |
| 49 | const wallet = createWalletClient({ |
| 50 | chain: baseSepolia, |
| 51 | transport: custom((window as any).ethereum) |
| 52 | }); |
| 53 | setWalletClient(wallet); |
| 54 | |
| 55 | const [address] = await wallet.requestAddresses(); |
| 56 | setAccount(address); |
| 57 | |
| 58 | const multiAccount = await toMultichainNexusAccount({ |
| 59 | chainConfigurations: [ |
| 60 | { |
| 61 | chain: baseSepolia, |
| 62 | transport: http(), |
| 63 | version: getMEEVersion(MEEVersion.V2_1_0) |
| 64 | } |
| 65 | ], |
| 66 | signer: createWalletClient({ |
| 67 | account: address, |
| 68 | transport: custom((window as any).ethereum) |
| 69 | }) |
| 70 | }); |
| 71 | setOrchestrator(multiAccount); |
| 72 | |
| 73 | const mee = await createMeeClient({ account: multiAccount }); |
| 74 | setMeeClient(mee); |
| 75 | }; |
| 76 | |
| 77 | const executeTransfers = async () => { |
| 78 | if (!orchestrator || !meeClient || !account) { |
| 79 | alert('Account not initialized'); |
| 80 | return; |
| 81 | } |
| 82 | |
| 83 | try { |
| 84 | setStatus('Encoding instructions…'); |
| 85 | await walletClient?.switchChain({ id: baseSepolia.id }); |
| 86 | |
| 87 | const transfers = await Promise.all( |
| 88 | recipients |
| 89 | .filter((r) => r) |
| 90 | .map((recipient) => |
| 91 | orchestrator.buildComposable({ |
| 92 | type: 'default', |
| 93 | data: { |
| 94 | abi: erc20Abi, |
| 95 | chainId: baseSepolia.id, |
| 96 | to: usdcAddress, |
| 97 | functionName: 'transfer', |
| 98 | args: [recipient as Hex, 1_000_000n] |
| 99 | } |
| 100 | }) |
| 101 | ) |
| 102 | ); |
| 103 | |
| 104 | const totalAmount = BigInt(transfers.length) * 1_000_000n; |
| 105 | |
| 106 | setStatus('Requesting quote…'); |
| 107 | const fusionQuote = await meeClient.getFusionQuote({ |
| 108 | instructions: transfers, |
| 109 | trigger: { |
| 110 | chainId: baseSepolia.id, |
| 111 | tokenAddress: usdcAddress, |
| 112 | amount: totalAmount |
| 113 | }, |
| 114 | feeToken: { |
| 115 | address: usdcAddress, |
| 116 | chainId: baseSepolia.id |
| 117 | } |
| 118 | }); |
| 119 | |
| 120 | setStatus('Please sign the transaction…'); |
| 121 | const { hash } = await meeClient.executeFusionQuote({ fusionQuote }); |
| 122 | |
| 123 | const link = getMeeScanLink(hash); |
| 124 | setMeeScanLink(link); |
| 125 | setStatus('Waiting for completion…'); |
| 126 | |
| 127 | await meeClient.waitForSupertransactionReceipt({ hash }); |
| 128 | setStatus('Transaction completed!'); |
| 129 | } catch (err: any) { |
| 130 | console.error(err); |
| 131 | setStatus(`Error: ${err.message ?? err}`); |
| 132 | } |
| 133 | }; |
| 134 | |
| 135 | return ( |
| 136 | <main style={{ padding: 40, fontFamily: 'sans-serif' }}> |
| 137 | <h1>Biconomy MEE Quickstart (Base Sepolia)</h1> |
| 138 | |
| 139 | <button onClick={connectAndInit} disabled={!!account}> |
| 140 | {account ? 'Connected' : 'Connect Wallet'} |
| 141 | </button> |
| 142 | |
| 143 | {account && ( |
| 144 | <div style={{ marginTop: 20 }}> |
| 145 | <p><strong>Address:</strong> {account}</p> |
| 146 | <p>USDC Balance: {balance ? `${formatUnits(balance, 6)} USDC` : '–'}</p> |
| 147 | |
| 148 | <h3>Recipients</h3> |
| 149 | {recipients.map((recipient, idx) => ( |
| 150 | <input |
| 151 | key={idx} |
| 152 | type="text" |
| 153 | value={recipient} |
| 154 | onChange={(e) => { |
| 155 | const updated = [...recipients]; |
| 156 | updated[idx] = e.target.value; |
| 157 | setRecipients(updated); |
| 158 | }} |
| 159 | placeholder="0x..." |
| 160 | style={{ display: 'block', margin: '8px 0', padding: '6px', width: '100%' }} |
| 161 | /> |
| 162 | ))} |
| 163 | |
| 164 | <button onClick={() => setRecipients([...recipients, ''])}> |
| 165 | Add Recipient |
| 166 | </button> |
| 167 | </div> |
| 168 | )} |
| 169 | |
| 170 | {meeClient && ( |
| 171 | <> |
| 172 | <p style={{ marginTop: 20 }}> |
| 173 | <strong>MEE client ready</strong> – you can now orchestrate multichain transactions! |
| 174 | </p> |
| 175 | <button onClick={executeTransfers}> |
| 176 | Send 1 USDC to each recipient |
| 177 | </button> |
| 178 | </> |
| 179 | )} |
| 180 | |
| 181 | {status && <p style={{ marginTop: 20 }}>{status}</p>} |
| 182 | |
| 183 | {meeScanLink && ( |
| 184 | <p style={{ marginTop: 10 }}> |
| 185 | <a href={meeScanLink} target="_blank" rel="noopener noreferrer"> |
| 186 | View on MEE Scan |
| 187 | </a> |
| 188 | </p> |
| 189 | )} |
| 190 | </main> |
| 191 | ); |
| 192 | } |