ox
Version:
Ethereum Standard Library
1,200 lines • 40.8 kB
JavaScript
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