Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/new-emus-wonder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"viem": minor
---

Added getDelegation utility for EIP7702
104 changes: 104 additions & 0 deletions site/pages/docs/eip7702/getDelegation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
---
description: Returns the address an account has delegated to via EIP-7702.
---

# getDelegation

Returns the address that an account has delegated to via [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702).

## Usage

:::code-group

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

const delegation = await publicClient.getDelegation({
address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e',
})
// '0x1234...5678' or undefined
```

```ts [client.ts]
import { createPublicClient, http } from 'viem'
import { mainnet } from 'viem/chains'

export const publicClient = createPublicClient({
chain: mainnet,
transport: http()
})
```

:::

## Return Value

[`Address`](/docs/glossary/types#address) | `undefined`

The address the account has delegated to, or `undefined` if the account is not delegated.

## Parameters

### address

- **Type:** [`Address`](/docs/glossary/types#address)

The address to check for delegation.

```ts
const delegation = await publicClient.getDelegation({
address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', // [!code focus]
})
```

### blockNumber (optional)

- **Type:** `bigint`

The block number to check the delegation at.

```ts
const delegation = await publicClient.getDelegation({
address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e',
blockNumber: 15121123n, // [!code focus]
})
```

### blockTag (optional)

- **Type:** `'latest' | 'earliest' | 'pending' | 'safe' | 'finalized'`
- **Default:** `'latest'`

The block tag to check the delegation at.

```ts
const delegation = await publicClient.getDelegation({
address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e',
blockTag: 'safe', // [!code focus]
})
```

## How It Works

This action retrieves the bytecode at the given address using [`eth_getCode`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getcode) and checks if it matches the EIP-7702 delegation designator format:

- **Delegation designator prefix:** `0xef0100`
- **Total length:** 23 bytes (3 byte prefix + 20 byte address)

If the bytecode matches this format, the delegated address is extracted and returned.

## Example: Check if Account is Delegated

```ts
import { publicClient } from './client'

const delegation = await publicClient.getDelegation({
address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e',
})

