UNPKG

viem

Version:

TypeScript Interface for Ethereum

194 lines (172 loc) • 4.76 kB
import type { Abi, Address } from 'abitype' import { type CallParameters, call } from '../actions/public/call.js' import type { Transport } from '../clients/transports/createTransport.js' import type { BaseError } from '../errors/base.js' import { OffchainLookupError, type OffchainLookupErrorType as OffchainLookupErrorType_, OffchainLookupResponseMalformedError, type OffchainLookupResponseMalformedErrorType, OffchainLookupSenderMismatchError, } from '../errors/ccip.js' import { HttpRequestError, type HttpRequestErrorType, } from '../errors/request.js' import type { Chain } from '../types/chain.js' import type { Hex } from '../types/misc.js' import type { Client } from '../clients/createClient.js' import type { ErrorType } from '../errors/utils.js' import { decodeErrorResult } from './abi/decodeErrorResult.js' import { encodeAbiParameters } from './abi/encodeAbiParameters.js' import { isAddressEqual } from './address/isAddressEqual.js' import { concat } from './data/concat.js' import { isHex } from './data/isHex.js' import { stringify } from './stringify.js' export const offchainLookupSignature = '0x556f1830' export const offchainLookupAbiItem = { name: 'OffchainLookup', type: 'error', inputs: [ { name: 'sender', type: 'address', }, { name: 'urls', type: 'string[]', }, { name: 'callData', type: 'bytes', }, { name: 'callbackFunction', type: 'bytes4', }, { name: 'extraData', type: 'bytes', }, ], } as const satisfies Abi[number] export type OffchainLookupErrorType = OffchainLookupErrorType_ | ErrorType export async function offchainLookup<chain extends Chain | undefined>( client: Client<Transport, chain>, { blockNumber, blockTag, data, to, }: Pick<CallParameters, 'blockNumber' | 'blockTag'> & { data: Hex to: Address }, ): Promise<Hex> { const { args } = decodeErrorResult({ data, abi: [offchainLookupAbiItem], }) const [sender, urls, callData, callbackSelector, extraData] = args const { ccipRead } = client const ccipRequest_ = ccipRead && typeof ccipRead?.request === 'function' ? ccipRead.request : ccipRequest try { if (!isAddressEqual(to, sender)) throw new OffchainLookupSenderMismatchError({ sender, to }) const result = await ccipRequest_({ data: callData, sender, urls }) const { data: data_ } = await call(client, { blockNumber, blockTag, data: concat([ callbackSelector, encodeAbiParameters( [{ type: 'bytes' }, { type: 'bytes' }], [result, extraData], ), ]), to, } as CallParameters) return data_! } catch (err) { throw new OffchainLookupError({ callbackSelector, cause: err as BaseError, data, extraData, sender, urls, }) } } export type CcipRequestParameters = { data: Hex sender: Address urls: readonly string[] } export type CcipRequestReturnType = Hex export type CcipRequestErrorType = | HttpRequestErrorType | OffchainLookupResponseMalformedErrorType | ErrorType export async function ccipRequest({ data, sender, urls, }: CcipRequestParameters): Promise<CcipRequestReturnType> { let error = new Error('An unknown error occurred.') for (let i = 0; i < urls.length; i++) { const url = urls[i] const method = url.includes('{data}') ? 'GET' : 'POST' const body = method === 'POST' ? { data, sender } : undefined const headers: HeadersInit = method === 'POST' ? { 'Content-Type': 'application/json' } : {} try { const response = await fetch( url.replace('{sender}', sender.toLowerCase()).replace('{data}', data), { body: JSON.stringify(body), headers, method, }, ) let result: any if ( response.headers.get('Content-Type')?.startsWith('application/json') ) { result = (await response.json()).data } else { result = (await response.text()) as any } if (!response.ok) { error = new HttpRequestError({ body, details: result?.error ? stringify(result.error) : response.statusText, headers: response.headers, status: response.status, url, }) continue } if (!isHex(result)) { error = new OffchainLookupResponseMalformedError({ result, url, }) continue } return result } catch (err) { error = new HttpRequestError({ body, details: (err as Error).message, url, }) } } throw error }