UNPKG

@unruggable/gateways

Version:

Trustless Ethereum Multichain CCIP-Read Gateway

120 lines (119 loc) 4.19 kB
import { AbiCoder } from 'ethers/abi'; import { id as keccakStr } from 'ethers/hash'; export const ABI_CODER = AbiCoder.defaultAbiCoder(); // https://adraffy.github.io/keccak.js/test/demo.html#algo=keccak-256&s=&escape=1&encoding=utf8 // "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" export const NULL_CODE_HASH = keccakStr(''); export const EVM_BLOCKHASH_DEPTH = 256; // TODO: make this a function of Chain export const MAINNET_BLOCK_SEC = 12; export const LATEST_BLOCK_TAG = 'latest'; // hex-prefixed w/o zero-padding export function toUnpaddedHex(x) { return '0x' + BigInt(x).toString(16); } // hex-prefixed left-pad w/truncation export function toPaddedHex(x, width = 32) { const i = x === '0x' ? 0n : BigInt.asUintN(width << 3, BigInt(x)); return '0x' + i.toString(16).padStart(width << 1, '0'); } // manual polyfill: ES2024 export function withResolvers() { let resolve; let reject; const promise = new Promise((ful, rej) => { resolve = ful; reject = rej; }); return { promise, resolve, reject }; } export function isBlockTag(x) { return typeof x === 'string' && !x.startsWith('0x'); } export async function fetchBlock(provider, relBlockTag = LATEST_BLOCK_TAG) { if (!isBlockTag(relBlockTag)) { let i = BigInt(relBlockTag); if (i < 0) i += await fetchBlockNumber(provider); relBlockTag = toUnpaddedHex(i); } const json = await provider.send('eth_getBlockByNumber', [ relBlockTag, false, ]); if (!json) throw new Error(`no block: ${relBlockTag}`); return json; } export async function fetchBlockFromHash(provider, blockHash) { const block = await provider.send('eth_getBlockByHash', [blockHash, false]); if (!block) throw new Error(`no blockhash: ${blockHash}`); return block; } // avoid an rpc if possible // use negative (-100) for offset from "latest" (#-100) export async function fetchBlockNumber(provider, relBlockTag = LATEST_BLOCK_TAG) { if (relBlockTag === LATEST_BLOCK_TAG) { return BigInt(await provider.send('eth_blockNumber', [])); } else if (isBlockTag(relBlockTag)) { const info = await fetchBlock(provider, relBlockTag); return BigInt(info.number); } else { let i = BigInt(relBlockTag); if (i < 0) i += await fetchBlockNumber(provider); return i; } } // avoid an rpc if possible // convert negative (-100) => absolute (#-100) export async function fetchBlockTag(provider, relBlockTag = LATEST_BLOCK_TAG) { return isBlockTag(relBlockTag) ? relBlockTag : fetchBlockNumber(provider, relBlockTag); } export async function fetchStorage(provider, target, slot, relBlockTag = LATEST_BLOCK_TAG) { const data = await provider.send('eth_getStorageAt', [ target, toPaddedHex(slot), relBlockTag, ]); if (!data) { throw new Error(`expected storage: ${target}<${toUnpaddedHex(slot)}>@${relBlockTag}`); } // i think i've seen "0x" before... return data.length === 66 ? data : toPaddedHex(data); } export function isEthersError(err) { return err instanceof Error && 'code' in err && 'shortMessage' in err; } export function isRevert(err) { return isEthersError(err) && err.code === 'CALL_EXCEPTION'; } export function isRPCError(err, ...code) { return (isEthersError(err) && err.error instanceof Object && 'code' in err.error && typeof err.error.code === 'number' && code.includes(err.error.code)); } export function flattenErrors(err, stringify = stringifyError) { const errors = [stringify(err)]; for (let e = err; e instanceof Error && e.cause; e = e.cause) { errors.push(stringify(e.cause)); } return errors.join(' <== '); } function stringifyError(err) { if (isEthersError(err) && err.code === 'SERVER_ERROR') { // this leaks api key via "requestUrl" // https://github.com/ethers-io/ethers.js/blob/d2c9ca0e0fd15e7884bcaab7d5152d68662e3e43/src.ts/utils/fetch.ts#L953 return err.shortMessage; } else { return String(err); } }