UNPKG

ox

Version:

Ethereum Standard Library

1,200 lines 40.8 kB
import * as AbiItem from './AbiItem.js'; import * as AbiParameters from './AbiParameters.js'; import * as Address from './Address.js'; import * as Bytes from './Bytes.js'; import * as Errors from './Errors.js'; import * as Hash from './Hash.js'; import * as Hex from './Hex.js'; import * as Cursor from './internal/cursor.js'; import { prettyPrint } from './internal/errors.js'; import * as formatAbiItem from './internal/human-readable/formatAbiItem.js'; /** * Module-scope regex for matching an array suffix on an indexed event * parameter type (e.g. `uint256[]`, `bytes32[3]`). Hoisted out of * `decode` / `encode` so we don't recompile per call. * * @internal */ const arraySuffixRegex = /^(.*)\[(\d+)?\]$/; /** * Encodes a primitive (32-byte) indexed event topic directly to a * left-padded 32-byte hex word, skipping the `AbiParameters.encode` * → `prepareParameters` → `Hex.concat` round-trip used for general * parameters. Returns `undefined` if the parameter type isn't a * supported primitive. * * @internal */ function encodePrimitiveTopic(type, value) { if (type === 'address') { Address.assert(value, { strict: false }); return `0x000000000000000000000000${value.slice(2).toLowerCase()}`; } if (type === 'bool') { if (typeof value !== 'boolean') return undefined; return value ? '0x0000000000000000000000000000000000000000000000000000000000000001' : '0x0000000000000000000000000000000000000000000000000000000000000000'; } // `uintN` / `intN` (covers all 8-bit-aligned widths up to 256). if (type.startsWith('uint') || type.startsWith('int')) { const signed = type[0] === 'i'; const sizeStr = signed ? type.slice(3) : type.slice(4); const size = sizeStr === '' ? 256 : Number(sizeStr); if (!Number.isFinite(size)) return undefined; return Hex.fromNumber(value, { size: 32, signed }); } // `bytesN` (fixed): right-pad to 32 bytes. if (type.startsWith('bytes') && type.length > 5) { if (typeof value !== 'string') return undefined; return Hex.padRight(value, 32); } return undefined; } /** * Decodes a primitive (32-byte) indexed event topic directly to a * primitive value, skipping the `AbiParameters.decode` round-trip. * Returns a `[value]` tuple on hit, or `undefined` on miss. * * @internal */ function decodePrimitiveTopic(type, topic, options = {}) { if (type === 'address') { // Trailing 20 bytes; topic is always 66 chars (`0x` + 64 hex). const address = `0x${topic.slice(26)}`; return [ options.checksumAddress === false ? address : Address.checksum(address), ]; } if (type === 'bool') { // Last byte determines truthiness (Solidity left-pads booleans). return [topic.charCodeAt(65) === 49 /* '1' */]; } if (type.startsWith('uint') || type.startsWith('int')) { const signed = type[0] === 'i'; const sizeStr = signed ? type.slice(3) : type.slice(4); const size = sizeStr === '' ? 256 : Number(sizeStr); if (!Number.isFinite(size)) return undefined; const big = size > 48 ? Hex.toBigInt(topic, { signed }) : Hex.toNumber(topic, { signed }); return [big]; } if (type.startsWith('bytes') && type.length > 5) { const sizeStr = type.slice(5); const size = Number(sizeStr); if (!Number.isFinite(size)) return undefined; // Take leading `size` bytes of the 32-byte topic. return [`0x${topic.slice(2, 2 + size * 2)}`]; } return undefined; } /** * Asserts that the provided arguments match the decoded log arguments. * * @example * ```ts twoslash * import { AbiEvent } from 'ox' * * const abiEvent = AbiEvent.from( * 'event Transfer(address indexed from, address indexed to, uint256 value)' * ) * * const args = AbiEvent.decode(abiEvent, { * data: '0x0000000000000000000000000000000000000000000000000000000000000001', * topics: [ * '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', * '0x000000000000000000000000a5cc3c03994db5b0d9a5eedd10cabab0813678ac', * '0x000000000000000000000000a5cc3c03994db5b0d9a5eedd10cabab0813678ac' * ] * }) * * AbiEvent.assertArgs(abiEvent, args, { * from: '0xa5cc3c03994db5b0d9a5eedd10cabab0813678ad', * to: '0xa5cc3c03994db5b0d9a5eedd10cabab0813678ac', * value: 1n * }) * * // @error: AbiEvent.ArgsMismatchError: Given arguments to not match the arguments decoded from the log. * // @error: Event: event Transfer(address indexed from, address indexed to, uint256 value) * // @error: Expected Arguments: * // @error: from: 0xa5cc3c03994db5b0d9a5eedd10cabab0813678ac * // @error: to: 0xa5cc3c03994db5b0d9a5eedd10cabab0813678ad * // @error: value: 1 * // @error: Given Arguments: * // @error: from: 0xa5cc3c03994db5b0d9a5eedd10cabab0813678ad * // @error: to: 0xa5cc3c03994db5b0d9a5eedd10cabab0813678ac * // @error: value: 1 * ``` * * @param abiEvent - ABI Event to check. * @param args - Decoded arguments. * @param matchArgs - The arguments to check. */ export function assertArgs(abiEvent, args, matchArgs) { if (!args || !matchArgs) throw new ArgsMismatchError({ abiEvent, expected: args, given: matchArgs, }); function isEqual(input, value, arg) { if (input.type === 'address') return Address.isEqual(value, arg); if (input.type === 'string') return (Hash.keccak256(Bytes.fromString(value), { as: 'Hex' }) === arg); if (input.type === 'bytes') { const hex = typeof value === 'string' ? value : Hex.fromBytes(value); return Hash.keccak256(hex, { as: 'Hex' }) === arg; } return value === arg; } if (Array.isArray(args) && Array.isArray(matchArgs)) { for (const [index, value] of matchArgs.entries()) { if (value === null || value === undefined) continue; const input = abiEvent.inputs[index]; if (!input) throw new InputNotFoundError({ abiEvent, name: `${index}`, }); const value_ = Array.isArray(value) ? value : [value]; let equal = false; for (const value of value_) { if (isEqual(input, value, args[index])) equal = true; } if (!equal) throw new ArgsMismatchError({ abiEvent, expected: args, given: matchArgs, }); } } if (typeof args === 'object' && !Array.isArray(args) && typeof matchArgs === 'object' && !Array.isArray(matchArgs)) for (const [key, value] of Object.entries(matchArgs)) { if (value === null || value === undefined) continue; const input = abiEvent.inputs.find((input) => input.name === key); if (!input) throw new InputNotFoundError({ abiEvent, name: key }); const value_ = Array.isArray(value) ? value : [value]; let equal = false; for (const value of value_) { if (isEqual(input, value, args[key])) equal = true; } if (!equal) throw new ArgsMismatchError({ abiEvent, expected: args, given: matchArgs, }); } } // eslint-disable-next-line jsdoc-js/require-jsdoc export function decode(...parameters) { const [abiEvent, log, options] = (() => { if (Array.isArray(parameters[0])) { const [abi, name, log, options] = parameters; return [fromAbi(abi, name), log, options]; } const [abiEvent, log, options] = parameters; return [abiEvent, log, options]; })(); const { data, topics } = log; const isAnonymous = abiEvent.anonymous === true; const argTopics = isAnonymous ? [...topics] : topics.slice(1); if (!isAnonymous) { const selector_ = topics[0]; const selector = getSelector(abiEvent); if (selector_ !== selector) throw new SelectorTopicMismatchError({ abiEvent, actual: selector_, expected: selector, }); } const { inputs } = abiEvent; const isUnnamed = inputs?.every((x) => !('name' in x && x.name)); let args = isUnnamed ? [] : {}; // Single-pass partition: keep both the input and its original index so we // don't have to call `inputs.indexOf(...)` per non-indexed entry below // (which is O(n^2) on event width). const indexedInputs = []; const nonIndexedInputs = []; const nonIndexedOriginalIndex = []; for (let i = 0; i < inputs.length; i++) { const input = inputs[i]; if ('indexed' in input && input.indexed) indexedInputs.push(input); else { nonIndexedInputs.push(input); nonIndexedOriginalIndex.push(i); } } // Decode topics (indexed args). for (let i = 0; i < indexedInputs.length; i++) { const param = indexedInputs[i]; const topic = argTopics[i]; if (!topic) throw new TopicsMismatchError({ abiEvent, param: param, }); args[isUnnamed ? i : param.name || i] = (() => { const t = param.type; if (t === 'string' || t === 'bytes' || t === 'tuple' || arraySuffixRegex.test(t)) return topic; // Fast path: 32-byte primitive (`address`, `bool`, `uintN`, // `intN`, `bytesN`). Reads the topic word directly without // allocating a `Cursor` or running `Bytes.fromHex`. const fast = decodePrimitiveTopic(t, topic, options); if (fast) return fast[0]; const decoded = AbiParameters.decode([param], topic, options) || []; return decoded[0]; })(); } // Decode data (non-indexed args). if (nonIndexedInputs.length > 0) { if (data && data !== '0x') { try { const decodedData = AbiParameters.decode(nonIndexedInputs, data, options); if (decodedData) { if (isUnnamed) args = [...args, ...decodedData]; else { for (let i = 0; i < nonIndexedInputs.length; i++) { const index = nonIndexedOriginalIndex[i]; args[nonIndexedInputs[i].name || index] = decodedData[i]; } } } } catch (err) { if (err instanceof AbiParameters.DataSizeTooSmallError || err instanceof Cursor.PositionOutOfBoundsError) throw new DataMismatchError({ abiEvent, data: data, parameters: nonIndexedInputs, size: Hex.size(data), }); throw err; } } else { throw new DataMismatchError({ abiEvent, data: '0x', parameters: nonIndexedInputs, size: 0, }); } } return Object.values(args).length > 0 ? args : undefined; } /** * Extracts an {@link ox#AbiEvent.AbiEvent} from an {@link ox#Abi.Abi} and decodes its arguments from a Log. * * @example * ```ts twoslash * import { Abi, AbiEvent } from 'ox' * * const abi = Abi.from([ * 'event Transfer(address indexed from, address indexed to, uint256 value)' * ]) * * const decoded = AbiEvent.decodeLog(abi, { * data: '0x0000000000000000000000000000000000000000000000000000000000000001', * topics: [ * '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', * '0x000000000000000000000000a5cc3c03994db5b0d9a5eedd10cabab0813678ac', * '0x000000000000000000000000a5cc3c03994db5b0d9a5eedd10cabab0813678ac' * ] * }) * // @log: { * // @log: event: { name: 'Transfer', type: 'event', ... }, * // @log: args: { * // @log: from: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', * // @log: to: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', * // @log: value: 1n, * // @log: }, * // @log: } * ``` * * @param abi - The ABI to extract an event from. * @param log - `topics` & `data` to decode. * @param options - Decoding options. * @returns The decoded event and arguments. */ export function decodeLog(abi, log, options) { const selector = log.topics[0]; if (!selector) throw new SelectorTopicNotFoundError(); const abiEvent = fromAbi(abi, selector); return { event: abiEvent, args: decode(abiEvent, log, options), }; } /** * Extracts and decodes Logs that match an ABI Event in an ABI. * * @example * ```ts twoslash * import { Abi, AbiEvent } from 'ox' * * const abi = Abi.from([ * 'event Transfer(address indexed from, address indexed to, uint256 value)' * ]) * * const logs = AbiEvent.extractLogs(abi, [ * { * data: '0x0000000000000000000000000000000000000000000000000000000000000001', * topics: [ * '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', * '0x000000000000000000000000a5cc3c03994db5b0d9a5eedd10cabab0813678ac', * '0x000000000000000000000000a5cc3c03994db5b0d9a5eedd10cabab0813678ac' * ] * } * ]) * // @log: [{ * // @log: eventName: 'Transfer', * // @log: args: { * // @log: from: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', * // @log: to: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', * // @log: value: 1n, * // @log: }, * // @log: topics: [...], * // @log: data: '0x...', * // @log: }] * ``` * * @param abi - The ABI to extract events from. * @param logs - Logs to extract. * @param options - Extraction options. * @returns The extracted logs. */ export function extractLogs(abi, logs, options = {}) { const { args, checksumAddress, strict = true, } = options; const eventName = (() => { if (!options.eventName) return undefined; if (Array.isArray(options.eventName)) return options.eventName; return [options.eventName]; })(); const abiEvents = abi.filter((item) => item.type === 'event'); const out = []; for (const log of logs) { const log_ = formatExtractLog(log); const selector = log_.topics[0]; if (!selector) continue; const items = abiEvents.filter((item) => getSelector(item) === selector); if (items.length === 0) continue; let event; for (const item of items) { try { event = { args: decodeForExtract(item, log_, { checksumAddress, strict: true, }), event: item, }; break; } catch { } } if (!event && !strict) { const item = items[0]; try { event = { args: decodeForExtract(item, log_, { checksumAddress, strict: false, }), event: item, }; } catch { const isUnnamed = item.inputs.some((x) => !('name' in x && x.name)); event = { args: isUnnamed ? [] : {}, event: item, }; } } if (!event) continue; if (eventName && !eventName.includes(event.event.name)) continue; if (!includesArgs(event.event, event.args, args)) continue; out.push({ ...log_, args: event.args, eventName: event.event.name, }); } return out; } function decodeForExtract(abiEvent, log, options) { const { data, topics } = log; const { strict } = options; const selector = topics[0]; if (selector !== getSelector(abiEvent)) throw new SelectorTopicMismatchError({ abiEvent, actual: selector, expected: getSelector(abiEvent), }); const { inputs } = abiEvent; const isUnnamed = inputs.some((x) => !('name' in x && x.name)); const args = isUnnamed ? [] : {}; const argTopics = topics.slice(1); const indexedInputs = []; const nonIndexedInputs = []; const missingIndexedInputs = []; for (let i = 0; i < inputs.length; i++) { const input = inputs[i]; if ('indexed' in input && input.indexed) indexedInputs.push([input, i]); else nonIndexedInputs.push(input); } for (let i = 0; i < indexedInputs.length; i++) { const [param, index] = indexedInputs[i]; const topic = argTopics[i]; if (!topic) { if (strict) throw new TopicsMismatchError({ abiEvent, param: param, }); missingIndexedInputs.push([param, index]); continue; } args[isUnnamed ? index : param.name || index] = decodeTopic(param, topic, { checksumAddress: options.checksumAddress, }); } const inputsToDecode = strict ? nonIndexedInputs : [...missingIndexedInputs.map(([param]) => param), ...nonIndexedInputs]; if (inputsToDecode.length > 0) { if (data && data !== '0x') { try { const decodedData = AbiParameters.decode(inputsToDecode, data, { checksumAddress: options.checksumAddress, }); if (decodedData) { let dataIndex = 0; if (!strict) { for (const [param, index] of missingIndexedInputs) { args[isUnnamed ? index : param.name || index] = decodedData[dataIndex++]; } } if (isUnnamed) { for (let i = 0; i < inputs.length; i++) if (args[i] === undefined && dataIndex < decodedData.length) args[i] = decodedData[dataIndex++]; } else { for (let i = 0; i < nonIndexedInputs.length; i++) { const input = nonIndexedInputs[i]; args[input.name] = decodedData[dataIndex++]; } } } } catch (err) { if (strict) { if (err instanceof AbiParameters.DataSizeTooSmallError || err instanceof Cursor.PositionOutOfBoundsError) throw new DataMismatchError({ abiEvent, data: data, parameters: inputsToDecode, size: Hex.size(data), }); throw err; } } } else if (strict) { throw new DataMismatchError({ abiEvent, data: '0x', parameters: inputsToDecode, size: 0, }); } } return Object.values(args).length > 0 ? args : undefined; } function decodeTopic(param, topic, options) { const type = param.type; if (type === 'string' || type === 'bytes' || type === 'tuple' || arraySuffixRegex.test(type)) return topic; const fast = decodePrimitiveTopic(type, topic, options); if (fast) return fast[0]; const decoded = AbiParameters.decode([param], topic, options) || []; return decoded[0]; } function formatExtractLog(log) { if (typeof log.blockNumber !== 'string') return log; return { ...log, blockHash: log.blockHash ? log.blockHash : null, blockNumber: log.blockNumber ? BigInt(log.blockNumber) : null, blockTimestamp: log.blockTimestamp ? BigInt(log.blockTimestamp) : log.blockTimestamp === null ? null : undefined, logIndex: log.logIndex ? Number(log.logIndex) : null, transactionHash: log.transactionHash ? log.transactionHash : null, transactionIndex: log.transactionIndex ? Number(log.transactionIndex) : null, }; } function includesArgs(abiEvent, args, matchArgs) { try { if (!matchArgs) return true; assertArgs(abiEvent, args, matchArgs); return true; } catch { return false; } } // eslint-disable-next-line jsdoc-js/require-jsdoc export function encode(...parameters) { const [abiEvent, args] = (() => { if (Array.isArray(parameters[0])) { const [abi, name, args] = parameters; return [fromAbi(abi, name), args]; } const [abiEvent, args] = parameters; return [abiEvent, args]; })(); let topics = []; if (args && abiEvent.inputs) { const indexedInputs = abiEvent.inputs.filter((param) => 'indexed' in param && param.indexed); const args_ = Array.isArray(args) ? args : Object.values(args).length > 0 ? (indexedInputs?.map((x, i) => args[x.name ?? i]) ?? []) : []; if (args_.length > 0) { const encode = (param, value) => { const t = param.type; if (t === 'string') return Hash.keccak256(Hex.fromString(value), { as: 'Hex' }); if (t === 'bytes') { const hex = typeof value === 'string' ? value : Hex.fromBytes(value); return Hash.keccak256(hex, { as: 'Hex' }); } if (t === 'tuple' || arraySuffixRegex.test(t)) throw new FilterTypeNotSupportedError(t); // Fast path for 32-byte primitives. const fast = encodePrimitiveTopic(t, value); if (fast) return fast; return AbiParameters.encode([param], [value]); }; topics = indexedInputs?.map((param, i) => { if (Array.isArray(args_[i])) return args_[i].map((_, j) => encode(param, args_[i][j])); return typeof args_[i] !== 'undefined' && args_[i] !== null ? encode(param, args_[i]) : null; }) ?? []; } } if (abiEvent.anonymous === true) return { topics }; const selector = (() => { if (abiEvent.hash) return abiEvent.hash; return getSelector(abiEvent); })(); return { topics: [selector, ...topics] }; } /** * Formats an {@link ox#AbiEvent.AbiEvent} into a **Human Readable ABI Error**. * * @example * ```ts twoslash * import { AbiEvent } from 'ox' * * const formatted = AbiEvent.format({ * type: 'event', * name: 'Transfer', * inputs: [ * { name: 'from', type: 'address', indexed: true }, * { name: 'to', type: 'address', indexed: true }, * { name: 'value', type: 'uint256' } * ] * }) * * formatted * // ^? * ``` * * @param abiEvent - The ABI Event to format. * @returns The formatted ABI Event. */ export function format(abiEvent) { return formatAbiItem.formatAbiItem(abiEvent); } /** * Parses an arbitrary **JSON ABI Event** or **Human Readable ABI Event** into a typed {@link ox#AbiEvent.AbiEvent}. * * @example * ### JSON ABIs * * ```ts twoslash * import { AbiEvent } from 'ox' * * const transfer = AbiEvent.from({ * name: 'Transfer', * type: 'event', * inputs: [ * { name: 'from', type: 'address', indexed: true }, * { name: 'to', type: 'address', indexed: true }, * { name: 'value', type: 'uint256' } * ] * }) * * transfer * //^? * ``` * * @example * ### Human Readable ABIs * * A Human Readable ABI can be parsed into a typed ABI object: * * ```ts twoslash * import { AbiEvent } from 'ox' * * const transfer = AbiEvent.from( * 'event Transfer(address indexed from, address indexed to, uint256 value)' // [!code hl] * ) * * transfer * //^? * ``` * * @param abiEvent - The ABI Event to parse. * @returns Typed ABI Event. */ export function from(abiEvent, options = {}) { return AbiItem.from(abiEvent, options); } /** * Extracts an {@link ox#AbiEvent.AbiEvent} from an {@link ox#Abi.Abi} given a name and optional arguments. * * @example * ### Extracting by Name * * ABI Events can be extracted by their name using the `name` option: * * ```ts twoslash * import { Abi, AbiEvent } from 'ox' * * const abi = Abi.from([ * 'function foo()', * 'event Transfer(address owner, address to, uint256 tokenId)', * 'function bar(string a) returns (uint256 x)' * ]) * * const item = AbiEvent.fromAbi(abi, 'Transfer') // [!code focus] * // ^? * ``` * * @example * ### Extracting by Selector * * ABI Events can be extract by their selector when {@link ox#Hex.Hex} is provided to `name`. * * ```ts twoslash * import { Abi, AbiEvent } from 'ox' * * const abi = Abi.from([ * 'function foo()', * 'event Transfer(address owner, address to, uint256 tokenId)', * 'function bar(string a) returns (uint256 x)' * ]) * const selector = * '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef' * const item = AbiEvent.fromAbi(abi, selector) // [!code focus] * // ^? * ``` * * :::note * * Extracting via a hex selector is useful when extracting an ABI Event from the first topic of a Log. * * ::: * * @param abi - The ABI to extract from. * @param name - The name (or selector) of the ABI item to extract. * @param options - Extraction options. * @returns The ABI item. */ export function fromAbi(abi, name, options) { const item = AbiItem.fromAbi(abi, name, options); if (item.type !== 'event') throw new AbiItem.NotFoundError({ name, type: 'event' }); return item; } /** * Computes the event selector (hash of event signature) for an {@link ox#AbiEvent.AbiEvent}. * * @example * ```ts twoslash * import { AbiEvent } from 'ox' * * const selector = AbiEvent.getSelector( * 'event Transfer(address indexed from, address indexed to, uint256 value)' * ) * // @log: '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f556a2' * ``` * * @example * ```ts twoslash * import { AbiEvent } from 'ox' * * const selector = AbiEvent.getSelector({ * name: 'Transfer', * type: 'event', * inputs: [ * { name: 'from', type: 'address', indexed: true }, * { name: 'to', type: 'address', indexed: true }, * { name: 'value', type: 'uint256' } * ] * }) * // @log: '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f556a2' * ``` * * @param abiItem - The ABI event to compute the selector for. * @returns The {@link ox#Hash.(keccak256:function)} hash of the event signature. */ export function getSelector(abiItem) { return AbiItem.getSignatureHash(abiItem); } /** * Thrown when the provided arguments do not match the expected arguments. * * @example * ```ts twoslash * import { AbiEvent } from 'ox' * * const abiEvent = AbiEvent.from( * 'event Transfer(address indexed from, address indexed to, uint256 value)' * ) * * const args = AbiEvent.decode(abiEvent, { * data: '0x0000000000000000000000000000000000000000000000000000000000000001', * topics: [ * '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', * '0x000000000000000000000000a5cc3c03994db5b0d9a5eedd10cabab0813678ac', * '0x000000000000000000000000a5cc3c03994db5b0d9a5eedd10cabab0813678ad' * ] * }) * * AbiEvent.assertArgs(abiEvent, args, { * from: '0xa5cc3c03994db5b0d9a5eedd10cabab0813678ad', * to: '0xa5cc3c03994db5b0d9a5eedd10cabab0813678ac', * value: 1n * }) * // @error: AbiEvent.ArgsMismatchError: Given arguments do not match the expected arguments. * // @error: Event: event Transfer(address indexed from, address indexed to, uint256 value) * // @error: Expected Arguments: * // @error: from: 0xa5cc3c03994db5b0d9a5eedd10cabab0813678ac * // @error: to: 0xa5cc3c03994db5b0d9a5eedd10cabab0813678ad * // @error: value: 1 * // @error: Given Arguments: * // @error: from: 0xa5cc3c03994db5b0d9a5eedd10cabab0813678ad * // @error: to: 0xa5cc3c03994db5b0d9a5eedd10cabab0813678ac * // @error: value: 1 * ``` * * ### Solution * * The provided arguments need to match the expected arguments. * * ```ts twoslash * // @noErrors * import { AbiEvent } from 'ox' * * const abiEvent = AbiEvent.from( * 'event Transfer(address indexed from, address indexed to, uint256 value)' * ) * * const args = AbiEvent.decode(abiEvent, { * data: '0x0000000000000000000000000000000000000000000000000000000000000001', * topics: [ * '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', * '0x000000000000000000000000a5cc3c03994db5b0d9a5eedd10cabab0813678ac', * '0x000000000000000000000000a5cc3c03994db5b0d9a5eedd10cabab0813678ad' * ] * }) * * AbiEvent.assertArgs(abiEvent, args, { * from: '0xa5cc3c03994db5b0d9a5eedd10cabab0813678ad', // [!code --] * from: '0xa5cc3c03994db5b0d9a5eedd10cabab0813678ac', // [!code ++] * to: '0xa5cc3c03994db5b0d9a5eedd10cabab0813678ac', // [!code --] * to: '0xa5cc3c03994db5b0d9a5eedd10cabab0813678ad', // [!code ++] * value: 1n * }) * ``` */ export class ArgsMismatchError extends Errors.BaseError { name = 'AbiEvent.ArgsMismatchError'; constructor({ abiEvent, expected, given, }) { super('Given arguments do not match the expected arguments.', { metaMessages: [ `Event: ${format(abiEvent)}`, `Expected Arguments: ${!expected ? 'None' : ''}`, expected ? prettyPrint(expected) : undefined, `Given Arguments: ${!given ? 'None' : ''}`, given ? prettyPrint(given) : undefined, ], }); } } /** * Thrown when no argument was found on the event signature. * * @example * ```ts twoslash * // @noErrors * import { AbiEvent } from 'ox' * * const abiEvent = AbiEvent.from( * 'event Transfer(address indexed from, address indexed to, uint256 value)' * ) * * const args = AbiEvent.decode(abiEvent, { * data: '0x0000000000000000000000000000000000000000000000000000000000000001', * topics: [ * '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', * '0x000000000000000000000000a5cc3c03994db5b0d9a5eedd10cabab0813678ac', * '0x000000000000000000000000a5cc3c03994db5b0d9a5eedd10cabab0813678ad' * ] * }) * * AbiEvent.assertArgs(abiEvent, args, { * a: 'b', * from: '0xa5cc3c03994db5b0d9a5eedd10cabab0813678ac', * to: '0xa5cc3c03994db5b0d9a5eedd10cabab0813678ad', * value: 1n * }) * // @error: AbiEvent.InputNotFoundError: Parameter "a" not found on `event Transfer(address indexed from, address indexed to, uint256 value)`. * ``` * * ### Solution * * Ensure the arguments match the event signature. * * ```ts twoslash * // @noErrors * import { AbiEvent } from 'ox' * * const abiEvent = AbiEvent.from( * 'event Transfer(address indexed from, address indexed to, uint256 value)' * ) * * const args = AbiEvent.decode(abiEvent, { * data: '0x0000000000000000000000000000000000000000000000000000000000000001', * topics: [ * '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', * '0x000000000000000000000000a5cc3c03994db5b0d9a5eedd10cabab0813678ac', * '0x000000000000000000000000a5cc3c03994db5b0d9a5eedd10cabab0813678ad' * ] * }) * * AbiEvent.assertArgs(abiEvent, args, { * a: 'b', // [!code --] * from: '0xa5cc3c03994db5b0d9a5eedd10cabab0813678ac', * to: '0xa5cc3c03994db5b0d9a5eedd10cabab0813678ad', * value: 1n * }) * ``` */ export class InputNotFoundError extends Errors.BaseError { name = 'AbiEvent.InputNotFoundError'; constructor({ abiEvent, name }) { super(`Parameter "${name}" not found on \`${format(abiEvent)}\`.`); } } /** * Thrown when the provided data size does not match the expected size from the non-indexed parameters. * * @example * ```ts twoslash * import { AbiEvent } from 'ox' * * const abiEvent = AbiEvent.from( * 'event Transfer(address indexed from, address to, uint256 value)' * // ↑ 32 bytes + ↑ 32 bytes = 64 bytes * ) * * const args = AbiEvent.decode(abiEvent, { * data: '0x0000000000000000000000000000000000000000000000000000000023c34600', * // ↑ 32 bytes ❌ * topics: [ * '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', * '0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266' * ] * }) * // @error: AbiEvent.DataMismatchError: Data size of 32 bytes is too small for non-indexed event parameters. * // @error: Non-indexed Parameters: (address to, uint256 value) * // @error: Data: 0x0000000000000000000000000000000000000000000000000000000023c34600 (32 bytes) * ``` * * ### Solution * * Ensure that the data size matches the expected size. * * ```ts twoslash * import { AbiEvent } from 'ox' * * const abiEvent = AbiEvent.from( * 'event Transfer(address indexed from, address to, uint256 value)' * // ↑ 32 bytes + ↑ 32 bytes = 64 bytes * ) * * const args = AbiEvent.decode(abiEvent, { * data: '0x0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000023c34600', * // ↑ 64 bytes ✅ * topics: [ * '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', * '0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266' * ] * }) * ``` */ export class DataMismatchError extends Errors.BaseError { name = 'AbiEvent.DataMismatchError'; abiEvent; data; parameters; size; constructor({ abiEvent, data, parameters, size, }) { super([ `Data size of ${size} bytes is too small for non-indexed event parameters.`, ].join('\n'), { metaMessages: [ `Non-indexed Parameters: (${AbiParameters.format(parameters)})`, `Data: ${data} (${size} bytes)`, ], }); this.abiEvent = abiEvent; this.data = data; this.parameters = parameters; this.size = size; } } /** * Thrown when the provided topics do not match the expected number of topics. * * @example * ```ts twoslash * import { AbiEvent } from 'ox' * * const abiEvent = AbiEvent.from( * 'event Transfer(address indexed from, address indexed to, uint256 value)' * ) * * const args = AbiEvent.decode(abiEvent, { * data: '0x0000000000000000000000000000000000000000000000000000000000000001', * topics: [ * '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', * '0x000000000000000000000000a5cc3c03994db5b0d9a5eedd10cabab0813678ac' * ] * }) * // @error: AbiEvent.TopicsMismatchError: Expected a topic for indexed event parameter "to" for "event Transfer(address indexed from, address indexed to, uint256 value)". * ``` * * ### Solution * * Ensure that the topics match the expected number of topics. * * ```ts twoslash * import { AbiEvent } from 'ox' * * const abiEvent = AbiEvent.from( * 'event Transfer(address indexed from, address indexed to, uint256 value)' * ) * * const args = AbiEvent.decode(abiEvent, { * data: '0x0000000000000000000000000000000000000000000000000000000000000001', * topics: [ * '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', * '0x000000000000000000000000a5cc3c03994db5b0d9a5eedd10cabab0813678ac', * '0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266' // [!code ++] * ] * }) * ``` * */ export class TopicsMismatchError extends Errors.BaseError { name = 'AbiEvent.TopicsMismatchError'; abiEvent; constructor({ abiEvent, param, }) { super([ `Expected a topic for indexed event parameter${param.name ? ` "${param.name}"` : ''} for "${format(abiEvent)}".`, ].join('\n')); this.abiEvent = abiEvent; } } /** * Thrown when the provided selector does not match the expected selector. * * @example * ```ts twoslash * import { AbiEvent } from 'ox' * * const transfer = AbiEvent.from( * 'event Transfer(address indexed from, address indexed to, bool sender)' * ) * * AbiEvent.decode(transfer, { * topics: [ * '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', * '0x000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045', * '0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266' * ] * }) * // @error: AbiEvent.SelectorTopicMismatchError: topics[0]="0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" does not match the expected topics[0]="0x3da3cd3cf420c78f8981e7afeefa0eab1f0de0eb56e78ad9ba918ed01c0b402f". * // @error: Event: event Transfer(address indexed from, address indexed to, bool sender) * // @error: Selector: 0x3da3cd3cf420c78f8981e7afeefa0eab1f0de0eb56e78ad9ba918ed01c0b402f * ``` * * ### Solution * * Ensure that the provided selector matches the selector of the event signature. * * ```ts twoslash * import { AbiEvent } from 'ox' * * const transfer = AbiEvent.from( * 'event Transfer(address indexed from, address indexed to, bool sender)' * ) * * AbiEvent.decode(transfer, { * topics: [ * '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', // [!code --] * '0x3da3cd3cf420c78f8981e7afeefa0eab1f0de0eb56e78ad9ba918ed01c0b402f', // [!code ++] * '0x000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045', * '0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266' * ] * }) * ``` */ export class SelectorTopicMismatchError extends Errors.BaseError { name = 'AbiEvent.SelectorTopicMismatchError'; constructor({ abiEvent, actual, expected, }) { super(`topics[0]="${actual}" does not match the expected topics[0]="${expected}".`, { metaMessages: [`Event: ${format(abiEvent)}`, `Selector: ${expected}`], }); } } /** * Thrown when the selector topic is not found. * * @example * ```ts twoslash * import { Abi, AbiEvent } from 'ox' * * const abi = Abi.from([ * 'event Transfer(address indexed from)' * ]) * * AbiEvent.decodeLog(abi, { topics: [], data: '0x' }) * // @error: AbiEvent.SelectorTopicNotFoundError: Selector topic not found. * ``` */ export class SelectorTopicNotFoundError extends Errors.BaseError { name = 'AbiEvent.SelectorTopicNotFoundError'; constructor() { super('Selector topic not found.'); } } /** * Thrown when the provided filter type is not supported. * * @example * ```ts twoslash * import { AbiEvent } from 'ox' * * const transfer = AbiEvent.from( * 'event Transfer((string) indexed a, string b)' * ) * * AbiEvent.encode(transfer, { * a: ['hello'] * }) * // @error: AbiEvent.FilterTypeNotSupportedError: Filter type "tuple" is not supported. * ``` * * ### Solution * * Provide a valid event input type. * * ```ts twoslash * // @noErrors * import { AbiEvent } from 'ox' * * const transfer = AbiEvent.from( * 'event Transfer((string) indexed a, string b)' * ) // [!code --] * const transfer = AbiEvent.from( * 'event Transfer(string indexed a, string b)' * ) // [!code ++] * ``` * * */ export class FilterTypeNotSupportedError extends Errors.BaseError { name = 'AbiEvent.FilterTypeNotSupportedError'; constructor(type) { super(`Filter type "${type}" is not supported.`); } } //# sourceMappingURL=AbiEvent.js.map