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 2066ed9
Show file tree
Hide file tree
Showing 38 changed files with 237 additions and 54 deletions.
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,20 @@
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 { FormEventHandler, useEffect, useState } from 'react';
import Jazzicon, { jsNumberForAddress } from 'react-jazzicon';
import truncateEthAddress from 'truncate-eth-address';
import { Layout } from './components';
import { ContractType } from './constants';
import { useContract } from './hooks/useContract';
import Jazzicon, { jsNumberForAddress } from 'react-jazzicon';
import truncateEthAddress from 'truncate-eth-address';
import { FormEventHandler, useEffect, useState } from 'react';
import { UserOperation, getSenderAddress, getUserOperationHash } from 'permissionless';
import { bundlerClient, getInitCode, paymasterClient, publicClient } from './sdk';
import { ENTRY_POINT_ADDRESS } from './constants/erc4337';
import { Hex, encodeFunctionData } from 'viem';
import { goerli } from 'viem/chains';
import { signMessage } from '@wagmi/core';

createWeb3Modal({
defaultChain: L2_CHAIN_CONFIG,
Expand All @@ -22,14 +28,92 @@ function App() {
const { address } = useAccount();
const [isTransfering, setTransfering] = useState(false);
const [transferAddress, setTransferAddress] = useState('');
const { data: signMessageData, error, isLoading, signMessageAsync, variables } = 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: UserOperation = {
sender: senderAddress,
nonce: 2n,
initCode: '0x',
callData,
maxFeePerGas: gasPrice.fast.maxFeePerGas,
maxPriorityFeePerGas: gasPrice.fast.maxPriorityFeePerGas,
// dummy signature
signature:
'0xa15569dd8f8324dbeabf8073fdec36d4b754f53ce5901e283c6de79af177dc94557fa3c9922cd7af2a96ca94402d35c39f266925ee6407aeb32b31d76978d4ba1c' as Hex
};

const sponsorUserOperationResult = await paymasterClient.sponsorUserOperation({
userOperation,
entryPoint: ENTRY_POINT_ADDRESS
});

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
})
}
});

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 +153,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]
})
]);
};

This file was deleted.

0 comments on commit 2066ed9

Please sign in to comment.