- Consolidated duplicate UndoManagers to single instance - Fixed connection promise to only resolve on 'connected' status - Fixed WebSocketProvider import (WebsocketProvider) - Added proper doc.destroy() cleanup - Renamed isPresenceInitialized property to avoid conflict Co-Authored-By: Paperclip <noreply@paperclip.ing>
202 lines
4.9 KiB
TypeScript
202 lines
4.9 KiB
TypeScript
import type { Abi, Address } from 'abitype'
|
|
|
|
import { type CallParameters, call } from '../actions/public/call.js'
|
|
import type { Client } from '../clients/createClient.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 { ErrorType } from '../errors/utils.js'
|
|
import type { Chain } from '../types/chain.js'
|
|
import type { Hex } from '../types/misc.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 {
|
|
localBatchGatewayRequest,
|
|
localBatchGatewayUrl,
|
|
} from './ens/localBatchGatewayRequest.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 = urls.includes(localBatchGatewayUrl)
|
|
? await localBatchGatewayRequest({
|
|
data: callData,
|
|
ccipRequest: ccipRequest_,
|
|
})
|
|
: 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
|
|
}
|