Skip to content

Commit

Permalink
feat: add ccipRead client config (#2006)
Browse files Browse the repository at this point in the history
* feat: allow custom offchainLookup handling

* correct import

* chore: tweaks

* docs: tweak

* tests: update

* chore: changeset

---------

Co-authored-by: moxey.eth <jakemoxey@gmail.com>
  • Loading branch information
TateB and jxom authored Mar 26, 2024
1 parent 0cca976 commit 742d4db
Show file tree
Hide file tree
Showing 19 changed files with 312 additions and 22 deletions.
5 changes: 5 additions & 0 deletions .changeset/gentle-maps-tan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"viem": patch
---

Added ability to specify a custom offchain lookup request for CCIP Read, and ability to disable CCIP Read entirely.
40 changes: 40 additions & 0 deletions site/pages/docs/clients/public.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,46 @@ const publicClient = createPublicClient({
})
```

### ccipRead (optional)

- **Type:** `(parameters: CcipRequestParameters) => Promise<CcipRequestReturnType> | false`
- **Default:** `true`

[CCIP Read](https://eips.ethereum.org/EIPS/eip-3668) configuration.

CCIP Read is enabled by default, but if set to `false`, the client will not support offchain CCIP lookups.

```ts twoslash
// [!include ~/snippets/publicClient.ts:imports]
// ---cut---
const publicClient = createPublicClient({
ccipRead: false, // [!code focus]
chain: mainnet,
transport: http(),
})
```

### ccipRead.request (optional)

- **Type:** `(parameters: CcipRequestParameters) => Promise<CcipRequestReturnType>`

A function that will be called to make the [offchain CCIP lookup request](https://eips.ethereum.org/EIPS/eip-3668#client-lookup-protocol).

```ts twoslash
// @noErrors
// [!include ~/snippets/publicClient.ts:imports]
// ---cut---
const publicClient = createPublicClient({
ccipRead: { // [!code focus]
async request({ data, sender, urls }) { // [!code focus]
// ... // [!code focus]
} // [!code focus]
}, // [!code focus]
chain: mainnet,
transport: http(),
})
```

### key (optional)

- **Type:** `string`
Expand Down
40 changes: 40 additions & 0 deletions site/pages/docs/clients/wallet.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,46 @@ const client = createWalletClient({
})
```

### ccipRead (optional)

- **Type:** `(parameters: CcipRequestParameters) => Promise<CcipRequestReturnType> | false`
- **Default:** `true`

[CCIP Read](https://eips.ethereum.org/EIPS/eip-3668) configuration.

CCIP Read is enabled by default, but if set to `false`, the client will not support offchain CCIP lookups.

```ts twoslash
import 'viem/window'
import { createWalletClient, custom } from 'viem'
// ---cut---
const client = createWalletClient({
ccipRead: false, // [!code focus]
transport: custom(window.ethereum!)
})
```

### ccipRead.request (optional)

- **Type:** `(parameters: CcipRequestParameters) => Promise<CcipRequestReturnType>`

A function that will be called to make the [offchain CCIP lookup request](https://eips.ethereum.org/EIPS/eip-3668#client-lookup-protocol).

```ts twoslash
// @noErrors
import 'viem/window'
import { createWalletClient, custom } from 'viem'
// ---cut---
const client = createWalletClient({
ccipRead: { // [!code focus]
async request({ data, sender, urls }) { // [!code focus]
// ... // [!code focus]
} // [!code focus]
}, // [!code focus]
transport: custom(window.ethereum!)
})
```

### key (optional)

- **Type:** `string`
Expand Down
40 changes: 39 additions & 1 deletion src/actions/public/call.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { describe, expect, test, vi } from 'vitest'
import { OffchainLookupExample } from '~test/contracts/generated.js'
import { baycContractConfig, usdcContractConfig } from '~test/src/abis.js'
import { createCcipServer } from '~test/src/ccip.js'
import { accounts, forkBlockNumber } from '~test/src/constants.js'
import { accounts, forkBlockNumber, localHttpUrl } from '~test/src/constants.js'
import {
deployOffchainLookupExample,
publicClient,
Expand All @@ -21,9 +21,11 @@ import { wait } from '../../utils/wait.js'

import { blobData } from '../../../test/src/kzg.js'
import {
http,
type Hex,
type StateMapping,
type StateOverride,
createClient,
encodeAbiParameters,
pad,
parseEther,
Expand Down Expand Up @@ -83,6 +85,42 @@ describe('ccip', () => {
await server.close()
})

test('ccip disabled', async () => {
const server = await createCcipServer()
const { contractAddress } = await deployOffchainLookupExample({
urls: [`${server.url}/{sender}/{data}`],
})

const calldata = encodeFunctionData({
abi: OffchainLookupExample.abi,
functionName: 'getAddress',
args: ['jxom.viem'],
})

const client = createClient({
ccipRead: false,
transport: http(localHttpUrl),
})

await expect(() =>
call(client, {
data: calldata,
to: contractAddress!,
}),
).rejects.toMatchInlineSnapshot(`
[CallExecutionError: Execution reverted with reason: custom error 556f1830:000000000000000000000000124ddf9b…00000000000000000000000000000000 (576 bytes).
Raw Call Arguments:
to: 0x124ddf9bdd2ddad012ef1d5bbd77c00f05c610da
data: 0xbf40fac1000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000096a786f6d2e7669656d0000000000000000000000000000000000000000000000
Details: execution reverted: custom error 556f1830:000000000000000000000000124ddf9b…00000000000000000000000000000000 (576 bytes)
Version: viem@1.0.2]
`)

await server.close()
})

test('error: invalid signature', async () => {
const server = await createCcipServer()
const { contractAddress } = await deployOffchainLookupExample({
Expand Down
7 changes: 5 additions & 2 deletions src/actions/public/call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,9 +233,12 @@ export async function call<TChain extends Chain | undefined>(
const { offchainLookup, offchainLookupSignature } = await import(
'../../utils/ccip.js'
)
if (data?.slice(0, 10) === offchainLookupSignature && to) {
if (
client.ccipRead !== false &&
data?.slice(0, 10) === offchainLookupSignature &&
to
)
return { data: await offchainLookup(client, { data, to }) }
}
throw getCallError(err as ErrorType, {
...args,
account,
Expand Down
56 changes: 56 additions & 0 deletions src/clients/createClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ test('creates', () => {
"account": undefined,
"batch": undefined,
"cacheTime": 4000,
"ccipRead": undefined,
"chain": undefined,
"extend": [Function],
"key": "base",
Expand Down Expand Up @@ -63,6 +64,7 @@ describe('transports', () => {
"account": undefined,
"batch": undefined,
"cacheTime": 4000,
"ccipRead": undefined,
"chain": {
"fees": undefined,
"formatters": undefined,
Expand Down Expand Up @@ -115,6 +117,7 @@ describe('transports', () => {
"account": undefined,
"batch": undefined,
"cacheTime": 4000,
"ccipRead": undefined,
"chain": {
"fees": undefined,
"formatters": undefined,
Expand Down Expand Up @@ -167,6 +170,7 @@ describe('transports', () => {
"account": undefined,
"batch": undefined,
"cacheTime": 4000,
"ccipRead": undefined,
"chain": undefined,
"extend": [Function],
"key": "base",
Expand Down Expand Up @@ -208,6 +212,53 @@ describe('config', () => {
"account": undefined,
"batch": undefined,
"cacheTime": 10000,
"ccipRead": undefined,
"chain": undefined,
"extend": [Function],
"key": "base",
"name": "Base Client",
"pollingInterval": 4000,
"request": [Function],
"transport": {
"key": "mock",
"name": "Mock Transport",
"request": [MockFunction spy],
"retryCount": 3,
"retryDelay": 150,
"timeout": undefined,
"type": "mock",
},
"type": "base",
}
`)
})

test('ccipRead', () => {
const mockTransport = () =>
createTransport({
key: 'mock',
name: 'Mock Transport',
request: vi.fn(async () => null) as unknown as EIP1193RequestFn,
type: 'mock',
})
const { uid, ...client } = createClient({
ccipRead: {
async request(_parameters) {
return '0x' as const
},
},
transport: mockTransport,
})

expect(uid).toBeDefined()
expect(client).toMatchInlineSnapshot(`
{
"account": undefined,
"batch": undefined,
"cacheTime": 4000,
"ccipRead": {
"request": [Function],
},
"chain": undefined,
"extend": [Function],
"key": "base",
Expand Down Expand Up @@ -248,6 +299,7 @@ describe('config', () => {
"account": undefined,
"batch": undefined,
"cacheTime": 4000,
"ccipRead": undefined,
"chain": undefined,
"extend": [Function],
"key": "bar",
Expand Down Expand Up @@ -288,6 +340,7 @@ describe('config', () => {
"account": undefined,
"batch": undefined,
"cacheTime": 4000,
"ccipRead": undefined,
"chain": undefined,
"extend": [Function],
"key": "base",
Expand Down Expand Up @@ -328,6 +381,7 @@ describe('config', () => {
"account": undefined,
"batch": undefined,
"cacheTime": 10000,
"ccipRead": undefined,
"chain": undefined,
"extend": [Function],
"key": "base",
Expand Down Expand Up @@ -368,6 +422,7 @@ describe('config', () => {
"account": undefined,
"batch": undefined,
"cacheTime": 4000,
"ccipRead": undefined,
"chain": undefined,
"extend": [Function],
"key": "base",
Expand Down Expand Up @@ -413,6 +468,7 @@ describe('extends', () => {
"batch": undefined,
"cacheTime": 4000,
"call": [Function],
"ccipRead": undefined,
"chain": {
"fees": undefined,
"formatters": undefined,
Expand Down
24 changes: 24 additions & 0 deletions src/clients/createClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import type {
} from '../types/eip1193.js'
import type { ExactPartial, Prettify } from '../types/utils.js'
import { parseAccount } from '../utils/accounts.js'
import type {
CcipRequestParameters,
CcipRequestReturnType,
} from '../utils/ccip.js'
import { uid } from '../utils/uid.js'
import type { PublicActions } from './decorators/public.js'
import type { WalletActions } from './decorators/wallet.js'
Expand Down Expand Up @@ -39,6 +43,22 @@ export type ClientConfig<
* @default 4_000
*/
cacheTime?: number | undefined
/**
* [CCIP Read](https://eips.ethereum.org/EIPS/eip-3668) configuration.
* If `false`, the client will not support offchain CCIP lookups.
*/
ccipRead?:
| {
/**
* A function that will be called to make the offchain CCIP lookup request.
* @see https://eips.ethereum.org/EIPS/eip-3668#client-lookup-protocol
*/
request?: (
parameters: CcipRequestParameters,
) => Promise<CcipRequestReturnType>
}
| false
| undefined
/** Chain for the client. */
chain?: Chain | undefined | chain
/** A key for the client. */
Expand Down Expand Up @@ -130,6 +150,8 @@ type Client_Base<
batch?: ClientConfig['batch'] | undefined
/** Time (in ms) that cached data will remain in memory. */
cacheTime: number
/** [CCIP Read](https://eips.ethereum.org/EIPS/eip-3668) configuration. */
ccipRead?: ClientConfig['ccipRead'] | undefined
/** Chain for the client. */
chain: chain
/** A key for the client. */
Expand Down Expand Up @@ -189,6 +211,7 @@ export function createClient(parameters: ClientConfig): Client {
const {
batch,
cacheTime = parameters.pollingInterval ?? 4_000,
ccipRead,
key = 'base',
name = 'Base Client',
pollingInterval = 4_000,
Expand All @@ -209,6 +232,7 @@ export function createClient(parameters: ClientConfig): Client {
account,
batch,
cacheTime,
ccipRead,
chain,
key,
name,
Expand Down
Loading

0 comments on commit 742d4db

Please sign in to comment.