ox
Version:
Ethereum Standard Library
1,660 lines (1,579 loc) • 59.3 kB
text/typescript
import * as abitype from 'abitype'
import type * as Abi from './Abi.js'
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 type * as internal from './internal/abiEvent.js'
import type * as AbiItem_internal from './internal/abiItem.js'
import * as Cursor from './internal/cursor.js'
import { prettyPrint } from './internal/errors.js'
import * as formatAbiItem from './internal/human-readable/formatAbiItem.js'
import type { Compute, IsNarrowable } from './internal/types.js'
/** Root type for an {@link ox#AbiItem.AbiItem} with an `event` type. */
export type AbiEvent = abitype.AbiEvent & {
hash?: Hex.Hex | undefined
overloads?: readonly AbiEvent[] | undefined
}
/**
* 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: string,
value: unknown,
): Hex.Hex | undefined {
if (type === 'address') {
Address.assert(value as Address.Address, { strict: false })
return `0x000000000000000000000000${(value as string).slice(2).toLowerCase()}` as Hex.Hex
}
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 as number | bigint, { 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 as Hex.Hex, 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: string,
topic: Hex.Hex,
options: decode.Options = {},
): [value: unknown] | undefined {
if (type === 'address') {
// Trailing 20 bytes; topic is always 66 chars (`0x` + 64 hex).
const address = `0x${topic.slice(26)}` as Address.Address
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)}` as Hex.Hex]
}
return undefined
}
/**
* Extracts an {@link ox#AbiEvent.AbiEvent} item from an {@link ox#Abi.Abi}, given a name.
*
* @example
* ```ts twoslash
* import { Abi, AbiEvent } from 'ox'
*
* const abi = Abi.from([
* 'event Foo(string)',
* 'event Bar(uint256)'
* ])
*
* type Foo = AbiEvent.FromAbi<typeof abi, 'Foo'>
* // ^?
* ```
*/
export type FromAbi<
abi extends Abi.Abi,
name extends ExtractNames<abi>,
> = abitype.ExtractAbiEvent<abi, name>
/**
* Extracts the names of all {@link ox#AbiError.AbiError} items in an {@link ox#Abi.Abi}.
*
* @example
* ```ts twoslash
* import { Abi, AbiEvent } from 'ox'
*
* const abi = Abi.from([
* 'event Foo(string)',
* 'event Bar(uint256)'
* ])
*
* type names = AbiEvent.Name<typeof abi>
* // ^?
* ```
*/
export type Name<abi extends Abi.Abi | readonly unknown[] = Abi.Abi> =
abi extends Abi.Abi ? ExtractNames<abi> : string
export type ExtractNames<abi extends Abi.Abi> =
abitype.ExtractAbiEventNames<abi>
/**
* 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<const abiEvent extends AbiEvent>(
abiEvent: abiEvent | AbiEvent,
args: unknown,
matchArgs: IsNarrowable<abiEvent, AbiEvent> extends true
? abiEvent['inputs'] extends readonly []
? never
: internal.ParametersToPrimitiveTypes<
abiEvent['inputs'],
{ EnableUnion: true; IndexedOnly: false; Required: false }
>
: unknown,
) {
if (!args || !matchArgs)
throw new ArgsMismatchError({
abiEvent,
expected: args,
given: matchArgs,
})
function isEqual(
input: abitype.AbiEventParameter,
value: unknown,
arg: unknown,
) {
if (input.type === 'address')
return Address.isEqual(value as Address.Address, arg as Address.Address)
if (input.type === 'string')
return (
Hash.keccak256(Bytes.fromString(value as string), { as: 'Hex' }) === arg
)
if (input.type === 'bytes') {
const hex =
typeof value === 'string'
? (value as Hex.Hex)
: Hex.fromBytes(value as Bytes.Bytes)
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 as Record<string, unknown>)[key]))
equal = true
}
if (!equal)
throw new ArgsMismatchError({
abiEvent,
expected: args,
given: matchArgs,
})
}
}
export declare namespace assertArgs {
type ErrorType =
| Address.isEqual.ErrorType
| Bytes.fromString.ErrorType
| Hash.keccak256.ErrorType
| ArgsMismatchError
| Errors.GlobalErrorType
}
/**
* ABI-Decodes the provided [Log Topics and Data](https://info.etherscan.com/what-is-event-logs/) according to the ABI Event's parameter types (`input`).
*
* :::tip
*
* This function is typically used to decode an [Event Log](https://info.etherscan.com/what-is-event-logs/) that may be returned from a Log Query (e.g. `eth_getLogs`) or Transaction Receipt.
*
* See the [End-to-end Example](#end-to-end).
*
* :::
*
* @example
* ```ts twoslash
* import { AbiEvent } from 'ox'
*
* const transfer = AbiEvent.from(
* 'event Transfer(address indexed from, address indexed to, uint256 value)'
* )
*
* const log = {
* // ...
* data: '0x0000000000000000000000000000000000000000000000000000000000000001',
* topics: [
* '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
* '0x000000000000000000000000a5cc3c03994db5b0d9a5eedd10cabab0813678ac',
* '0x000000000000000000000000a5cc3c03994db5b0d9a5eedd10cabab0813678ac'
* ]
* } as const
*
* const decoded = AbiEvent.decode(transfer, log)
* // @log: {
* // @log: from: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC',
* // @log: to: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC',
* // @log: value: 1n
* // @log: }
* ```
*
* @example
* ### ABI-shorthand
*
* You can also specify an entire ABI object and an event name as parameters to {@link ox#AbiEvent.(decode:function)}:
*
* ```ts twoslash
* // @noErrors
* import { Abi, AbiEvent } from 'ox'
*
* const abi = Abi.from([...])
* const log = {
* // ...
* data: '0x0000000000000000000000000000000000000000000000000000000000000001',
* topics: [
* '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
* '0x000000000000000000000000a5cc3c03994db5b0d9a5eedd10cabab0813678ac',
* '0x000000000000000000000000a5cc3c03994db5b0d9a5eedd10cabab0813678ac',
* ],
* } as const
*
* const decoded = AbiEvent.decode(
* abi, // [!code focus]
* 'Transfer', // [!code focus]
* log
* )
* // @log: {
* // @log: from: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC',
* // @log: to: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC',
* // @log: value: 1n
* // @log: }
* ```
*
* @example
* ### End-to-end
*
* Below is an end-to-end example of using `AbiEvent.decode` to decode the topics of a `Transfer` event on the [Wagmi Mint Example contract](https://etherscan.io/address/0xfba3912ca04dd458c843e2ee08967fc04f3579c2).
*
* ```ts twoslash
* import 'ox/window'
* import { AbiEvent, Hex } from 'ox'
*
* // 1. Instantiate the `Transfer` ABI Event.
* const transfer = AbiEvent.from(
* 'event Transfer(address indexed from, address indexed to, uint256 value)'
* )
*
* // 2. Encode the ABI Event into Event Topics.
* const { topics } = AbiEvent.encode(transfer)
*
* // 3. Query for events matching the encoded Topics.
* const logs = await window.ethereum!.request({
* method: 'eth_getLogs',
* params: [
* {
* address: '0xfba3912ca04dd458c843e2ee08967fc04f3579c2',
* fromBlock: Hex.fromNumber(19760235n),
* toBlock: Hex.fromNumber(19760240n),
* topics
* }
* ]
* })
*
* // 4. Decode the Log. // [!code focus]
* const decoded = AbiEvent.decode(transfer, logs[0]!) // [!code focus]
* // @log: {
* // @log: from: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC',
* // @log: to: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC',
* // @log: value: 603n
* // @log: }
* ```
*
* :::note
*
* For simplicity, the above example uses `window.ethereum.request`, but you can use any
* type of JSON-RPC interface.
*
* :::
*
* @param abiEvent - The ABI Event to decode.
* @param log - `topics` & `data` to decode.
* @param options - Decoding options.
* @returns The decoded event.
*/
export function decode<
const abi extends Abi.Abi | readonly unknown[],
name extends Name<abi>,
const args extends AbiItem_internal.ExtractArgs<abi, name> | undefined =
undefined,
//
abiEvent extends AbiEvent = AbiItem.fromAbi.ReturnType<
abi,
name,
args,
AbiEvent
>,
allNames = Name<abi>,
>(
abi: abi | Abi.Abi | readonly unknown[],
name: Hex.Hex | (name extends allNames ? name : never),
log: decode.Log,
options?: decode.Options,
): decode.ReturnType<abiEvent>
export function decode<const abiEvent extends AbiEvent>(
abiEvent: abiEvent | AbiEvent,
log: decode.Log,
options?: decode.Options,
): decode.ReturnType<abiEvent>
// eslint-disable-next-line jsdoc-js/require-jsdoc
export function decode(
...parameters:
| [
abi: Abi.Abi | readonly unknown[],
name: Hex.Hex | string,
log: decode.Log,
options?: decode.Options | undefined,
]
| [
abiEvent: AbiEvent,
log: decode.Log,
options?: decode.Options | undefined,
]
): decode.ReturnType {
const [abiEvent, log, options] = (() => {
if (Array.isArray(parameters[0])) {
const [abi, name, log, options] = parameters as [
Abi.Abi | readonly unknown[],
Hex.Hex | string,
decode.Log,
decode.Options | undefined,
]
return [fromAbi(abi, name), log, options] as const
}
const [abiEvent, log, options] = parameters as [
AbiEvent,
decode.Log,
decode.Options | undefined,
]
return [abiEvent, log, options] as const
})()
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: any = 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: abitype.AbiEventParameter[] = []
const nonIndexedInputs: abitype.AbiEventParameter[] = []
const nonIndexedOriginalIndex: number[] = []
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 as abitype.AbiParameter & { indexed: boolean },
})
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
}
export declare namespace decode {
type Log = {
data?: Hex.Hex | undefined
topics: readonly Hex.Hex[]
}
type Options = {
/**
* Whether decoded addresses should be checksummed.
*
* @default true
*/
checksumAddress?: boolean | undefined
}
type ReturnType<abiEvent extends AbiEvent = AbiEvent> =
IsNarrowable<abiEvent, AbiEvent> extends true
? abiEvent['inputs'] extends readonly []
? undefined
: internal.ParametersToPrimitiveTypes<
abiEvent['inputs'],
{ EnableUnion: false; IndexedOnly: false; Required: true }
>
: unknown
type ErrorType =
| AbiParameters.decode.ErrorType
| getSelector.ErrorType
| DataMismatchError
| SelectorTopicMismatchError
| TopicsMismatchError
| Errors.GlobalErrorType
}
/**
* 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<const abi extends Abi.Abi | readonly unknown[]>(
abi: abi | Abi.Abi | readonly unknown[],
log: decodeLog.Log,
options?: decodeLog.Options,
): decodeLog.ReturnType<decodeLog.ExtractEvent<abi>> {
const selector = log.topics[0]
if (!selector) throw new SelectorTopicNotFoundError()
const abiEvent = fromAbi(abi, selector) as decodeLog.ExtractEvent<abi>
return {
event: abiEvent,
args: decode(abiEvent, log, options),
}
}
export declare namespace decodeLog {
type ExtractEvent<abi extends Abi.Abi | readonly unknown[]> =
AbiItem.fromAbi.ReturnType<abi, Name<abi>, undefined, AbiEvent>
type Log = decode.Log
type Options = decode.Options
type ReturnType<abiEvent extends AbiEvent = AbiEvent> = {
event: abiEvent
args: decode.ReturnType<abiEvent>
}
type ErrorType =
| decode.ErrorType
| fromAbi.ErrorType
| SelectorTopicNotFoundError
| Errors.GlobalErrorType
}
/**
* 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<
const abi extends Abi.Abi | readonly unknown[],
const logs extends readonly extractLogs.Log[],
eventName extends
| extractLogs.EventName<abi>
| readonly extractLogs.EventName<abi>[]
| undefined = undefined,
strict extends boolean | undefined = true,
>(
abi: abi | Abi.Abi | readonly unknown[],
logs: logs | readonly extractLogs.Log[],
options: extractLogs.Options<abi, eventName, strict> = {},
): extractLogs.ReturnType<
extractLogs.ExtractEvent<abi, eventName>,
logs[number],
strict
>[] {
const {
args,
checksumAddress,
strict = true,
} = options as extractLogs.Options
const eventName = (() => {
if (!options.eventName) return undefined
if (Array.isArray(options.eventName)) return options.eventName
return [options.eventName as string]
})()
const abiEvents = (abi as Abi.Abi).filter(
(item): item is AbiEvent => item.type === 'event',
)
const out: extractLogs.ReturnType[] = []
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:
| {
args: unknown
event: AbiEvent
}
| undefined
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 as never
}
export declare namespace extractLogs {
type EventName<abi extends Abi.Abi | readonly unknown[]> = Name<abi>
type ExtractEvent<
abi extends Abi.Abi | readonly unknown[],
eventName extends EventName<abi> | readonly EventName<abi>[] | undefined =
EventName<abi>,
> = AbiItem.fromAbi.ReturnType<
abi,
eventName extends readonly (infer name)[]
? name & EventName<abi>
: eventName extends EventName<abi>
? eventName
: EventName<abi>,
undefined,
AbiEvent
>
type Args<abiEvent extends AbiEvent = AbiEvent> =
IsNarrowable<abiEvent, AbiEvent> extends true
? abiEvent['inputs'] extends readonly []
? never
: internal.ParametersToPrimitiveTypes<
abiEvent['inputs'],
{ EnableUnion: true; IndexedOnly: false; Required: false }
>
: unknown
type Log = decode.Log & {
address?: Address.Address | undefined
blockHash?: Hex.Hex | null | undefined
blockNumber?: bigint | Hex.Hex | null | undefined
blockTimestamp?: bigint | Hex.Hex | null | undefined
logIndex?: number | Hex.Hex | null | undefined
removed?: boolean | undefined
transactionHash?: Hex.Hex | null | undefined
transactionIndex?: number | Hex.Hex | null | undefined
}
type Options<
abi extends Abi.Abi | readonly unknown[] = Abi.Abi,
eventName extends EventName<abi> | readonly EventName<abi>[] | undefined =
EventName<abi>,
strict extends boolean | undefined = boolean | undefined,
abiEvent extends AbiEvent = ExtractEvent<abi, eventName>,
> = decode.Options & {
/** Arguments to match against decoded event arguments. */
args?: Args<abiEvent> | undefined
/** Event name, or event names, to extract. */
eventName?:
| eventName
| EventName<abi>
| readonly EventName<abi>[]
| undefined
/**
* Whether to strictly decode log topics and data.
*
* @default true
*/
strict?: strict | boolean | undefined
}
type ReturnType<
abiEvent extends AbiEvent = AbiEvent,
log extends Log = Log,
strict extends boolean | undefined = boolean | undefined,
> = Compute<
log & {
args: IsNarrowable<abiEvent, AbiEvent> extends true
? abiEvent['inputs'] extends readonly []
? undefined
: internal.ParametersToPrimitiveTypes<
abiEvent['inputs'],
{
EnableUnion: false
IndexedOnly: false
Required: strict extends boolean ? strict : false
}
>
: unknown
eventName: abiEvent['name']
}
>
type ErrorType =
| AbiParameters.decode.ErrorType
| decode.ErrorType
| getSelector.ErrorType
| Errors.GlobalErrorType
}
function decodeForExtract(
abiEvent: AbiEvent,
log: decode.Log,
options: decode.Options & { strict: boolean },
) {
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: any = isUnnamed ? [] : {}
const argTopics = topics.slice(1)
const indexedInputs: [abitype.AbiEventParameter, number][] = []
const nonIndexedInputs: abitype.AbiEventParameter[] = []
const missingIndexedInputs: [abitype.AbiEventParameter, number][] = []
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 as abitype.AbiParameter & { indexed: boolean },
})
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: abitype.AbiEventParameter,
topic: Hex.Hex,
options: decode.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<const log extends extractLogs.Log>(log: 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: AbiEvent, args: unknown, matchArgs: unknown) {
try {
if (!matchArgs) return true
assertArgs(abiEvent, args, matchArgs)
return true
} catch {
return false
}
}
/**
* ABI-encodes the provided event input (`inputs`) into an array of [Event Topics](https://info.etherscan.com/what-is-event-logs/).
*
* :::tip
*
* This function is typically used to encode event arguments into [Event Topics](https://info.etherscan.com/what-is-event-logs/).
*
* See the [End-to-end Example](#end-to-end).
*
* :::
*
* @example
* ```ts twoslash
* import { AbiEvent } from 'ox'
*
* const transfer = AbiEvent.from(
* 'event Transfer(address indexed from, address indexed to, uint256 value)'
* )
*
* const { topics } = AbiEvent.encode(transfer)
* // @log: ['0x406dade31f7ae4b5dbc276258c28dde5ae6d5c2773c5745802c493a2360e55e0']
* ```
*
* @example
* ### Passing Arguments
*
* You can pass `indexed` parameter values to `AbiEvent.encode`.
*
* TypeScript types will be inferred from the ABI Event, to guard you from inserting the wrong values.
*
* For example, the `Transfer` event below accepts an `address` type for the `from` and `to` attributes.
*
* ```ts twoslash
* import { AbiEvent } from 'ox'
*
* const transfer = AbiEvent.from(
* 'event Transfer(address indexed from, address indexed to, uint256 value)'
* )
*
* const { topics } = AbiEvent.encode(transfer, {
* from: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', // [!code hl]
* to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8' // [!code hl]
* })
* // @log: [
* // @log: '0x406dade31f7ae4b5dbc276258c28dde5ae6d5c2773c5745802c493a2360e55e0',
* // @log: '0x00000000000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266',
* // @log: '0x0000000000000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8'
* // @log: ]
* ```
*
* @example
* ### ABI-shorthand
*
* You can also specify an entire ABI object and an event name as parameters to {@link ox#AbiEvent.(encode:function)}:
*
* ```ts twoslash
* // @noErrors
* import { Abi, AbiEvent } from 'ox'
*
* const abi = Abi.from([...])
*
* const { topics } = AbiEvent.encode(
* abi, // [!code focus]
* 'Transfer', // [!code focus]
* {
* from: '0xf39fd6e51aad88f6f4ce6ab882779cfffb92266', // [!code focus]
* to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
* }
* )
* // @log: [
* // @log: '0x406dade31f7ae4b5dbc276258c28dde5ae6d5c2773c5745802c493a2360e55e0',
* // @log: '0x00000000000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266',
* // @log: '0x0000000000000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8'
* // @log: ]
* ```
*
* @example
* ### End-to-end
*
* Below is an end-to-end example of using `AbiEvent.encode` to encode the topics of a `Transfer` event and query for events matching the encoded topics on the [Wagmi Mint Example contract](https://etherscan.io/address/0xfba3912ca04dd458c843e2ee08967fc04f3579c2).
*
* ```ts twoslash
* import 'ox/window'
* import { AbiEvent, Hex } from 'ox'
*
* // 1. Instantiate the `Transfer` ABI Event.
* const transfer = AbiEvent.from(
* 'event Transfer(address indexed from, address indexed to, uint256 value)'
* )
*
* // 2. Encode the ABI Event into Event Topics.
* const { topics } = AbiEvent.encode(transfer)
*
* // 3. Query for events matching the encoded Topics.
* const logs = await window.ethereum!.request({
* method: 'eth_getLogs',
* params: [
* {
* address: '0xfba3912ca04dd458c843e2ee08967fc04f3579c2',
* fromBlock: Hex.fromNumber(19760235n),
* toBlock: Hex.fromNumber(19760240n),
* topics
* }
* ]
* })
* // @log: [
* // @log: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
* // @log: "0x0000000000000000000000000000000000000000000000000000000000000000",
* // @log: "0x0000000000000000000000000c04d9e9278ec5e4d424476d3ebec70cb5d648d1",
* // @log: "0x000000000000000000000000000000000000000000000000000000000000025b",
* // @log: ]
* ```
*
* :::note
*
* For simplicity, the above example uses `window.ethereum.request`, but you can use any
* type of JSON-RPC interface.
*
* :::
*
* @param abiEvent - The event to encode.
* @param args - The arguments to encode.
* @returns The encoded event topics.
*/
export function encode<
const abi extends Abi.Abi | readonly unknown[],
name extends Name<abi>,
const args extends AbiItem_internal.ExtractArgs<abi, name> | undefined =
undefined,
//
abiEvent extends AbiEvent = AbiItem.fromAbi.ReturnType<
abi,
name,
args,
AbiEvent
>,
allNames = Name<abi>,
>(
abi: abi | Abi.Abi | readonly unknown[],
name: Hex.Hex | (name extends allNames ? name : never),
...[args]: encode.Args<abiEvent>
): encode.ReturnType
export function encode<const abiEvent extends AbiEvent>(
abiEvent: abiEvent | AbiEvent,
...[args]: encode.Args<abiEvent>
): encode.ReturnType
// eslint-disable-next-line jsdoc-js/require-jsdoc
export function encode(
...parameters:
| [
abi: Abi.Abi | readonly unknown[],
name: Hex.Hex | string,
args?: readonly unknown[] | Record<string, unknown>,
]
| [abiEvent: AbiEvent, args?: readonly unknown[] | Record<string, unknown>]
): encode.ReturnType {
const [abiEvent, args] = (() => {
if (Array.isArray(parameters[0])) {
const [abi, name, args] = parameters as [
Abi.Abi | readonly unknown[],
Hex.Hex | string,
readonly unknown[] | Record<string, unknown> | undefined,
]
return [fromAbi(abi, name), args]
}
const [abiEvent, args] = parameters as [
AbiEvent,
readonly unknown[] | Record<string, unknown> | undefined,
]
return [abiEvent, args]
})()
let topics: (Hex.Hex | Hex.Hex[] | null)[] = []
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: any, i: number) => (args as any)[x.name ?? i],
) ?? [])
: []
if (args_.length > 0) {
const encode = (param: abitype.AbiParameter, value: unknown) => {
const t = param.type
if (t === 'string')
return Hash.keccak256(Hex.fromString(value as string), { as: 'Hex' })
if (t === 'bytes') {
const hex =
typeof value === 'string'
? (value as Hex.Hex)
: Hex.fromBytes(value as Bytes.Bytes)
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((_: any, j: number) =>
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] }
}
export declare namespace encode {
type Args<abiEvent extends AbiEvent> =
IsNarrowable<abiEvent, AbiEvent> extends true
? abiEvent['inputs'] extends readonly []
? []
: internal.ParametersToPrimitiveTypes<
abiEvent['inputs']
> extends infer result
? result extends readonly []
? []
: [result] | []
: []
: [readonly unknown[] | Record<string, unknown>] | []
type ReturnType = {
topics:
| Compute<[selector: Hex.Hex, ...(Hex.Hex | readonly Hex.Hex[] | null)[]]>
| readonly (Hex.Hex | readonly Hex.Hex[] | null)[]
}
type ErrorType =
| AbiParameters.encode.ErrorType
| getSelector.ErrorType
| Hex.fromString.ErrorType
| Hash.keccak256.ErrorType
| Errors.GlobalErrorType
}
/**
* 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<const abiEvent extends AbiEvent>(
abiEvent: abiEvent | AbiEvent,
): format.ReturnType<abiEvent> {
return formatAbiItem.formatAbiItem(abiEvent) as never
}
export declare namespace format {
type ReturnType<abiEvent extends AbiEvent = AbiEvent> =
formatAbiItem.FormatAbiItem<abiEvent>
type ErrorType = Errors.GlobalErrorType
}
/**
* 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<
const abiEvent extends AbiEvent | string | readonly string[],
>(
abiEvent: (abiEvent | AbiEvent | string | readonly string[]) &
(
| (abiEvent extends string ? internal.Signature<abiEvent> : never)
| (abiEvent extends readonly string[]
? internal.Signatures<abiEvent>
: never)
| AbiEvent
),
options: from.Options = {},
): from.ReturnType<abiEvent> {
return AbiItem.from(abiEvent as AbiEvent, options) as never
}
export declare namespace from {
type Options = {
/**
* Whether or not to prepare the extracted event (optimization for encoding performance).
* When `true`, the `hash` property is computed and included in the returned value.
*
* @default true
*/
prepare?: boolean | undefined
}
type ReturnType<abiEvent extends AbiEvent | string | readonly string[]> =
AbiItem.from.ReturnType<abiEvent>
type ErrorType = AbiItem.from.ErrorType | Errors.GlobalErrorType
}
/**
* 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<
const abi extends Abi.Abi | readonly unknown[],
name extends Name<abi>,
const args extends AbiItem_internal.ExtractArgs<abi, name> | undefined =
undefined,
//
allNames = Name<abi>,
>(
abi: abi | Abi.Abi | readonly unknown[],
name: Hex.Hex | (name extends allNames ? name : never),
options?: AbiItem.fromAbi.Options<
abi,
name,
args,
AbiItem_internal.ExtractArgs<abi, name>
>,
): AbiItem.fromAbi.ReturnType<abi, name, args, AbiEvent> {
const item = AbiItem.fromAbi(abi, name, options as any)
if (item.type !== 'event')
throw new AbiItem.NotFoundError({ name, type: 'event' })
return item as never
}
export declare namespace fromAbi {
type ErrorType = AbiItem.fromAbi.ErrorType | Errors.GlobalErrorType
}
/**
* 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: string | AbiEvent): Hex.Hex {
return AbiItem.getSignatureHash(abiItem)
}
export declare namespace getSelector {
type ErrorType = AbiItem.getSignatureHash.ErrorType | Errors.GlobalErrorType
}
/**
* 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 {
override readonly name = 'AbiEvent.ArgsMismatchError'
constructor({
abiEvent,
expected,
given,
}: {
abiEvent: AbiEvent
expected: unknown
given: unknown
}) {
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 indexe