Skip to content

Instantly share code, notes, and snippets.

@TABASCOatw
Last active October 13, 2023 05:28
Show Gist options
  • Save TABASCOatw/61be56f0c2346f757692a1b33248bda9 to your computer and use it in GitHub Desktop.
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
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