Skip to content

Commit

Permalink
feat: add webauthn and 7702 guides
Browse files Browse the repository at this point in the history
  • Loading branch information
kopy-kat committed Jan 15, 2025
1 parent 9dc80af commit 6fbc700
Show file tree
Hide file tree
Showing 5 changed files with 466 additions and 1 deletion.
3 changes: 2 additions & 1 deletion pages/module-sdk/account-guides/_meta.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"safe": "Safe",
"kernel": "Kernel"
"kernel": "Kernel",
"eip-7702": "EIP-7702"
}
234 changes: 234 additions & 0 deletions pages/module-sdk/account-guides/eip-7702.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
import { Steps } from 'nextra/components'

# How to use the Module SDK with EIP-7702

[EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) is an improvement to the Ethereum protocol that allows an EOA to delegate to a smart contract and be used as a smart contract account. In thi guide, we will walk you through using EIP-7702 in combination with the Safe and the Module SDK.

You can view the full source code in the [module sdk demos repository](https://github.com/rhinestonewtf/module-sdk-demos).

<Steps>

### Install the packages

For this guide, we use the latest version of module sdk, permissionless ^0.2 and viem ^2.21.

```sh npm2yarn
npm i viem @rhinestone/module-sdk permissionless
```

### Import the required functions and constants

```typescript copy
import { createSmartAccountClient } from 'permissionless'
import { toSafeSmartAccount } from 'permissionless/accounts'
import { createPublicClient, http, encodePacked } from 'viem'
import { erc7579Actions } from 'permissionless/actions/erc7579'
import { createPimlicoClient } from 'permissionless/clients/pimlico'
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
import {
createPaymasterClient,
entryPoint07Address,
} from 'viem/account-abstraction'
import {
getSocialRecoveryValidator,
RHINESTONE_ATTESTER_ADDRESS,
getOwnableValidator,
} from '@rhinestone/module-sdk'
```

### Create the clients

Create the smart account client, the bundler client and the paymaster client. You will need to add your own urls here.

```typescript copy
const publicClient = createPublicClient({
transport: http(rpcUrl),
chain: chain,
})

const pimlicoClient = createPimlicoClient({
transport: http(bundlerUrl),
entryPoint: {
address: entryPoint07Address,
version: '0.7',
},
})

const paymasterClient = createPaymasterClient({
transport: http(paymasterUrl),
})
```

### Create the signer

The Safe account will need to have a signer to sign user operations. In permissionless.js, the default Safe account validates ECDSA signatures.

Note that we currently still need a Safe owner that is different to the Safe (in this case the EOA) but this is likely to change in the future.

For example, to create a signer based on a private key:

```typescript copy
const owner = privateKeyToAccount(generatePrivateKey())
```

### Create the EOA to delegate from

Create the EOA that you want to transform into a smart account. Note, that this EOA can already exist.

```typescript copy
const eoa = privateKeyToAccount(generatePrivateKey())
```

### Create the validator to use

Next, we need to create a validator to use on the account. This could be an EOA based one, Webauthn or any other validator.

```typescript copy
const ownableValidator = getOwnableValidator({
owners: [owner.address],
threshold: 1,
});
```

### Delegate the EOA to the Safe

To delegate to the Safe we need to do the following things:

1. Create a sponsor account (optional): this allows for the sponsor to pay the gas and not the user
2. Sign the EIP-7702 authorization to actually delegate the EOA to the Safe
3. Setup the Safe with the EOA as the owner and the validator as the Ownable Validator

Note: currently, this setup could be frontrun so that someone else initializes the users account with different owners or modules. For this reason, you shouldn't use this in production but the Safe team is working on removing this issue before EIP-7702 reaches mainnets.

```typescript copy
const sponsorAccount = privateKeyToAccount(sponsorPrivateKey);

const authorization = await signAuthorization(publicClient, {
account: account,
contractAddress: "0x29fcB43b46531BcA003ddC8FCB67FFE91900C762",
delegate: sponsorAccount,
});

const txHash = await writeContract(publicClient, {
address: account.address,
abi: parseAbi([
"function setup(address[] calldata _owners,uint256 _threshold,address to,bytes calldata data,address fallbackHandler,address paymentToken,uint256 payment, address paymentReceiver) external",
]),
functionName: "setup",
args: [
[safeOwner.address],
BigInt(1),
"0x7579011aB74c46090561ea277Ba79D510c6C00ff",
encodeFunctionData({
abi: parseAbi([
"struct ModuleInit {address module;bytes initData;}",
"function addSafe7579(address safe7579,ModuleInit[] calldata validators,ModuleInit[] calldata executors,ModuleInit[] calldata fallbacks, ModuleInit[] calldata hooks,address[] calldata attesters,uint8 threshold) external",
]),
functionName: "addSafe7579",
args: [
"0x7579EE8307284F293B1927136486880611F20002",
[
{
module: ownableValidator.address,
initData: ownableValidator.initData,
},
],
[],
[],
[],
[
RHINESTONE_ATTESTER_ADDRESS, // Rhinestone Attester
],
1,
],
}),
"0x7579EE8307284F293B1927136486880611F20002",
zeroAddress,
BigInt(0),
zeroAddress,
],
account: sponsorAccount,
authorizationList: [authorization],
});

await publicClient.waitForTransactionReceipt({
hash: txHash,
});
```


### Create the Safe account client

Create the Safe account object using the signer. Note that you should only use the `MockAttester` on testnets.

```typescript copy
const safeAccount = await toSafeSmartAccount({
address: eoa.address,
client: publicClient,
owners: [owner],
version: '1.4.1',
entryPoint: {
address: entryPoint07Address,
version: '0.7',
},
safe4337ModuleAddress: '0x7579EE8307284F293B1927136486880611F20002',
erc7579LaunchpadAddress: '0x7579011aB74c46090561ea277Ba79D510c6C00ff',
})
```

### Create the smart account client

The smart account client is used to interact with the smart account. You will need to add your own bundler url and the chain that you are using.

```typescript copy
const smartAccountClient = createSmartAccountClient({
account: safeAccount,
chain: chain,
bundlerTransport: http(bundlerUrl),
paymaster: paymasterClient,
userOperation: {
estimateFeesPerGas: async () => {
return (await pimlicoClient.getUserOperationGasPrice()).fast
},
},
}).extend(erc7579Actions())
```

### Create the module object

Get the module object for the module that you want to install on the smart account. In this case, we will install the Social Recovery Module. We will pass to it a number of guardians that can recover the account as well as a threshold of guardians required to recover the account.

```typescript copy
const guardian1 = privateKeyToAccount(
'0xc171c45f3d35fad832c53cade38e8d21b8d5cc93d1887e867fac626c1c0d6be7',
) // the key coresponding to the first guardian

const guardian2 = privateKeyToAccount(
'0x1a4c05be22dd9294615087ba1dba4266ae68cdc320d9164dbf3650ec0db60f67',
) // the key coresponding to the second guardian

const socialRecovery = getSocialRecoveryValidator({
threshold: 2,
guardians: [guardian1.address, guardian2.address],
})
```

### Install the module

With this module object, we can now install it on the smart account.

```typescript copy
const opHash = await smartAccountClient.installModule(socialRecovery)
```

### Wait for the UserOperation to be confirmed

Let's wait until the UserOperation is confirmed, after which the module will be installed.

```typescript copy
await pimlicoClient.waitForUserOperationReceipt({
hash: opHash,
})
```

</Steps>
1 change: 1 addition & 0 deletions pages/module-sdk/using-modules/_meta.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"smart-sessions": "Smart Sessions",
"webauthn": "WebAuthn",
"social-recovery": "Social Recovery",
"deadman-switch": "Deadman Switch"
}
2 changes: 2 additions & 0 deletions pages/module-sdk/using-modules/smart-sessions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ The Smart Sessions Module allows developers to create session keys with scoped p

We will first set up the smart account, install the Smart Sessions Module, create a session and then use this new session to execute a UserOperation.

For futher guides, check out the [module-sdk-demos](https://github.com/rhinestonewtf/module-sdk-demos/), which contains an example of using smart sessions on the frontend. You can also try out the [hosted demo](https://module-demos.rhinestone.wtf/smart-sessions).

<Steps>

### Install the packages
Expand Down
Loading

0 comments on commit 6fbc700

Please sign in to comment.