From 97a8230f5cc26e23712cfaa4c0af7c00d1151f41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rui=20Sim=C3=A3o?= Date: Wed, 8 Nov 2023 11:23:27 +0000 Subject: [PATCH] feat: add permissionaless --- .../permissionless/src/App.tsx | 81 +++++++++++-------- .../connectors/Web3AuthConnectorInstance.tsx | 14 ++-- .../src/connectors/wagmi-connectors.ts | 4 +- .../permissionless/src/constants/contracts.ts | 3 +- .../src/contracts/abi/CTF.abi.ts | 26 +++--- .../permissionless/src/sdk.ts | 8 +- 6 files changed, 74 insertions(+), 62 deletions(-) diff --git a/examples/account-abstraction/permissionless/src/App.tsx b/examples/account-abstraction/permissionless/src/App.tsx index 27b8780b..df672730 100644 --- a/examples/account-abstraction/permissionless/src/App.tsx +++ b/examples/account-abstraction/permissionless/src/App.tsx @@ -1,19 +1,19 @@ import { createWeb3Modal } from '@web3modal/wagmi/react'; -import { useAccount, useMutation, useQuery, useSignMessage } from 'wagmi'; +import { useAccount, useMutation, usePublicClient, useQuery, useSignMessage } from 'wagmi'; import { L2_CHAIN_CONFIG, L2_PROJECT_ID, config } from './connectors/wagmi-connectors'; import { CTA, Card, Flex, H1, Input, P, Span } from '@interlay/ui'; -import { UserOperation, getSenderAddress, getUserOperationHash } from 'permissionless'; +import { UserOperation, getAccountNonce, getSenderAddress, getUserOperationHash } from 'permissionless'; import { FormEventHandler, useEffect, useState } from 'react'; import Jazzicon, { jsNumberForAddress } from 'react-jazzicon'; import truncateEthAddress from 'truncate-eth-address'; import { Hex, encodeFunctionData } from 'viem'; -import { goerli } from 'viem/chains'; +import { optimismGoerli } from 'viem/chains'; import { Layout } from './components'; -import { ContractType } from './constants'; +import { ContractType, contracts } from './constants'; import { ENTRY_POINT_ADDRESS } from './constants/erc4337'; import { useContract } from './hooks/useContract'; -import { bundlerClient, getInitCode, publicClient } from './sdk'; +import { bundlerClient, getInitCode, paymasterClient, publicClient } from './sdk'; createWeb3Modal({ defaultChain: L2_CHAIN_CONFIG, @@ -29,6 +29,8 @@ function App() { const [transferAddress, setTransferAddress] = useState(''); const { signMessageAsync } = useSignMessage(); + const { getBytecode } = usePublicClient(); + const flagOwner = useQuery(['owner'], { queryFn: () => read.flagHolder() as Promise, refetchInterval: 5000 @@ -41,32 +43,49 @@ function App() { const initCode = getInitCode(address); - const senderAddress = await getSenderAddress(publicClient, { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const senderAddress = await getSenderAddress(publicClient as any, { initCode, entryPoint: ENTRY_POINT_ADDRESS }); + const call = encodeFunctionData({ + abi: contracts[ContractType.CTF].abi, + functionName: 'captureFlag', + args: [] + }); + const callData = encodeFunctionData({ abi: [ { - inputs: [], - name: 'captureFlag', - outputs: [{ type: 'address', name: '', internalType: 'address' }], + inputs: [ + { name: 'dest', type: 'address' }, + { name: 'value', type: 'uint256' }, + { name: 'func', type: 'bytes' } + ], + name: 'execute', + outputs: [], stateMutability: 'nonpayable', type: 'function' } - ] + ], + args: [contracts[ContractType.CTF].address, 0n, call] }); const gasPrice = await bundlerClient.getUserOperationGasPrice(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const nonce = await getAccountNonce(publicClient as any, { + sender: senderAddress, + entryPoint: ENTRY_POINT_ADDRESS + }); + + const byteCode = await getBytecode({ address: senderAddress }); + const userOperation = { sender: senderAddress, - nonce: 2n, - // NOTE: should pass this if the account was not created yet - // initCode: initCode - // NOT putting "0x" because account was already created - initCode: '0x', + nonce, + initCode: byteCode?.toString() ? '0x' : initCode, callData, maxFeePerGas: gasPrice.fast.maxFeePerGas, maxPriorityFeePerGas: gasPrice.fast.maxPriorityFeePerGas, @@ -76,41 +95,33 @@ function App() { }; // NOTE: code for sponsoring the userOP - // const sponsorUserOperationResult = await paymasterClient.sponsorUserOperation({ - // userOperation, - // entryPoint: ENTRY_POINT_ADDRESS - // }); - - // const sponsoredUserOp: UserOperation = { - // ...userOperation, - // ...sponsorUserOperationResult - // }; + const sponsorUserOperationResult = await paymasterClient.sponsorUserOperation({ + userOperation, + entryPoint: ENTRY_POINT_ADDRESS + }); - const finalUserOperation: UserOperation = { + const sponsoredUserOp: UserOperation = { ...userOperation, - callGasLimit: 60000n, - verificationGasLimit: 60000n, - preVerificationGas: 60000n, - paymasterAndData: '0x' + ...sponsorUserOperationResult }; const signature = await signMessageAsync({ message: { /** Raw data representation of the message. */ raw: getUserOperationHash({ - userOperation: finalUserOperation, - chainId: goerli.id, + userOperation: sponsoredUserOp, + chainId: optimismGoerli.id, entryPoint: ENTRY_POINT_ADDRESS }) // eslint-disable-next-line @typescript-eslint/no-explicit-any } as unknown as any }); - finalUserOperation.signature = signature; + sponsoredUserOp.signature = signature; // SUBMIT THE USER OPERATION TO BE BUNDLED const userOperationHash = await bundlerClient.sendUserOperation({ - userOperation: finalUserOperation, + userOperation: sponsoredUserOp, entryPoint: ENTRY_POINT_ADDRESS }); @@ -121,7 +132,7 @@ function App() { const receipt = await bundlerClient.waitForUserOperationReceipt({ hash: userOperationHash }); const txHash = receipt.receipt.transactionHash; - console.log(`UserOperation included: https://goerli.lineascan.build/tx/${txHash}`); + console.log(`UserOperation included: https://goerli-optimism.etherscan.io/tx/${txHash}`); }, onSuccess: () => flagOwner.refetch() }); @@ -201,7 +212,7 @@ function App() { )} capturaFlagMutation.mutate()} diff --git a/examples/account-abstraction/permissionless/src/connectors/Web3AuthConnectorInstance.tsx b/examples/account-abstraction/permissionless/src/connectors/Web3AuthConnectorInstance.tsx index 9b4e504a..b7abfad6 100644 --- a/examples/account-abstraction/permissionless/src/connectors/Web3AuthConnectorInstance.tsx +++ b/examples/account-abstraction/permissionless/src/connectors/Web3AuthConnectorInstance.tsx @@ -4,7 +4,7 @@ import { Web3Auth } from '@web3auth/modal'; import { EthereumPrivateKeyProvider } from '@web3auth/ethereum-provider'; import { OpenloginAdapter } from '@web3auth/openlogin-adapter'; import { Chain } from 'wagmi'; -import { L2_BLOCK_EXPLORER, L2_RPC_URL } from '../config'; +import { optimismGoerli } from 'viem/chains'; export default function Web3AuthConnectorInstance(chains: Chain[]) { // Create Web3Auth Instance @@ -21,12 +21,12 @@ export default function Web3AuthConnectorInstance(chains: Chain[]) { const chainConfig = { chainNamespace: 'eip155', - chainId: '0x5', - rpcTarget: 'https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161', - displayName: 'Görli', - blockExplorer: 'https://goerli.etherscan.io', - ticker: 'gETH', - tickerName: 'Ethereum' + chainId: '0x1a4', + rpcTarget: optimismGoerli.rpcUrls.default.http[0], + displayName: optimismGoerli.name, + blockExplorer: optimismGoerli.blockExplorers.default.url, + ticker: optimismGoerli.nativeCurrency.symbol, + tickerName: optimismGoerli.nativeCurrency.name }; const web3AuthInstance = new Web3Auth({ diff --git a/examples/account-abstraction/permissionless/src/connectors/wagmi-connectors.ts b/examples/account-abstraction/permissionless/src/connectors/wagmi-connectors.ts index d3d3af47..cc33a661 100644 --- a/examples/account-abstraction/permissionless/src/connectors/wagmi-connectors.ts +++ b/examples/account-abstraction/permissionless/src/connectors/wagmi-connectors.ts @@ -6,7 +6,7 @@ import { L2_BLOCK_EXPLORER, L2_CHAIN_ID, L2_MULTICALL3_ADDRESS, L2_RPC_URL, L2_W import { InjectedConnector } from 'wagmi/connectors/injected'; import Web3AuthConnectorInstance from './Web3AuthConnectorInstance'; -import { goerli } from 'viem/chains'; +import { optimismGoerli } from 'viem/chains'; const L2_PROJECT_ID = import.meta.env.VITE_WALLET_CONNECT_PROJECT_ID as string; const L2_METADATA = { @@ -39,7 +39,7 @@ const L2_CHAIN_CONFIG = { } } as const satisfies Chain; -const chains = [goerli]; +const chains = [optimismGoerli]; // const chains = [L2_CHAIN_CONFIG]; const { publicClient, webSocketPublicClient } = configureChains(chains, [publicProvider()]); diff --git a/examples/account-abstraction/permissionless/src/constants/contracts.ts b/examples/account-abstraction/permissionless/src/constants/contracts.ts index 339df380..919a2f29 100644 --- a/examples/account-abstraction/permissionless/src/constants/contracts.ts +++ b/examples/account-abstraction/permissionless/src/constants/contracts.ts @@ -6,7 +6,8 @@ enum ContractType { const contracts = { [ContractType.CTF]: { - address: '0x7349289C7C4097D82d670Df784Fec290cB22CCaf', + address: '0x4478eE0Fd9F054ADBb422c55Beb21DF2bcCe71C8', + // address: '0x7349289C7C4097D82d670Df784Fec290cB22CCaf', // address: '0x587255805b6bcb4Eb34a811AaFb2aa65ef8aA72b', abi: CTFAbi } diff --git a/examples/account-abstraction/permissionless/src/contracts/abi/CTF.abi.ts b/examples/account-abstraction/permissionless/src/contracts/abi/CTF.abi.ts index 052e0f57..93f85306 100644 --- a/examples/account-abstraction/permissionless/src/contracts/abi/CTF.abi.ts +++ b/examples/account-abstraction/permissionless/src/contracts/abi/CTF.abi.ts @@ -1,24 +1,24 @@ export const CTFAbi = [ - { type: 'constructor', stateMutability: 'nonpayable', inputs: [] }, + { inputs: [], stateMutability: 'nonpayable', type: 'constructor' }, { - type: 'event', + anonymous: false, + inputs: [{ indexed: true, internalType: 'address', name: 'flagHolder', type: 'address' }], name: 'FlagCaptured', - inputs: [{ type: 'address', name: 'flagHolder', internalType: 'address', indexed: true }], - anonymous: false + type: 'event' }, - { type: 'function', stateMutability: 'nonpayable', outputs: [], name: 'captureFlag', inputs: [] }, + { inputs: [], name: 'captureFlag', outputs: [], stateMutability: 'nonpayable', type: 'function' }, { - type: 'function', - stateMutability: 'view', - outputs: [{ type: 'address', name: '', internalType: 'address' }], + inputs: [], name: 'flagHolder', - inputs: [] + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function' }, { - type: 'function', - stateMutability: 'nonpayable', - outputs: [], + inputs: [{ internalType: 'address', name: 'newFlagHolder', type: 'address' }], name: 'transferOwnership', - inputs: [{ type: 'address', name: 'newFlagHolder', internalType: 'address' }] + outputs: [], + stateMutability: 'nonpayable', + type: 'function' } ]; diff --git a/examples/account-abstraction/permissionless/src/sdk.ts b/examples/account-abstraction/permissionless/src/sdk.ts index 575ef266..dcd1bae1 100644 --- a/examples/account-abstraction/permissionless/src/sdk.ts +++ b/examples/account-abstraction/permissionless/src/sdk.ts @@ -1,10 +1,10 @@ import { bundlerActions } from 'permissionless'; import { pimlicoBundlerActions, pimlicoPaymasterActions } from 'permissionless/actions/pimlico'; import { concat, createClient, createPublicClient, encodeFunctionData, http } from 'viem'; -import { goerli } from 'viem/chains'; +import { optimismGoerli } from 'viem/chains'; import { SIMPLE_ACCOUNT_FACTORY_ADDRESS } from './constants/erc4337'; -const chain = goerli; +const chain = optimismGoerli; const apiKey = import.meta.env.VITE_PIMLICO_API_KEY; // REPLACE THIS // CREATE THE CLIENTS @@ -14,7 +14,7 @@ export const publicClient = createPublicClient({ }); export const bundlerClient = createClient({ - transport: http(`https://api.pimlico.io/v1/${chain.name.toLowerCase()}/rpc?apikey=${apiKey}`), + transport: http(`https://api.pimlico.io/v1/${'optimism-goerli'}/rpc?apikey=${apiKey}`), chain }) .extend(bundlerActions) @@ -22,7 +22,7 @@ export const bundlerClient = createClient({ export const paymasterClient = createClient({ // ⚠️ using v2 of the API ⚠️ - transport: http(`https://api.pimlico.io/v2/${chain.name.toLowerCase()}/rpc?apikey=${apiKey}`), + transport: http(`https://api.pimlico.io/v2/${'optimism-goerli'}/rpc?apikey=${apiKey}`), chain }).extend(pimlicoPaymasterActions);