if (delegation) {
console.log(`Account is delegated to: ${delegation}`)
} else {
console.log('Account is not delegated')
}
```
4 changes: 4 additions & 0 deletions site/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,10 @@ export const sidebar = {
text: 'verifyAuthorization',
link: '/docs/eip7702/verifyAuthorization',
},
{
text: 'getDelegation',
link: '/docs/eip7702/getDelegation',
},
],
},
],
Expand Down
6 changes: 6 additions & 0 deletions src/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,12 @@ export {
type GetContractEventsReturnType,
getContractEvents,
} from './public/getContractEvents.js'
export {
type GetDelegationErrorType,
type GetDelegationParameters,
type GetDelegationReturnType,
getDelegation,
} from './public/getDelegation.js'
export {
type GetEip712DomainErrorType,
type GetEip712DomainParameters,
Expand Down
116 changes: 116 additions & 0 deletions src/actions/public/getDelegation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { beforeAll, describe, expect, test } from 'vitest'

import { EoaOptional } from '~contracts/generated.js'
import { anvilMainnet } from '~test/anvil.js'
import { accounts } from '~test/constants.js'
import { deploy } from '~test/utils.js'
import { generatePrivateKey } from '../../accounts/generatePrivateKey.js'
import { privateKeyToAccount } from '../../accounts/privateKeyToAccount.js'
import { mine } from '../test/mine.js'
import { sendTransaction } from '../wallet/sendTransaction.js'
import { signAuthorization } from '../wallet/signAuthorization.js'
import { getBlockNumber } from './getBlockNumber.js'
import { getDelegation } from './getDelegation.js'

const client = anvilMainnet.getClient({ account: true })
const localAccount = privateKeyToAccount(accounts[0].privateKey)

describe('getDelegation', () => {
let delegation: `0x${string}`

beforeAll(async () => {
const { contractAddress } = await deploy(client, {
abi: EoaOptional.abi,
bytecode: EoaOptional.bytecode.object,
})
delegation = contractAddress!
})

test('returns undefined for non-delegated EOA', async () => {
const result = await getDelegation(client, {
address: localAccount.address,
})
expect(result).toBeUndefined()
})

test('returns undefined for contract address', async () => {
const result = await getDelegation(client, {
address: delegation,
})
expect(result).toBeUndefined()
})

test('returns delegated address for EIP-7702 delegated account', async () => {
const account = privateKeyToAccount(generatePrivateKey())

// Create delegation
const authorization = await signAuthorization(client, {
account,
address: delegation,
})

await sendTransaction(client, {
authorizationList: [authorization],
gas: 1_000_000n,
})
await mine(client, { blocks: 1 })

const result = await getDelegation(client, {
address: account.address,
})
expect(result).toBe(delegation)
})

test('with blockNumber', async () => {
const account = privateKeyToAccount(generatePrivateKey())

const blockNumberBefore = await getBlockNumber(client)

// Create delegation
const authorization = await signAuthorization(client, {
account,
address: delegation,
})

await sendTransaction(client, {
authorizationList: [authorization],
gas: 1_000_000n,
})
await mine(client, { blocks: 1 })

// Should be undefined at the block before delegation
const resultBefore = await getDelegation(client, {
address: account.address,
blockNumber: blockNumberBefore,
})
expect(resultBefore).toBeUndefined()

// Should have delegation at current block
const resultAfter = await getDelegation(client, {
address: account.address,
})
expect(resultAfter).toBe(delegation)
})

test('with blockTag', async () => {
const account = privateKeyToAccount(generatePrivateKey())

// Create delegation
const authorization = await signAuthorization(client, {
account,
address: delegation,
})

await sendTransaction(client, {
authorizationList: [authorization],
gas: 1_000_000n,
})
await mine(client, { blocks: 1 })

const result = await getDelegation(client, {
address: account.address,
blockTag: 'latest',
})
expect(result).toBe(delegation)
})
})
75 changes: 75 additions & 0 deletions src/actions/public/getDelegation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import type { Address } from 'abitype'

import type { Client } from '../../clients/createClient.js'
import type { Transport } from '../../clients/transports/createTransport.js'
import type { ErrorType } from '../../errors/utils.js'
import type { BlockTag } from '../../types/block.js'
import type { Chain } from '../../types/chain.js'
import { type SizeErrorType, size } from '../../utils/data/size.js'
import { type SliceErrorType, slice } from '../../utils/data/slice.js'
import { type GetCodeErrorType, getCode } from './getCode.js'

export type GetDelegationParameters = {
/** The address to check for delegation. */
address: Address
} & (
| {
blockNumber?: undefined
blockTag?: BlockTag | undefined
}
| {
blockNumber?: bigint | undefined
blockTag?: undefined
}
)

export type GetDelegationReturnType = Address | undefined

export type GetDelegationErrorType =
| GetCodeErrorType
| SliceErrorType
| SizeErrorType
| ErrorType

/**
* Returns the address that an account has delegated to via EIP-7702.
*
* - Docs: https://viem.sh/docs/actions/public/getDelegation
*
* @param client - Client to use
* @param parameters - {@link GetDelegationParameters}
* @returns The delegated address, or undefined if not delegated. {@link GetDelegationReturnType}
*
* @example
* import { createPublicClient, http } from 'viem'
* import { mainnet } from 'viem/chains'
* import { getDelegation } from 'viem/actions'
*
* const client = createPublicClient({
* chain: mainnet,
* transport: http(),
* })
* const delegation = await getDelegation(client, {
* address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e',
* })
*/
export async function getDelegation<chain extends Chain | undefined>(
client: Client<Transport, chain>,
{ address, blockNumber, blockTag = 'latest' }: GetDelegationParameters,
): Promise<GetDelegationReturnType> {
const code = await getCode(client, {
address,
...(blockNumber !== undefined ? { blockNumber } : { blockTag }),
} as GetDelegationParameters)

if (!code) return undefined

// EIP-7702 delegation designator: 0xef0100 prefix (3 bytes) + address (20 bytes) = 23 bytes
if (size(code) !== 23) return undefined

// Check for EIP-7702 delegation designator prefix
if (!code.startsWith('0xef0100')) return undefined

// Extract the delegated address (bytes 3-23)
return slice(code, 3, 23) as Address
}
29 changes: 29 additions & 0 deletions src/clients/decorators/public.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ import {
type GetContractEventsReturnType,
getContractEvents,
} from '../../actions/public/getContractEvents.js'
import {
type GetDelegationParameters,
type GetDelegationReturnType,
getDelegation,
} from '../../actions/public/getDelegation.js'
import {
type GetEip712DomainParameters,
type GetEip712DomainReturnType,
Expand Down Expand Up @@ -758,6 +763,29 @@ export type PublicActions<
* })
*/
getCode: (args: GetCodeParameters) => Promise<GetCodeReturnType>
/**
* Returns the address that an account has delegated to via EIP-7702.
*
* - Docs: https://viem.sh/docs/actions/public/getDelegation
*
* @param args - {@link GetDelegationParameters}
* @returns The delegated address, or undefined if not delegated. {@link GetDelegationReturnType}
*
* @example
* import { createPublicClient, http } from 'viem'
* import { mainnet } from 'viem/chains'
*
* const client = createPublicClient({
* chain: mainnet,
* transport: http(),
* })
* const delegation = await client.getDelegation({
* address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e',
* })
*/
getDelegation: (
args: GetDelegationParameters,
) => Promise<GetDelegationReturnType>
/**
* Returns a list of event logs emitted by a contract.
*
Expand Down Expand Up @@ -2062,6 +2090,7 @@ export function publicActions<
getBytecode: (args) => getCode(client, args),
getChainId: () => getChainId(client),
getCode: (args) => getCode(client, args),
getDelegation: (args) => getDelegation(client, args),
getContractEvents: (args) => getContractEvents(client, args),
getEip712Domain: (args) => getEip712Domain(client, args),
getEnsAddress: (args) => getEnsAddress(client, args),
Expand Down
Loading