Skip to content

Commit

Permalink
feat: prepareAuthorization (#3062)
Browse files Browse the repository at this point in the history
* feat: add `prepareAuthorization`

* chore: format

* Update rotten-camels-knock.md

* chore: up snapshots
  • Loading branch information
jxom authored Nov 26, 2024
1 parent dc97a0b commit 5ab60e3
Show file tree
Hide file tree
Showing 9 changed files with 572 additions and 60 deletions.
5 changes: 5 additions & 0 deletions .changeset/rotten-camels-knock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"viem": patch
---

**Experimental (EIP-7702):** Added `prepareAuthorization`.
179 changes: 179 additions & 0 deletions site/pages/experimental/eip7702/prepareAuthorization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
---
description: Prepares an EIP-7702 Authorization for signing.
---

# prepareAuthorization

Prepares an [EIP-7702 Authorization](https://eips.ethereum.org/EIPS/eip-7702) for signing.
This Action will fill the required fields of the Authorization object if they are not provided (e.g. `nonce` and `chainId`).

With the prepared Authorization object, you can use [`signAuthorization`](/experimental/eip7702/signAuthorization) to sign over it.

## Usage

:::code-group

```ts twoslash [example.ts]
import { walletClient } from './client'

const authorization = await walletClient.prepareAuthorization({ // [!code focus]
contractAddress: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', // [!code focus]
}) // [!code focus]
// @log: {
// @log: chainId: 1,
// @log: contractAddress: "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2",
// @log: nonce: 1,
// @log: }

const signedAuthorization = await walletClient.signAuthorization(authorization)
```

```ts twoslash [client.ts] filename="client.ts"
import { createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { mainnet } from 'viem/chains'
import { eip7702Actions } from 'viem/experimental'

export const walletClient = createWalletClient({
account: privateKeyToAccount('0x...'),
chain: mainnet,
transport: http(),
}).extend(eip7702Actions())
```

:::

### Explicit Scoping

We can explicitly set a `nonce` and/or `chainId` by supplying them as parameters:

:::code-group

```ts twoslash [example.ts]
import { walletClient } from './client'

const authorization = await walletClient.prepareAuthorization({
contractAddress: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
chainId: 10, // [!code focus]
})
// @log: {
// @log: chainId: 10,
// @log: contractAddress: "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2",
// @log: nonce: 420,
// @log: }

const signedAuthorization = await walletClient.signAuthorization(authorization)
```

```ts twoslash [client.ts] filename="client.ts"
import { createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { mainnet } from 'viem/chains'
import { eip7702Actions } from 'viem/experimental'

export const walletClient = createWalletClient({
account: privateKeyToAccount('0x...'),
chain: mainnet,
transport: http(),
}).extend(eip7702Actions())
```

:::

## Returns

`Authorization`

A prepared & unsigned Authorization object.

## Parameters

### account

- **Type:** `Account`

Account to use to prepare the Authorization object.

Accepts a [Local Account (Private Key, etc)](/docs/clients/wallet#local-accounts-private-key-mnemonic-etc).

```ts twoslash
import { privateKeyToAccount } from 'viem/accounts'
import { walletClient } from './client'

const authorization = await walletClient.prepareAuthorization({
account: privateKeyToAccount('0x...'), // [!code focus]
contractAddress: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2'
})
```

### chainId (optional)

- **Type:** `Address`
- **Default:** `client.chain.id` or Network chain ID

The Chain ID to scope the Authorization to. If set to zero (`0`), then the Authorization will
be valid on all chains.

```ts twoslash
import { privateKeyToAccount } from 'viem/accounts'
import { walletClient } from './client'

const authorization = await walletClient.prepareAuthorization({
account: privateKeyToAccount('0x...'),
contractAddress: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
chainId: 1, // [!code focus]
})
```

### contractAddress

- **Type:** `Address`

The target Contract to designate onto the Account.

```ts twoslash
import { privateKeyToAccount } from 'viem/accounts'
import { walletClient } from './client'

const authorization = await walletClient.prepareAuthorization({
account: privateKeyToAccount('0x...'),
contractAddress: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2' // [!code focus]
})
```

### delegate (optional)

- **Type:** `true | Address | Account`

Whether the EIP-7702 Transaction will be executed by another Account.

If not specified, it will be assumed that the EIP-7702 Transaction will be executed by the Account that signed the Authorization.

```ts twoslash
import { privateKeyToAccount } from 'viem/accounts'
import { walletClient } from './client'

const authorization = await walletClient.prepareAuthorization({
account: privateKeyToAccount('0x...'),
contractAddress: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
delegate: true, // [!code focus]
})
```

### nonce (optional)

- **Type:** `Address`
- **Default:** Account's next available nonce.

The nonce to scope the Authorization to.

```ts twoslash
import { privateKeyToAccount } from 'viem/accounts'
import { walletClient } from './client'

const authorization = await walletClient.prepareAuthorization({
account: privateKeyToAccount('0x...'),
contractAddress: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
nonce: 69, // [!code focus]
})
```
4 changes: 4 additions & 0 deletions site/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1301,6 +1301,10 @@ export const sidebar = {
{
text: 'Actions',
items: [
{
text: 'prepareAuthorization',
link: '/experimental/eip7702/prepareAuthorization',
},
{
text: 'signAuthorization',
link: '/experimental/eip7702/signAuthorization',
Expand Down
174 changes: 174 additions & 0 deletions src/experimental/eip7702/actions/prepareAuthorization.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { beforeAll, expect, test } from 'vitest'
import { wagmiContractConfig } from '../../../../test/src/abis.js'
import { anvilMainnet } from '../../../../test/src/anvil.js'
import { accounts } from '../../../../test/src/constants.js'
import { privateKeyToAccount } from '../../../accounts/privateKeyToAccount.js'
import { reset } from '../../../actions/index.js'
import { prepareAuthorization } from './prepareAuthorization.js'

const account = privateKeyToAccount(accounts[0].privateKey)
const client = anvilMainnet.getClient()

beforeAll(async () => {
await reset(client, {
blockNumber: anvilMainnet.forkBlockNumber,
jsonRpcUrl: anvilMainnet.forkUrl,
})
})

test('default', async () => {
const authorization = await prepareAuthorization(client, {
account,
contractAddress: wagmiContractConfig.address,
chainId: 1,
nonce: 0,
})

expect(authorization).toMatchInlineSnapshot(
`
{
"chainId": 1,
"contractAddress": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2",
"nonce": 0,
}
`,
)
})

test('behavior: partial authorization: no chainId + nonce', async () => {
const authorization = await prepareAuthorization(client, {
account,
contractAddress: wagmiContractConfig.address,
})

expect(authorization).toMatchInlineSnapshot(
`
{
"chainId": 1,
"contractAddress": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2",
"nonce": 664,
}
`,
)
})

test('behavior: partial authorization: no nonce', async () => {
const authorization = await prepareAuthorization(client, {
account,
contractAddress: wagmiContractConfig.address,
chainId: 10,
})

expect(authorization).toMatchInlineSnapshot(
`
{
"chainId": 10,
"contractAddress": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2",
"nonce": 664,
}
`,
)
})

test('behavior: partial authorization: no chainId', async () => {
const authorization = await prepareAuthorization(client, {
account,
contractAddress: wagmiContractConfig.address,
nonce: 69,
})

expect(authorization).toMatchInlineSnapshot(
`
{
"chainId": 1,
"contractAddress": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2",
"nonce": 69,
}
`,
)
})

test('behavior: delegate is address', async () => {
const authorization = await prepareAuthorization(client, {
account,
contractAddress: wagmiContractConfig.address,
delegate: '0x0000000000000000000000000000000000000000',
})

expect(authorization.nonce).toBe(663)
})

test('behavior: delegate is truthy', async () => {
const authorization = await prepareAuthorization(client, {
account,
contractAddress: wagmiContractConfig.address,
delegate: true,
})

expect(authorization.nonce).toBe(663)
})

test('behavior: account as delegate', async () => {
const authorization = await prepareAuthorization(client, {
account,
contractAddress: wagmiContractConfig.address,
delegate: account,
})

expect(authorization.nonce).toBe(664)
})

test('behavior: hoisted account on client', async () => {
const client = anvilMainnet.getClient({ account })
const authorization = await prepareAuthorization(client, {
contractAddress: wagmiContractConfig.address,
chainId: 1,
nonce: 0,
})

expect(authorization).toMatchInlineSnapshot(
`
{
"chainId": 1,
"contractAddress": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2",
"nonce": 0,
}
`,
)
})

test('behavior: no client chain', async () => {
const client = anvilMainnet.getClient({ chain: false })
const authorization = await prepareAuthorization(client, {
account,
contractAddress: wagmiContractConfig.address,
nonce: 0,
})

expect(authorization).toMatchInlineSnapshot(
`
{
"chainId": 1,
"contractAddress": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2",
"nonce": 0,
}
`,
)
})

test('error: no account', async () => {
await expect(() =>
// @ts-expect-error
prepareAuthorization(client, {
contractAddress: wagmiContractConfig.address,
chainId: 1,
nonce: 0,
}),
).rejects.toThrowErrorMatchingInlineSnapshot(`
[AccountNotFoundError: Could not find an Account to execute with this Action.
Please provide an Account with the \`account\` argument on the Action, or by supplying an \`account\` to the Client.
Docs: https://viem.sh/experimental/eip7702/prepareAuthorization
Version: viem@x.y.z]
`)
})
Loading

0 comments on commit 5ab60e3

Please sign in to comment.