Skip to content

Commit

Permalink
feat: add permissionless
Browse files Browse the repository at this point in the history
  • Loading branch information
danielsimao committed Oct 31, 2023
1 parent 03c618b commit 8112ce5
Show file tree
Hide file tree
Showing 39 changed files with 248 additions and 55 deletions.
2 changes: 2 additions & 0 deletions examples/account-abstraction/permissionless/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
VITE_WALLET_CONNECT_PROJECT_ID=
VITE_PIMLICO_API_KEY=
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,15 @@
"@web3auth/openlogin-adapter": "^7.0.4",
"@web3modal/wagmi": "^3.1.0",
"big.js": "^6.2.1",
"permissionless": "^0.0.9",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-jazzicon": "^1.0.4",
"styled-components": "^6.0.8",
"truncate-eth-address": "^1.0.2",
"viem": "^1.10.9",
"vite-plugin-node-polyfills": "^0.15.0",
"wagmi": "^1.4.1"
"wagmi": "^1.4.5"
},
"devDependencies": {
"@typechain/ethers-v6": "^0.5.0",
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import { createWeb3Modal } from '@web3modal/wagmi/react';
import { useAccount, useMutation, useQuery } from 'wagmi';
import { useAccount, useMutation, 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 { 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 { Layout } from './components';
import { ContractType } from './constants';
import { ENTRY_POINT_ADDRESS } from './constants/erc4337';
import { useContract } from './hooks/useContract';
import Jazzicon, { jsNumberForAddress } from 'react-jazzicon';
import truncateEthAddress from 'truncate-eth-address';
import { FormEventHandler, useEffect, useState } from 'react';
import { bundlerClient, getInitCode, publicClient } from './sdk';

createWeb3Modal({
defaultChain: L2_CHAIN_CONFIG,
Expand All @@ -22,14 +27,102 @@ function App() {
const { address } = useAccount();
const [isTransfering, setTransfering] = useState(false);
const [transferAddress, setTransferAddress] = useState('');
const { signMessageAsync } = useSignMessage();

const flagOwner = useQuery<string, Error, string>(['owner'], {
queryFn: () => read.flagHolder() as Promise<string>,
refetchInterval: 10000
refetchInterval: 5000
});

const capturaFlagMutation = useMutation({
mutationFn: () => write.captureFlag(),
// mutationFn: () => write.captureFlag(),
mutationFn: async () => {
if (!address) return;

const initCode = getInitCode(address);

const senderAddress = await getSenderAddress(publicClient, {
initCode,
entryPoint: ENTRY_POINT_ADDRESS
});

const callData = encodeFunctionData({
abi: [
{
inputs: [],
name: 'captureFlag',
outputs: [{ type: 'address', name: '', internalType: 'address' }],
stateMutability: 'nonpayable',
type: 'function'
}
]
});

const gasPrice = await bundlerClient.getUserOperationGasPrice();

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',
callData,
maxFeePerGas: gasPrice.fast.maxFeePerGas,
maxPriorityFeePerGas: gasPrice.fast.maxPriorityFeePerGas,
// dummy signature
signature:
'0xa15569dd8f8324dbeabf8073fdec36d4b754f53ce5901e283c6de79af177dc94557fa3c9922cd7af2a96ca94402d35c39f266925ee6407aeb32b31d76978d4ba1c' as Hex
};

// NOTE: code for sponsoring the userOP
// const sponsorUserOperationResult = await paymasterClient.sponsorUserOperation({
// userOperation,
// entryPoint: ENTRY_POINT_ADDRESS
// });

// const sponsoredUserOp: UserOperation = {
// ...userOperation,
// ...sponsorUserOperationResult
// };

const finalUserOperation: UserOperation = {
...userOperation,
callGasLimit: 60000n,
verificationGasLimit: 60000n,
preVerificationGas: 60000n,
paymasterAndData: '0x'
};

const signature = await signMessageAsync({
message: {
/** Raw data representation of the message. */
raw: getUserOperationHash({
userOperation: finalUserOperation,
chainId: goerli.id,
entryPoint: ENTRY_POINT_ADDRESS
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as unknown as any
});

finalUserOperation.signature = signature;

// SUBMIT THE USER OPERATION TO BE BUNDLED
const userOperationHash = await bundlerClient.sendUserOperation({
userOperation: finalUserOperation,
entryPoint: ENTRY_POINT_ADDRESS
});

console.log('Received User Operation hash:', userOperationHash);

// let's also wait for the userOperation to be included, by continually querying for the receipts
console.log('Querying for receipts...');
const receipt = await bundlerClient.waitForUserOperationReceipt({ hash: userOperationHash });
const txHash = receipt.receipt.transactionHash;

console.log(`UserOperation included: https://goerli.lineascan.build/tx/${txHash}`);
},
onSuccess: () => flagOwner.refetch()
});

Expand Down Expand Up @@ -69,7 +162,7 @@ function App() {
<Flex direction='column' alignItems='center' gap='spacing1'>
<P>Current Holder</P>
<Flex elementType='span' gap='spacing2' alignItems='center'>
<Jazzicon diameter={40} seed={jsNumberForAddress(flagOwner.data || '0x0') || '0'} />
<Jazzicon diameter={40} seed={jsNumberForAddress(flagOwner.data || '0x0')} />
<Span weight='bold' style={{ color: 'inherit' }} size='xl' color='tertiary'>
{isCurrentOwner ? 'YOU' : truncateEthAddress(flagOwner.data || '0x0')}
</Span>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { CTA, Flex, Span } from '@interlay/ui';
import { useWeb3Modal } from '@web3modal/wagmi/react';
import { getSenderAddress } from 'permissionless';
import Jazzicon, { jsNumberForAddress } from 'react-jazzicon';
import truncateEthAddress from 'truncate-eth-address';
import { concat, encodeFunctionData } from 'viem';
import { useAccount, useBalance } from 'wagmi';
import { ENTRY_POINT_ADDRESS, SIMPLE_ACCOUNT_FACTORY_ADDRESS } from '../../constants/erc4337';
import { CTAWrapper, StyledHeader } from './Layout.styles';
import { publicClient } from '../../sdk';
import { useState } from 'react';

const getInitCode = (ownerAddress: `0x${string}`) => {
return concat([
SIMPLE_ACCOUNT_FACTORY_ADDRESS,
encodeFunctionData({
abi: [
{
inputs: [
{ name: 'owner', type: 'address' },
{ name: 'salt', type: 'uint256' }
],
name: 'createAccount',
outputs: [{ name: 'ret', type: 'address' }],
stateMutability: 'nonpayable',
type: 'function'
}
],
args: [ownerAddress, 0n]
})
]);
};

const Header = () => {
const { open } = useWeb3Modal();

const [smartContractAddress, setSmartContractAddress] = useState('');

const { address, isConnecting } = useAccount({
onConnect: async ({ address }) => {
if (!address) return;
const senderAddress = await getSenderAddress(publicClient, {
initCode: getInitCode(address),
entryPoint: ENTRY_POINT_ADDRESS
});

setSmartContractAddress(senderAddress);
}
});
const { data } = useBalance({ address });

return (
<StyledHeader elementType='header' alignItems='center' justifyContent='space-between'>
<a href='/' aria-label='navigate to home page'>
<img
src='https://uploads-ssl.webflow.com/64e85c2f3609488b3ed725f4/64ede4ad095a0a3801df095f_BobLogo.svg'
width='137'
alt='logo'
/>
</a>
<CTAWrapper>
<Span>{smartContractAddress}</Span>
<Span>Balance: {data?.value.toString()}</Span>
<CTA disabled={isConnecting} size='small' onClick={() => open()}>
{address ? (
<Flex elementType='span' gap='spacing2'>
<Jazzicon diameter={20} seed={jsNumberForAddress(address)} />
<Span style={{ color: 'inherit' }} size='s' color='tertiary'>
{truncateEthAddress(address)}
</Span>
</Flex>
) : (
'Connect Wallet'
)}
</CTA>
</CTAWrapper>
</StyledHeader>
);
};

export { Header };
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export default function Web3AuthConnectorInstance(chains: Chain[]) {
const privateKeyProvider = new EthereumPrivateKeyProvider({
config: { chainConfig }
});

const openloginAdapterInstance = new OpenloginAdapter({
privateKeyProvider,
adapterSettings: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const ENTRY_POINT_ADDRESS = '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789';
export const SIMPLE_ACCOUNT_FACTORY_ADDRESS = '0x9406Cc6185a346906296840746125a0E44976454';
48 changes: 48 additions & 0 deletions examples/account-abstraction/permissionless/src/sdk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
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 { SIMPLE_ACCOUNT_FACTORY_ADDRESS } from './constants/erc4337';

const chain = goerli;
const apiKey = import.meta.env.VITE_PIMLICO_API_KEY; // REPLACE THIS

// CREATE THE CLIENTS
export const publicClient = createPublicClient({
transport: http(chain.rpcUrls.default.http[0]),
chain
});

export const bundlerClient = createClient({
transport: http(`https://api.pimlico.io/v1/${chain.name.toLowerCase()}/rpc?apikey=${apiKey}`),
chain
})
.extend(bundlerActions)
.extend(pimlicoBundlerActions);

export const paymasterClient = createClient({
// ⚠️ using v2 of the API ⚠️
transport: http(`https://api.pimlico.io/v2/${chain.name.toLowerCase()}/rpc?apikey=${apiKey}`),
chain
}).extend(pimlicoPaymasterActions);

export const getInitCode = (ownerAddress: `0x${string}`) => {
return concat([
SIMPLE_ACCOUNT_FACTORY_ADDRESS,
encodeFunctionData({
abi: [
{
inputs: [
{ name: 'owner', type: 'address' },
{ name: 'salt', type: 'uint256' }
],
name: 'createAccount',
outputs: [{ name: 'ret', type: 'address' }],
stateMutability: 'nonpayable',
type: 'function'
}
],
args: [ownerAddress, 0n]
})
]);
};
1 change: 0 additions & 1 deletion examples/account-abstraction/web3auth/.env.example

This file was deleted.

This file was deleted.

0 comments on commit 8112ce5

Please sign in to comment.