UNPKG

viem

Version:

TypeScript Interface for Ethereum

253 lines (230 loc) • 8.17 kB
import type { Abi, AbiParameter, Address } from 'abitype' import { AbiItemAmbiguityError, type AbiItemAmbiguityErrorType, } from '../../errors/abi.js' import type { ErrorType } from '../../errors/utils.js' import type { AbiItem, AbiItemArgs, AbiItemName, ExtractAbiItemForArgs, Widen, } from '../../types/contract.js' import type { Hex } from '../../types/misc.js' import type { UnionEvaluate } from '../../types/utils.js' import { type IsHexErrorType, isHex } from '../../utils/data/isHex.js' import { type IsAddressErrorType, isAddress } from '../address/isAddress.js' import { toEventSelector } from '../hash/toEventSelector.js' import { type ToFunctionSelectorErrorType, toFunctionSelector, } from '../hash/toFunctionSelector.js' export type GetAbiItemParameters< abi extends Abi | readonly unknown[] = Abi, name extends AbiItemName<abi> = AbiItemName<abi>, args extends AbiItemArgs<abi, name> | undefined = AbiItemArgs<abi, name>, /// allArgs = AbiItemArgs<abi, name>, allNames = AbiItemName<abi>, > = { abi: abi name: | allNames // show all options | (name extends allNames ? name : never) // infer value | Hex // function selector } & UnionEvaluate< readonly [] extends allArgs ? { args?: | allArgs // show all options // infer value, widen inferred value of `args` conditionally to match `allArgs` | (abi extends Abi ? args extends allArgs ? Widen<args> : never : never) | undefined } : { args?: | allArgs // show all options | (Widen<args> & (args extends allArgs ? unknown : never)) // infer value, widen inferred value of `args` match `allArgs` (e.g. avoid union `args: readonly [123n] | readonly [bigint]`) | undefined } > export type GetAbiItemErrorType = | IsArgOfTypeErrorType | IsHexErrorType | ToFunctionSelectorErrorType | AbiItemAmbiguityErrorType | ErrorType export type GetAbiItemReturnType< abi extends Abi | readonly unknown[] = Abi, name extends AbiItemName<abi> = AbiItemName<abi>, args extends AbiItemArgs<abi, name> | undefined = AbiItemArgs<abi, name>, > = abi extends Abi ? Abi extends abi ? AbiItem | undefined : ExtractAbiItemForArgs< abi, name, args extends AbiItemArgs<abi, name> ? args : AbiItemArgs<abi, name> > : AbiItem | undefined export function getAbiItem< const abi extends Abi | readonly unknown[], name extends AbiItemName<abi>, const args extends AbiItemArgs<abi, name> | undefined = undefined, >( parameters: GetAbiItemParameters<abi, name, args>, ): GetAbiItemReturnType<abi, name, args> { const { abi, args = [], name } = parameters as unknown as GetAbiItemParameters const isSelector = isHex(name, { strict: false }) const abiItems = (abi as Abi).filter((abiItem) => { if (isSelector) { if (abiItem.type === 'function') return toFunctionSelector(abiItem) === name if (abiItem.type === 'event') return toEventSelector(abiItem) === name return false } return 'name' in abiItem && abiItem.name === name }) if (abiItems.length === 0) return undefined as GetAbiItemReturnType<abi, name, args> if (abiItems.length === 1) return abiItems[0] as GetAbiItemReturnType<abi, name, args> let matchedAbiItem: AbiItem | undefined = undefined for (const abiItem of abiItems) { if (!('inputs' in abiItem)) continue if (!args || args.length === 0) { if (!abiItem.inputs || abiItem.inputs.length === 0) return abiItem as GetAbiItemReturnType<abi, name, args> continue } if (!abiItem.inputs) continue if (abiItem.inputs.length === 0) continue if (abiItem.inputs.length !== args.length) continue const matched = args.every((arg, index) => { const abiParameter = 'inputs' in abiItem && abiItem.inputs![index] if (!abiParameter) return false return isArgOfType(arg, abiParameter) }) if (matched) { // Check for ambiguity against already matched parameters (e.g. `address` vs `bytes20`). if ( matchedAbiItem && 'inputs' in matchedAbiItem && matchedAbiItem.inputs ) { const ambiguousTypes = getAmbiguousTypes( abiItem.inputs, matchedAbiItem.inputs, args as readonly unknown[], ) if (ambiguousTypes) throw new AbiItemAmbiguityError( { abiItem, type: ambiguousTypes[0], }, { abiItem: matchedAbiItem, type: ambiguousTypes[1], }, ) } matchedAbiItem = abiItem } } if (matchedAbiItem) return matchedAbiItem as GetAbiItemReturnType<abi, name, args> return abiItems[0] as GetAbiItemReturnType<abi, name, args> } type IsArgOfTypeErrorType = IsAddressErrorType | ErrorType /** @internal */ export function isArgOfType(arg: unknown, abiParameter: AbiParameter): boolean { const argType = typeof arg const abiParameterType = abiParameter.type switch (abiParameterType) { case 'address': return isAddress(arg as Address, { strict: false }) case 'bool': return argType === 'boolean' case 'function': return argType === 'string' case 'string': return argType === 'string' default: { if (abiParameterType === 'tuple' && 'components' in abiParameter) return Object.values(abiParameter.components).every( (component, index) => { return isArgOfType( Object.values(arg as unknown[] | Record<string, unknown>)[index], component as AbiParameter, ) }, ) // `(u)int<M>`: (un)signed integer type of `M` bits, `0 < M <= 256`, `M % 8 == 0` // https://regexr.com/6v8hp if ( /^u?int(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/.test( abiParameterType, ) ) return argType === 'number' || argType === 'bigint' // `bytes<M>`: binary type of `M` bytes, `0 < M <= 32` // https://regexr.com/6va55 if (/^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/.test(abiParameterType)) return argType === 'string' || arg instanceof Uint8Array // fixed-length (`<type>[M]`) and dynamic (`<type>[]`) arrays // https://regexr.com/6va6i if (/[a-z]+[1-9]{0,3}(\[[0-9]{0,}\])+$/.test(abiParameterType)) { return ( Array.isArray(arg) && arg.every((x: unknown) => isArgOfType(x, { ...abiParameter, // Pop off `[]` or `[M]` from end of type type: abiParameterType.replace(/(\[[0-9]{0,}\])$/, ''), } as AbiParameter), ) ) } return false } } } /** @internal */ export function getAmbiguousTypes( sourceParameters: readonly AbiParameter[], targetParameters: readonly AbiParameter[], args: AbiItemArgs, ): AbiParameter['type'][] | undefined { for (const parameterIndex in sourceParameters) { const sourceParameter = sourceParameters[parameterIndex] const targetParameter = targetParameters[parameterIndex] if ( sourceParameter.type === 'tuple' && targetParameter.type === 'tuple' && 'components' in sourceParameter && 'components' in targetParameter ) return getAmbiguousTypes( sourceParameter.components, targetParameter.components, (args as any)[parameterIndex], ) const types = [sourceParameter.type, targetParameter.type] const ambiguous = (() => { if (types.includes('address') && types.includes('bytes20')) return true if (types.includes('address') && types.includes('string')) return isAddress(args[parameterIndex] as Address, { strict: false }) if (types.includes('address') && types.includes('bytes')) return isAddress(args[parameterIndex] as Address, { strict: false }) return false })() if (ambiguous) return types } return }