Last active
October 13, 2023 05:28
-
-
Save TABASCOatw/61be56f0c2346f757692a1b33248bda9 to your computer and use it in GitHub Desktop.
AA demo leveraging Particle WaaS for account management and using Pimlico as the bundler + paymaster
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useState, useEffect } from 'react'; | |
import axios from 'axios'; | |
import { ethers } from 'ethers'; | |
import { ParticleNetwork } from '@particle-network/auth'; | |
import { ParticleProvider } from '@particle-network/provider'; | |
import { EthereumGoerli } from '@particle-network/chains'; | |
import { createPublicClient, createClient, http } from 'viem'; | |
import { pimlicoBundlerActions, pimlicoPaymasterActions } from 'permissionless/actions/pimlico'; | |
import { getAccountNonce, getUserOperationHash, bundlerActions } from 'permissionless'; | |
import { goerli } from 'viem/chains'; | |
import './App.css'; | |
const App = () => { | |
const [userInfo, setUserInfo] = useState(null); | |
const [ethBalance, setEthBalance] = useState(null); | |
const [smartAccount, setSmartAccount] = useState(null); | |
const particle = new ParticleNetwork({ | |
projectId: process.env.REACT_APP_PROJECT_ID, | |
clientKey: process.env.REACT_APP_CLIENT_KEY, | |
appId: process.env.REACT_APP_APP_ID, | |
chainName: EthereumGoerli.name, | |
chainId: EthereumGoerli.id, | |
wallet: { displayWalletEntry: true, uiMode: 'dark' }, | |
}); | |
particle.setERC4337(true); | |
const provider = new ethers.providers.Web3Provider(new ParticleProvider(particle.auth)) | |
useEffect(() => { | |
const fetchEthBalance = async () => { | |
const signer = provider.getSigner(); | |
const signerAddress = await signer.getAddress(); | |
const response = await axios.post('https://rpc.particle.network/evm-chain', { | |
"jsonrpc": "2.0", | |
"id": "1", | |
"chainId": EthereumGoerli.id, | |
"method": "particle_aa_getSmartAccount", | |
"params": [[signerAddress]] | |
}, { | |
auth: { | |
username: process.env.REACT_APP_PROJECT_ID, | |
password: process.env.REACT_APP_SERVER_KEY, | |
} | |
}); | |
const smartAccounts = response.data.result; | |
const smartAccount = smartAccounts[0].smartAccountAddress; | |
setSmartAccount(smartAccount); | |
const balance = await provider.getBalance(smartAccount); | |
setEthBalance(ethers.utils.formatEther(balance)); | |
}; | |
fetchEthBalance(); | |
}, [userInfo]); | |
const handleLogin = async (preferredAuthType) => { | |
const user = await particle.auth.login({ preferredAuthType }); | |
setUserInfo(user); | |
}; | |
const executeUserOp = async () => { | |
try { | |
const signer = provider.getSigner(); | |
const publicClient = createPublicClient({ | |
transport: http("https://rpc.ankr.com/eth_goerli"), | |
chain: goerli | |
}); | |
const apiKey = process.env.REACT_APP_PIMLICO_KEY; | |
const bundlerClient = createClient({ | |
transport: http(`https://api.pimlico.io/v1/goerli/rpc?apikey=${apiKey}`), | |
chain: goerli | |
}).extend(bundlerActions).extend(pimlicoBundlerActions); | |
const paymasterClient = createClient({ | |
transport: http(`https://api.pimlico.io/v2/goerli/rpc?apikey=${apiKey}`), | |
chain: goerli | |
}).extend(pimlicoPaymasterActions); | |
const entryPoint = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"; | |
const nonce = await getAccountNonce(publicClient, { | |
address: smartAccount, | |
entryPoint | |
}); | |
const accountABI = ["function executeCall(address to, uint256 value, bytes data)"]; | |
const account = new ethers.utils.Interface(accountABI); | |
const callData = account.encodeFunctionData("executeCall", ["0x000000000000000000000000000000000000dEaD", ethers.utils.parseUnits('0.001', 'ether'), "0x"]); | |
const gasPrice = await bundlerClient.getUserOperationGasPrice(); | |
const userOperation = { | |
sender: smartAccount, | |
nonce: nonce, | |
initCode: "0x", | |
callData, | |
maxFeePerGas: gasPrice.fast.maxFeePerGas, | |
maxPriorityFeePerGas: gasPrice.fast.maxPriorityFeePerGas, | |
signature: "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c" // Placeholder for the signature | |
}; | |
const sponsorUserOperationResult = await paymasterClient.sponsorUserOperation({ | |
userOperation, | |
entryPoint | |
}); | |
const sponsoredUserOperation = { | |
...userOperation, | |
preVerificationGas: sponsorUserOperationResult.preVerificationGas, | |
verificationGasLimit: sponsorUserOperationResult.verificationGasLimit, | |
callGasLimit: sponsorUserOperationResult.callGasLimit, | |
paymasterAndData: sponsorUserOperationResult.paymasterAndData | |
}; | |
const userOperationHash = getUserOperationHash({ | |
userOperation: sponsoredUserOperation, | |
chainId: EthereumGoerli.id, | |
entryPoint | |
}); | |
const signature = await signer.signMessage(ethers.utils.arrayify(userOperationHash)); | |
sponsoredUserOperation.signature = signature; | |
const userOperationHashResult = await bundlerClient.sendUserOperation({ | |
userOperation: sponsoredUserOperation, | |
entryPoint | |
}); | |
console.log(userOperationHashResult); | |
} catch (error) { | |
console.error("An error occurred:", error); | |
} | |
}; | |
return ( | |
<div className="App"> | |
{userInfo ? ( | |
<div> | |
<h2>{userInfo.name}</h2> | |
<p>{ethBalance} ETH</p> | |
<button onClick={executeUserOp}>Execute User Operation</button> | |
</div> | |
) : ( | |
<div> | |
<button onClick={() => handleLogin('google')}>Login with Google</button> | |
<button onClick={() => handleLogin('twitter')}>Login with Twitter</button> | |
</div> | |
)} | |
</div> | |
); | |
}; | |
export default App; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment