diff --git a/advanced/wallets/react-wallet-v2/src/components/ChainSmartAddressMini.tsx b/advanced/wallets/react-wallet-v2/src/components/ChainSmartAddressMini.tsx
index cc33b2435..51f0f6bd1 100644
--- a/advanced/wallets/react-wallet-v2/src/components/ChainSmartAddressMini.tsx
+++ b/advanced/wallets/react-wallet-v2/src/components/ChainSmartAddressMini.tsx
@@ -3,6 +3,9 @@ import { Hex } from 'viem'
import ChainAddressMini from './ChainAddressMini'
import { createOrRestoreEIP155Wallet, eip155Wallets } from '@/utils/EIP155WalletUtil'
import { Spinner } from '@nextui-org/react'
+import { Chain, allowedChains } from '@/utils/SmartAccountUtils'
+import { useSnapshot } from 'valtio'
+import SettingsStore from '@/store/SettingsStore'
interface Props {
namespace: string
@@ -18,7 +21,8 @@ const getKey = (namespace?: string) => {
}
export default function ChainSmartAddressMini({ namespace }: Props) {
- const { address } = useSmartAccount(getKey(namespace) as `0x${string}`)
+ const { activeChainId } = useSnapshot(SettingsStore.state)
+ const { address } = useSmartAccount(getKey(namespace) as `0x${string}`, allowedChains.find((c) => c.id.toString() === activeChainId) as Chain)
if (!address) return
return (
diff --git a/advanced/wallets/react-wallet-v2/src/components/SmartAccountCard.tsx b/advanced/wallets/react-wallet-v2/src/components/SmartAccountCard.tsx
index 3c3354bb8..f361a5cf8 100644
--- a/advanced/wallets/react-wallet-v2/src/components/SmartAccountCard.tsx
+++ b/advanced/wallets/react-wallet-v2/src/components/SmartAccountCard.tsx
@@ -5,9 +5,10 @@ import { updateSignClientChainId } from '@/utils/WalletConnectUtil'
import { Avatar, Button, Text, Tooltip, Loading } from '@nextui-org/react'
import { eip155Wallets } from '@/utils/EIP155WalletUtil'
import Image from 'next/image'
-import { useState, useEffect } from 'react'
+import { useState } from 'react'
import { useSnapshot } from 'valtio'
import useSmartAccount from '@/hooks/useSmartAccount'
+import { Chain, allowedChains } from '@/utils/SmartAccountUtils'
interface Props {
name: string
@@ -28,13 +29,14 @@ export default function SmartAccountCard({
}: Props) {
const [copied, setCopied] = useState(false)
const { activeChainId } = useSnapshot(SettingsStore.state)
+ const chain = allowedChains.find((c) => c.id.toString() === chainId.split(':')[0]) as Chain
const {
deploy,
isDeployed,
address: smartAccountAddress,
loading,
sendTestTransaction,
- } = useSmartAccount(eip155Wallets[address].getPrivateKey() as `0x${string}`)
+ } = useSmartAccount(eip155Wallets[address].getPrivateKey() as `0x${string}`, chain)
function onCopy() {
navigator?.clipboard?.writeText(address)
diff --git a/advanced/wallets/react-wallet-v2/src/data/EIP155Data.ts b/advanced/wallets/react-wallet-v2/src/data/EIP155Data.ts
index fbeb31d1e..0c16d6ba5 100644
--- a/advanced/wallets/react-wallet-v2/src/data/EIP155Data.ts
+++ b/advanced/wallets/react-wallet-v2/src/data/EIP155Data.ts
@@ -81,6 +81,7 @@ export const EIP155_TEST_CHAINS: Record = {
rgb: '99, 125, 234',
rpc: 'https://rpc.sepolia.org',
namespace: 'eip155',
+ smartAccountEnabled: true,
},
'eip155:43113': {
chainId: 43113,
@@ -96,7 +97,8 @@ export const EIP155_TEST_CHAINS: Record = {
logo: '/chain-logos/eip155-137.png',
rgb: '130, 71, 229',
rpc: 'https://matic-mumbai.chainstacklabs.com',
- namespace: 'eip155'
+ namespace: 'eip155',
+ smartAccountEnabled: true,
},
'eip155:420': {
chainId: 420,
diff --git a/advanced/wallets/react-wallet-v2/src/hooks/useInitialization.ts b/advanced/wallets/react-wallet-v2/src/hooks/useInitialization.ts
index d96955fe7..9f378ab93 100644
--- a/advanced/wallets/react-wallet-v2/src/hooks/useInitialization.ts
+++ b/advanced/wallets/react-wallet-v2/src/hooks/useInitialization.ts
@@ -49,7 +49,7 @@ export default function useInitialization() {
// restart transport if relayer region changes
const onRelayerRegionChange = useCallback(() => {
try {
- web3wallet.core.relayer.restartTransport(relayerRegionURL)
+ web3wallet?.core?.relayer.restartTransport(relayerRegionURL)
prevRelayerURLValue.current = relayerRegionURL
} catch (err: unknown) {
alert(err)
diff --git a/advanced/wallets/react-wallet-v2/src/hooks/useSmartAccount.ts b/advanced/wallets/react-wallet-v2/src/hooks/useSmartAccount.ts
index b282422dd..2f383d153 100644
--- a/advanced/wallets/react-wallet-v2/src/hooks/useSmartAccount.ts
+++ b/advanced/wallets/react-wallet-v2/src/hooks/useSmartAccount.ts
@@ -1,10 +1,11 @@
import { SmartAccountLib } from "@/lib/SmartAccountLib";
import SettingsStore from "@/store/SettingsStore";
+import { Chain, VITALIK_ADDRESS } from "@/utils/SmartAccountUtils";
import { useCallback, useEffect, useState } from "react";
import { useSnapshot } from "valtio";
import { Hex } from "viem";
-export default function useSmartAccount(signerPrivateKey?: Hex) {
+export default function useSmartAccount(signerPrivateKey: Hex, chain: Chain) {
const [loading, setLoading] = useState(false)
const [client, setClient] = useState();
const [isDeployed, setIsDeployed] = useState(false)
@@ -14,7 +15,8 @@ export default function useSmartAccount(signerPrivateKey?: Hex) {
const execute = useCallback(async (callback: () => void) => {
try {
setLoading(true)
- await callback()
+ const res = await callback()
+ console.log('result:', res)
setLoading(false)
}
catch (e) {
@@ -30,18 +32,23 @@ export default function useSmartAccount(signerPrivateKey?: Hex) {
const sendTestTransaction = useCallback(async () => {
if (!client) return
- execute(client?.sendTestTransaction)
+ execute(() => client?.sendTransaction({
+ to: VITALIK_ADDRESS,
+ value: 0n,
+ data: '0x',
+ }))
}, [client, execute])
useEffect(() => {
- if (!signerPrivateKey) return
+ console.log('chain', chain)
+ if (!signerPrivateKey || !chain) return
const smartAccountClient = new SmartAccountLib({
+ chain,
privateKey: signerPrivateKey,
- chain: 'goerli',
sponsored: smartAccountSponsorshipEnabled,
})
setClient(smartAccountClient)
- }, [signerPrivateKey, smartAccountSponsorshipEnabled])
+ }, [signerPrivateKey, smartAccountSponsorshipEnabled, chain])
useEffect(() => {
client?.checkIfSmartAccountDeployed()
@@ -49,7 +56,7 @@ export default function useSmartAccount(signerPrivateKey?: Hex) {
setIsDeployed(deployed)
setAddress(client?.address)
})
- }, [client])
+ }, [client, chain])
return {
diff --git a/advanced/wallets/react-wallet-v2/src/hooks/useWalletConnectEventsManager.ts b/advanced/wallets/react-wallet-v2/src/hooks/useWalletConnectEventsManager.ts
index af4f6c2be..a860ba2ac 100644
--- a/advanced/wallets/react-wallet-v2/src/hooks/useWalletConnectEventsManager.ts
+++ b/advanced/wallets/react-wallet-v2/src/hooks/useWalletConnectEventsManager.ts
@@ -117,7 +117,7 @@ export default function useWalletConnectEventsManager(initialized: boolean) {
* Set up WalletConnect event listeners
*****************************************************************************/
useEffect(() => {
- if (initialized) {
+ if (initialized && web3wallet) {
//sign
web3wallet.on('session_proposal', onSessionProposal)
web3wallet.on('session_request', onSessionRequest)
diff --git a/advanced/wallets/react-wallet-v2/src/lib/SmartAccountLib.ts b/advanced/wallets/react-wallet-v2/src/lib/SmartAccountLib.ts
index db38ea7c8..5f0e65f89 100644
--- a/advanced/wallets/react-wallet-v2/src/lib/SmartAccountLib.ts
+++ b/advanced/wallets/react-wallet-v2/src/lib/SmartAccountLib.ts
@@ -1,23 +1,20 @@
-import { createSmartAccountClient, getAccountNonce, signUserOperationHashWithECDSA } from 'permissionless'
+import { BundlerActions, BundlerClient, bundlerActions, createSmartAccountClient, getAccountNonce } from 'permissionless'
import { privateKeyToSafeSmartAccount } from 'permissionless/accounts'
import * as chains from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts'
-import { type Chain, createWalletClient, formatEther, createPublicClient, http, Address, Hex } from 'viem'
-import { createPimlicoBundlerClient, createPimlicoPaymasterClient } from 'permissionless/clients/pimlico'
+import { createWalletClient, formatEther, createPublicClient, http, Address, Hex, PublicClient, createClient, WalletClient } from 'viem'
+import { PimlicoPaymasterClient, createPimlicoPaymasterClient } from 'permissionless/clients/pimlico'
import { UserOperation } from 'permissionless/types'
-import { GOERLI_PAYMASTER_ADDRESS, GOERLI_USDC_ADDRESS, genereteApproveCallData, genereteDummyCallData } from '@/utils/ERC20PaymasterUtil'
import { providers } from 'ethers'
+import { PimlicoBundlerActions, pimlicoBundlerActions } from 'permissionless/actions/pimlico'
+import { Chain, ENTRYPOINT_ADDRESSES, PAYMASTER_ADDRESSES, USDC_ADDRESSES, VITALIK_ADDRESS, approveUSDCSpendCallData, bundlerUrl, paymasterUrl, publicRPCUrl } from '@/utils/SmartAccountUtils'
-export const smartAccountEnabledChains = ['sepolia', 'goerli'] as const
-export type SmartAccountEnabledChains = typeof smartAccountEnabledChains[number]
type SmartAccountLibOptions = {
privateKey: `0x${string}`
- chain: SmartAccountEnabledChains
+ chain: Chain
sponsored?: boolean
};
-const ENTRY_POINT_ADDRESS = '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789'
-
// -- Helpers -----------------------------------------------------------------
const pimlicoApiKey = process.env.NEXT_PUBLIC_PIMLICO_KEY
const projectId = process.env.NEXT_PUBLIC_PROJECT_ID
@@ -25,192 +22,105 @@ const projectId = process.env.NEXT_PUBLIC_PROJECT_ID
// -- Sdk ---------------------------------------------------------------------
export class SmartAccountLib {
public chain: Chain
- private pimlicoApiKey: string
- #signerPrivateKey: `0x${string}`;
public isDeployed: boolean = false;
public address?: `0x${string}`;
public sponsored: boolean = true;
+
+ private publicClient: PublicClient
+ private paymasterClient: PimlicoPaymasterClient
+ private bundlerClient: BundlerClient & BundlerActions & PimlicoBundlerActions
+ private signerClient: WalletClient
+
+ #signerPrivateKey: `0x${string}`;
public constructor({ privateKey, chain, sponsored = true }: SmartAccountLibOptions) {
if (!pimlicoApiKey) {
- throw new Error('Missing required data in SmartAccountSdk')
+ throw new Error('A Pimlico API Key is required')
}
- this.pimlicoApiKey = pimlicoApiKey
- this.chain = chains[chain] as Chain
- this.#signerPrivateKey = privateKey
+ this.chain = chain
this.sponsored = sponsored
- }
-
-
- // -- Private -----------------------------------------------------------------
- private get walletConnectTransport() {
- return http(
- `https://rpc.walletconnect.com/v1/?chainId=EIP155:${this.chain.id}&projectId=${projectId}`,
- { retryDelay: 1000 }
- )
- }
-
- private get bundlerTransport() {
- return http(
- `https://api.pimlico.io/v1/${this.chain.name.toLowerCase()}/rpc?apikey=${this.pimlicoApiKey}`,
- { retryDelay: 1000 }
- )
- }
-
- private get paymasterTransport() {
- return http(
- `https://api.pimlico.io/v2/${this.chain.name.toLowerCase()}/rpc?apikey=${this.pimlicoApiKey}`,
- { retryDelay: 1000 }
- )
- }
-
-
- private get bundlerClient() {
- return createPimlicoBundlerClient({
- chain: this.chain,
- transport: this.bundlerTransport
+ this.#signerPrivateKey = privateKey
+ this.publicClient = createPublicClient({
+ transport: http(publicRPCUrl({ chain: this.chain }))
})
- }
- private get publicClient() {
- return createPublicClient({
- chain: this.chain,
- transport: this.walletConnectTransport
+ this.paymasterClient = createPimlicoPaymasterClient({
+ transport: http(paymasterUrl({ chain: this.chain }))
})
- }
- private get paymasterClient() {
- return createPimlicoPaymasterClient({
- chain: this.chain,
- transport: this.paymasterTransport
+ this.bundlerClient = createClient({
+ transport: http(bundlerUrl({ chain: this.chain })),
+ chain: this.chain
})
- }
+ .extend(bundlerActions)
+ .extend(pimlicoBundlerActions)
- private get signerClient(){
- const signerAccount = privateKeyToAccount(this.#signerPrivateKey)
- return createWalletClient({
- account: signerAccount,
+ this.signerClient = createWalletClient({
+ account: privateKeyToAccount(this.#signerPrivateKey),
chain: this.chain,
- transport: this.walletConnectTransport
+ transport: http(publicRPCUrl({ chain: this.chain }))
})
- }
- private getAccountNonce() {
- return getAccountNonce(this.publicClient, {
- entryPoint: ENTRY_POINT_ADDRESS,
- sender: this.address as Address
- })
}
- private getSmartAccountClient = async () => {
- const smartAccount = await privateKeyToSafeSmartAccount(this.publicClient, {
- privateKey: this.#signerPrivateKey,
- safeVersion: '1.4.1',
- entryPoint: ENTRY_POINT_ADDRESS
- })
+ // -- Private -----------------------------------------------------------------
+ private getSmartAccountClient = async (
+ sponsorUserOperation?: (args: {
+ userOperation: UserOperation
+ entryPoint: Address
+ }) => Promise
+ ) => {
+ const account = await this.getAccount()
return createSmartAccountClient({
- account: smartAccount,
+ account,
chain: this.chain,
- transport: this.bundlerTransport,
- sponsorUserOperation: this.sponsored ? this.paymasterClient.sponsorUserOperation : undefined,
+ transport: http(bundlerUrl({ chain: this.chain })),
+ sponsorUserOperation: sponsorUserOperation
+ ? sponsorUserOperation
+ : this.sponsored ? this.paymasterClient.sponsorUserOperation : undefined
+ }).extend(pimlicoBundlerActions)
+ }
+
+ public getNonce = async () => {
+ const smartAccountClient = await this.getSmartAccountClient()
+ return getAccountNonce(this.publicClient, {
+ sender: smartAccountClient.account.address as Hex,
+ entryPoint: ENTRYPOINT_ADDRESSES[this.chain.name]
})
}
private prefundSmartAccount = async (address: `0x${string}`) => {
- const signerAccountViemClient = this.signerClient
- const publicClient = this.publicClient;
- const bundlerClient = this.bundlerClient;
- const smartAccountBalance = await publicClient.getBalance({ address })
+ if (this.sponsored) {
+ return
+ }
+
+ const smartAccountBalance = await this.publicClient.getBalance({ address })
console.log(`Smart Account Balance: ${formatEther(smartAccountBalance)} ETH`)
if (smartAccountBalance < 1n) {
console.log(`Smart Account has no balance. Starting prefund`)
- const { fast: fastPrefund } = await bundlerClient.getUserOperationGasPrice()
- const prefundHash = await signerAccountViemClient.sendTransaction({
+ const { fast: fastPrefund } = await this.bundlerClient.getUserOperationGasPrice()
+ const prefundHash = await this.signerClient.sendTransaction({
to: address,
+ chain: this.chain,
+ account: this.signerClient.account!,
value: 10000000000000000n,
maxFeePerGas: fastPrefund.maxFeePerGas,
maxPriorityFeePerGas: fastPrefund.maxPriorityFeePerGas
})
- await publicClient.waitForTransactionReceipt({ hash: prefundHash })
+ await this.publicClient.waitForTransactionReceipt({ hash: prefundHash })
console.log(`Prefunding Success`)
- const newSmartAccountBalance = await publicClient.getBalance({ address })
+ const newSmartAccountBalance = await this.publicClient.getBalance({ address })
console.log(
`Smart Account Balance: ${formatEther(newSmartAccountBalance)} ETH`
)
}
}
- private prepareSponsoredUserOperation = async (callData: Hex, paymaster?: Hex) => {
- // 1. Get new nonce
- const newNonce = await this.getAccountNonce()
-
- // 2. Get gas price
- const { fast } = await this.bundlerClient.getUserOperationGasPrice()
-
- // 3. Generate dummy user operation with empty transfer to vitalik
- const sponsoredUserOperation: UserOperation = {
- sender: this.address as Address,
- nonce: newNonce,
- initCode: "0x",
- callData: callData,
- callGasLimit: 100_000n, // hardcode it for now at a high value
- verificationGasLimit: 500_000n, // hardcode it for now at a high value
- preVerificationGas: 50_000n, // hardcode it for now at a high value
- maxFeePerGas: fast.maxFeePerGas,
- maxPriorityFeePerGas: fast.maxPriorityFeePerGas,
- paymasterAndData: paymaster ?? '0x', // to use the erc20 paymaster, put its address in the paymasterAndData field
- signature: "0x"
- }
-
- //4. Sign the userop
- sponsoredUserOperation.signature = await signUserOperationHashWithECDSA({
- account: this.signerClient.account,
- userOperation: sponsoredUserOperation,
- chainId: this.chain.id,
- entryPoint: ENTRY_POINT_ADDRESS
- })
-
- return sponsoredUserOperation;
- }
-
- private submitUserOperation = async (userOperation: UserOperation) => {
- const userOperationHash = await this.bundlerClient.sendUserOperation({
- userOperation,
- entryPoint: ENTRY_POINT_ADDRESS
- })
- console.log(`UserOperation submitted. Hash: ${userOperationHash}`)
-
- console.log("Querying for receipts...")
- const receipt = await this.bundlerClient.waitForUserOperationReceipt({
- hash: userOperationHash
- })
- console.log(`Receipt found!\nTransaction hash: ${receipt.receipt.transactionHash}`)
- }
-
- private approveUSDC = async () => {
- const approveCallData = genereteApproveCallData(GOERLI_USDC_ADDRESS, GOERLI_PAYMASTER_ADDRESS)
- const gasPriceResult = await this.bundlerClient.getUserOperationGasPrice()
- const nonce = await this.getAccountNonce()
-
- const smartAccountClient = await this.getSmartAccountClient();
- console.log(`Approving USDC for ${this.address} with nonce ${nonce}`)
- const hash = await smartAccountClient.sendTransaction({
- to: GOERLI_USDC_ADDRESS,
- value: 0n,
- maxFeePerGas: gasPriceResult.fast.maxFeePerGas,
- maxPriorityFeePerGas: gasPriceResult.fast.maxPriorityFeePerGas,
- data: approveCallData,
- })
-
- await this.publicClient.waitForTransactionReceipt({ hash })
- console.log(`USDC Approval Success`)
- }
-
private getSmartAccountUSDCBalance = async () => {
const params = {
abi: [
@@ -221,7 +131,7 @@ export class SmartAccountLib {
type: "function",
}
],
- address: GOERLI_USDC_ADDRESS as Hex,
+ address: USDC_ADDRESSES[this.chain.name] as Hex,
functionName: "balanceOf",
args: [this.address!]
}
@@ -229,10 +139,48 @@ export class SmartAccountLib {
return usdcBalance
}
+ private sponsorUserOperation = async ({ userOperation }: { userOperation: UserOperation }) => {
+ const userOperationWithPaymasterAndData = {
+ ...userOperation,
+ paymasterAndData: PAYMASTER_ADDRESSES[this.chain.name]
+ }
+
+ console.log('Estimating gas limits...', userOperationWithPaymasterAndData)
+
+ const gasLimits = await this.bundlerClient.estimateUserOperationGas({
+ userOperation: userOperationWithPaymasterAndData,
+ entryPoint: ENTRYPOINT_ADDRESSES[this.chain.name]
+ })
+
+ return {
+ ...userOperationWithPaymasterAndData,
+ callGasLimit: gasLimits.callGasLimit,
+ verificationGasLimit: gasLimits.verificationGasLimit,
+ preVerificationGas: gasLimits.preVerificationGas
+ }
+ }
+
// -- Public ------------------------------------------------------------------
- static isSmartAccount = async (address: Address, chain: SmartAccountEnabledChains) => {
+ public getAccount = async () =>
+ privateKeyToSafeSmartAccount(this.publicClient, {
+ privateKey: this.#signerPrivateKey,
+ safeVersion: '1.4.1', // simple version
+ entryPoint: ENTRYPOINT_ADDRESSES[this.chain.name], // global entrypoint
+ setupTransactions: [
+ {
+ to: USDC_ADDRESSES[this.chain.name],
+ value: 0n,
+ data: approveUSDCSpendCallData({
+ to: PAYMASTER_ADDRESSES[this.chain.name],
+ amount: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn
+ })
+ }
+ ]
+ })
+
+ static isSmartAccount = async (address: Address, chain: Chain) => {
const client = createPublicClient({
- chain: chains[chain],
+ chain,
transport: http(
`https://rpc.walletconnect.com/v1/?chainId=EIP155:${chains.goerli.id}&projectId=${projectId}`,
{ retryDelay: 1000 }
@@ -242,24 +190,21 @@ export class SmartAccountLib {
return Boolean(bytecode)
}
- // By default first transaction will deploy the smart contract if it hasn't been deployed yet
- public sendTestTransaction = async () => {
- const publicClient = this.publicClient;
- const bundlerClient = this.bundlerClient;
- const smartAccountClient = await this.getSmartAccountClient();
- const { fast: testGas, } = await bundlerClient.getUserOperationGasPrice()
-
- const testHash = await smartAccountClient.sendTransaction({
- to: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045' as `0x${string}`,
- value: 0n,
- maxFeePerGas: testGas.maxFeePerGas,
- maxPriorityFeePerGas: testGas.maxPriorityFeePerGas,
+ public sendTransaction = async ({
+ to,
+ value,
+ data
+ }: { to: Address; value: bigint; data: Hex }) => {
+ console.log(`Sending Transaction to ${to} with value ${value.toString()} and data ${data}`)
+ const smartAccountClient = await this.getSmartAccountClient()
+ const gasPrices = await smartAccountClient.getUserOperationGasPrice()
+ return smartAccountClient.sendTransaction({
+ to,
+ value,
+ data,
+ maxFeePerGas: gasPrices.fast.maxFeePerGas,
+ maxPriorityFeePerGas: gasPrices.fast.maxPriorityFeePerGas
})
-
- console.log(`Sending Test Transaction With Hash: ${testHash}`)
-
- await publicClient.waitForTransactionReceipt({ hash: testHash })
- console.log(`Test Transaction Success`)
}
public signMessage = async (message: string) => {
@@ -280,7 +225,11 @@ export class SmartAccountLib {
return smartAccountClient.account.signTransaction(transaction)
}
- public sendUSDCSponsoredTransaction = async () => {
+ public sendUSDCSponsoredTransaction = async ({
+ to,
+ value,
+ data
+ }: { to: Address; value: bigint; data: Hex }) => {
// 1. Check USDC Balance on smart account
const usdcBalance = await this.getSmartAccountUSDCBalance()
@@ -292,22 +241,22 @@ export class SmartAccountLib {
)
}
+ const smartAccountClient = await this.getSmartAccountClient(this.sponsorUserOperation)
+ const gasPrices = await smartAccountClient.getUserOperationGasPrice()
-
- // 2. Approve USDC usage (currently sponsored by Pimlico veridfy paymaster)
- await this.approveUSDC()
-
- // 3. Send transaction
- const dummyCallData = genereteDummyCallData()
- const userOperation = await this.prepareSponsoredUserOperation(dummyCallData, GOERLI_PAYMASTER_ADDRESS)
-
- await this.submitUserOperation(userOperation)
+ return smartAccountClient.sendTransaction({
+ to,
+ value,
+ data,
+ maxFeePerGas: gasPrices.fast.maxFeePerGas,
+ maxPriorityFeePerGas: gasPrices.fast.maxPriorityFeePerGas
+ })
}
public checkIfSmartAccountDeployed = async () => {
const smartAccountClient = await this.getSmartAccountClient();
- console.log('checking if deployed', smartAccountClient.account.address)
+ console.log('checking if deployed', smartAccountClient.account.address, this.chain.name)
const bytecode = await this.publicClient.getBytecode({ address: smartAccountClient.account.address })
this.isDeployed = Boolean(bytecode)
@@ -328,7 +277,11 @@ export class SmartAccountLib {
await this.prefundSmartAccount(smartAccountClient.account.address)
// Step 4: Create account by sending test tx
- await this.sendTestTransaction()
+ await this.sendTransaction({
+ to: VITALIK_ADDRESS,
+ value: 0n,
+ data: '0x'
+ })
await this.checkIfSmartAccountDeployed()
console.log(`Account Created`)
}
diff --git a/advanced/wallets/react-wallet-v2/src/pages/_app.tsx b/advanced/wallets/react-wallet-v2/src/pages/_app.tsx
index 8de06ad42..8cf397609 100644
--- a/advanced/wallets/react-wallet-v2/src/pages/_app.tsx
+++ b/advanced/wallets/react-wallet-v2/src/pages/_app.tsx
@@ -20,11 +20,11 @@ export default function App({ Component, pageProps }: AppProps) {
useWalletConnectEventsManager(initialized)
useEffect(() => {
if (!initialized) return
- web3wallet.core.relayer.on(RELAYER_EVENTS.connect, () => {
+ web3wallet?.core.relayer.on(RELAYER_EVENTS.connect, () => {
styledToast('Network connection is restored!', 'success')
})
- web3wallet.core.relayer.on(RELAYER_EVENTS.disconnect, () => {
+ web3wallet?.core.relayer.on(RELAYER_EVENTS.disconnect, () => {
styledToast('Network connection lost.', 'error')
})
}, [initialized])
diff --git a/advanced/wallets/react-wallet-v2/src/utils/EIP155RequestHandlerUtil.ts b/advanced/wallets/react-wallet-v2/src/utils/EIP155RequestHandlerUtil.ts
index 74af1d164..6cc20b5bd 100644
--- a/advanced/wallets/react-wallet-v2/src/utils/EIP155RequestHandlerUtil.ts
+++ b/advanced/wallets/react-wallet-v2/src/utils/EIP155RequestHandlerUtil.ts
@@ -12,6 +12,7 @@ import { SignClientTypes } from '@walletconnect/types'
import { getSdkError } from '@walletconnect/utils'
import { providers } from 'ethers'
import { Hex } from 'viem'
+import { allowedChains } from './SmartAccountUtils'
type RequestEventArgs = Omit
@@ -22,12 +23,11 @@ const getWallet = async (params: any) => {
return eoaWallet
}
- // TODO improve for multichain
const deployedSmartAccounts = await Promise.all(Object.values(eip155Wallets).map(async (wallet) => {
const smartAccount = new SmartAccountLib({
privateKey: wallet.getPrivateKey() as Hex,
- chain: 'goerli',
- sponsored: true, // Sponsor for now but should be dynamic according to SettingsStore
+ chain: allowedChains[0], // TODO: FIX FOR MULTI NETWORK
+ sponsored: true, // TODO: Sponsor for now but should be dynamic according to SettingsStore
})
const isDeployed = await smartAccount.checkIfSmartAccountDeployed()
if (isDeployed) {
@@ -35,7 +35,7 @@ const getWallet = async (params: any) => {
}
return null
}));
- const validSmartAccounts = deployedSmartAccounts.filter(val => !!val) as Array
+ const validSmartAccounts = deployedSmartAccounts.filter(Boolean) as Array
const smartAccountAddress = getWalletAddressFromParams(validSmartAccounts.map(acc => acc.address!), params)
return validSmartAccounts.find((smartAccount) => smartAccount?.address === smartAccountAddress) as SmartAccountLib
diff --git a/advanced/wallets/react-wallet-v2/src/utils/ERC20PaymasterUtil.ts b/advanced/wallets/react-wallet-v2/src/utils/ERC20PaymasterUtil.ts
deleted file mode 100644
index aa890260e..000000000
--- a/advanced/wallets/react-wallet-v2/src/utils/ERC20PaymasterUtil.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { Address, Hex, encodeFunctionData } from "viem"
-
-export const VITALIK_ADDRESS = '0xd8da6bf26964af9d7eed9e03e53415d37aa96045' as Hex
-export const GOERLI_PAYMASTER_ADDRESS = '0xEc43912D8C772A0Eba5a27ea5804Ba14ab502009'
-export const GOERLI_USDC_ADDRESS = '0x07865c6e87b9f70255377e024ace6630c1eaa37f'
-
-export const genereteDummyCallData = () => {
- // SEND EMPTY CALL TO VITALIK
- const to = VITALIK_ADDRESS
- const value = 0n
- const data = "0x"
-
- const callData = encodeFunctionData({
- abi: [
- {
- inputs: [
- { name: "dest", type: "address" },
- { name: "value", type: "uint256" },
- { name: "func", type: "bytes" }
- ],
- name: "execute",
- outputs: [],
- stateMutability: "nonpayable",
- type: "function"
- }
- ],
- args: [to, value, data]
- })
-
- return callData
-}
-
-
-export const genereteApproveCallData = (erc20TokenAddress: Address, paymasterAddress: Address) => {
- const approveData = encodeFunctionData({
- abi: [
- {
- inputs: [
- { name: "_spender", type: "address" },
- { name: "_value", type: "uint256" }
- ],
- name: "approve",
- outputs: [{ name: "", type: "bool" }],
- payable: false,
- stateMutability: "nonpayable",
- type: "function"
- }
- ],
- args: [paymasterAddress, 0xffffffffffffn]
- })
-
- // GENERATE THE CALLDATA TO APPROVE THE USDC
- const to = erc20TokenAddress
- const value = 0n
- const data = approveData
-
- const callData = encodeFunctionData({
- abi: [
- {
- inputs: [
- { name: "dest", type: "address" },
- { name: "value", type: "uint256" },
- { name: "func", type: "bytes" }
- ],
- name: "execute",
- outputs: [],
- stateMutability: "nonpayable",
- type: "function"
- }
- ],
- args: [to, value, data]
- })
-
- return callData
- }
\ No newline at end of file
diff --git a/advanced/wallets/react-wallet-v2/src/utils/SmartAccountUtils.ts b/advanced/wallets/react-wallet-v2/src/utils/SmartAccountUtils.ts
new file mode 100644
index 000000000..9378e4ca8
--- /dev/null
+++ b/advanced/wallets/react-wallet-v2/src/utils/SmartAccountUtils.ts
@@ -0,0 +1,132 @@
+import { Hex, createPublicClient, encodeFunctionData, http } from "viem"
+import { goerli, polygonMumbai, sepolia } from 'viem/chains'
+
+const apiKey = process.env.NEXT_PUBLIC_PIMLICO_KEY
+
+// Types
+export const allowedChains = [sepolia, polygonMumbai, goerli] as const
+export type Chain = (typeof allowedChains)[number]
+export type UrlConfig = {
+ chain: Chain
+}
+
+// Entrypoints [I think this is constant but JIC]
+export const ENTRYPOINT_ADDRESSES: Record = {
+ Sepolia: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789',
+ 'Polygon Mumbai': '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789',
+ 'Goerli': '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789'
+}
+
+// Paymasters
+// https://docs.pimlico.io/paymaster/erc20-paymaster/contract-addresses
+export const PAYMASTER_ADDRESSES: Record = {
+ Sepolia: '0x0000000000325602a77416A16136FDafd04b299f',
+ 'Polygon Mumbai': '0x000000000009B901DeC1aaB9389285965F49D387',
+ Goerli: '0xEc43912D8C772A0Eba5a27ea5804Ba14ab502009'
+}
+
+// USDC
+export const USDC_ADDRESSES: Record = {
+ Sepolia: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
+ 'Polygon Mumbai': '0x9999f7fea5938fd3b1e26a12c3f2fb024e194f97',
+ Goerli: '0x07865c6e87b9f70255377e024ace6630c1eaa37f'
+}
+
+// RPC URLs
+export const RPC_URLS: Record = {
+ Sepolia: 'https://rpc.ankr.com/eth_sepolia',
+ 'Polygon Mumbai': 'https://mumbai.rpc.thirdweb.com',
+ Goerli: 'https://ethereum-goerli.publicnode.com'
+}
+
+// Pimlico RPC names
+export const PIMLICO_NETWORK_NAMES: Record = {
+ Sepolia: 'sepolia',
+ 'Polygon Mumbai': 'mumbai',
+ Goerli: 'goerli'
+}
+
+export const VITALIK_ADDRESS = '0xd8da6bf26964af9d7eed9e03e53415d37aa96045' as Hex
+
+export const publicRPCUrl = ({ chain }: UrlConfig) => RPC_URLS[chain.name]
+export const paymasterUrl = ({ chain }: UrlConfig) =>
+ `https://api.pimlico.io/v2/${PIMLICO_NETWORK_NAMES[chain.name]}/rpc?apikey=${apiKey}`
+export const bundlerUrl = ({ chain }: UrlConfig) =>
+ `https://api.pimlico.io/v1/${PIMLICO_NETWORK_NAMES[chain.name]}/rpc?apikey=${apiKey}`
+
+
+const publicClient = ({ chain }: UrlConfig) => createPublicClient({
+ transport: http(publicRPCUrl({ chain })),
+})
+
+export const approvePaymasterUSDCSpend = (chain: Chain) => {
+ // Approve paymaster to spend USDC on our behalf
+ const approveData = approveUSDCSpendCallData({
+ to: PAYMASTER_ADDRESSES[chain.name],
+ amount: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn
+ })
+
+ // GENERATE THE CALLDATA FOR USEROP TO SEND TO THE SMART ACCOUNT
+ const dest = USDC_ADDRESSES[chain.name] // Execute tx in USDC contract
+ const value = 0n
+ const data = approveData // Execute approve call
+
+ return generateUserOperationExecuteCallData({ dest, value, data })
+}
+
+export const approveUSDCSpendCallData = ({ to, amount }: { to: Hex, amount: bigint }) => {
+ return encodeFunctionData({
+ abi: [
+ {
+ inputs: [
+ { name: "_spender", type: "address" },
+ { name: "_value", type: "uint256" }
+ ],
+ name: "approve",
+ outputs: [{ name: "", type: "bool" }],
+ payable: false,
+ stateMutability: "nonpayable",
+ type: "function"
+ }
+ ],
+ args: [to, amount]
+ })
+}
+
+// Wraps the call data in the execute function in order to send via UserOperation
+export const generateUserOperationExecuteCallData = ({ dest, data, value }: { dest: Hex, data: Hex, value: bigint }) => {
+ return encodeFunctionData({
+ abi: [
+ {
+ inputs: [
+ { name: "dest", type: "address" },
+ { name: "value", type: "uint256" },
+ { name: "func", type: "bytes" }
+ ],
+ name: "execute",
+ outputs: [],
+ stateMutability: "nonpayable",
+ type: "function"
+ }
+ ],
+ args: [dest, value, data]
+ })
+}
+
+
+export const getUSDCBalance = async ({ address, chain }: { address: Hex, chain: Chain }) => {
+ return publicClient({ chain }).readContract({
+ abi: [
+ {
+ inputs: [{ name: "_owner", type: "address" }],
+ name: "balanceOf",
+ outputs: [{ name: "balance", type: "uint256" }],
+ type: "function",
+ stateMutability: "view"
+ }
+ ],
+ address: USDC_ADDRESSES[chain.name],
+ functionName: "balanceOf",
+ args: [address]
+ })
+}
\ No newline at end of file
diff --git a/advanced/wallets/react-wallet-v2/src/utils/WalletConnectUtil.ts b/advanced/wallets/react-wallet-v2/src/utils/WalletConnectUtil.ts
index 24f1105f9..b2fea49b5 100644
--- a/advanced/wallets/react-wallet-v2/src/utils/WalletConnectUtil.ts
+++ b/advanced/wallets/react-wallet-v2/src/utils/WalletConnectUtil.ts
@@ -40,11 +40,11 @@ export async function updateSignClientChainId(chainId: string, address: string)
[namespace]: {
...session.namespaces[namespace],
chains: [
- ...new Set([chainId].concat(Array.from(session.namespaces[namespace].chains || [])))
+ ...new Set([chainId].concat(Array.from(session?.namespaces?.[namespace]?.chains || [])))
],
accounts: [
...new Set(
- [`${chainId}:${address}`].concat(Array.from(session.namespaces[namespace].accounts))
+ [`${chainId}:${address}`].concat(Array.from(session?.namespaces?.[namespace]?.accounts || []))
)
]
}
diff --git a/advanced/wallets/react-wallet-v2/src/views/SessionProposalModal.tsx b/advanced/wallets/react-wallet-v2/src/views/SessionProposalModal.tsx
index ec12fb6ed..99d017ef7 100644
--- a/advanced/wallets/react-wallet-v2/src/views/SessionProposalModal.tsx
+++ b/advanced/wallets/react-wallet-v2/src/views/SessionProposalModal.tsx
@@ -36,6 +36,7 @@ import { Hex } from 'viem'
import ChainSmartAddressMini from '@/components/ChainSmartAddressMini'
import { useSnapshot } from 'valtio'
import SettingsStore from '@/store/SettingsStore'
+import { allowedChains } from '@/utils/SmartAccountUtils'
const StyledText = styled(Text, {
fontWeight: 400
@@ -236,9 +237,10 @@ export default function SessionProposalModal() {
// TODO: improve for multi network
console.log('checking if SA is deployed', eip155Wallets[eip155Addresses[0]])
+ const chainId = namespaces['eip155'].chains?.[0]
const smartAccountClient = new SmartAccountLib({
privateKey: eip155Wallets[eip155Addresses[0]].getPrivateKey() as Hex,
- chain: 'goerli',
+ chain: allowedChains.find(chain => chain.id.toString() === chainId)!,
sponsored: smartAccountSponsorshipEnabled,
})
const isDeployed = await smartAccountClient.checkIfSmartAccountDeployed()