You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1056 lines
28 KiB
1056 lines
28 KiB
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 type * as Errors from './Errors.js'
|
|
import * as Hex from './Hex.js'
|
|
import type * as internal from './internal/abiFunction.js'
|
|
import type * as AbiItem_internal from './internal/abiItem.js'
|
|
import type * as AbiParameters_internal from './internal/abiParameters.js'
|
|
import type { IsNarrowable } from './internal/types.js'
|
|
|
|
/** Root type for an {@link ox#AbiItem.AbiItem} with a `function` type. */
|
|
export type AbiFunction = abitype.AbiFunction & {
|
|
hash?: Hex.Hex | undefined
|
|
overloads?: readonly AbiFunction[] | undefined
|
|
}
|
|
|
|
/**
|
|
* Extracts an {@link ox#AbiFunction.AbiFunction} item from an {@link ox#Abi.Abi}, given a name.
|
|
*
|
|
* @example
|
|
* ```ts twoslash
|
|
* import { Abi, AbiFunction } from 'ox'
|
|
*
|
|
* const abi = Abi.from([
|
|
* 'function foo(string)',
|
|
* 'function bar(uint256)',
|
|
* ])
|
|
*
|
|
* type Foo = AbiFunction.FromAbi<typeof abi, 'foo'>
|
|
* // ^?
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
* ```
|
|
*/
|
|
export type FromAbi<
|
|
abi extends Abi.Abi,
|
|
name extends ExtractNames<abi>,
|
|
> = abitype.ExtractAbiFunction<abi, name>
|
|
|
|
/**
|
|
* Extracts the names of all {@link ox#AbiFunction.AbiFunction} items in an {@link ox#Abi.Abi}.
|
|
*
|
|
* @example
|
|
* ```ts twoslash
|
|
* import { Abi, AbiFunction } from 'ox'
|
|
*
|
|
* const abi = Abi.from([
|
|
* 'function foo(string)',
|
|
* 'function bar(uint256)',
|
|
* ])
|
|
*
|
|
* type names = AbiFunction.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,
|
|
abiStateMutability extends
|
|
abitype.AbiStateMutability = abitype.AbiStateMutability,
|
|
> = abitype.ExtractAbiFunctionNames<abi, abiStateMutability>
|
|
|
|
/**
|
|
* ABI-decodes function arguments according to the ABI Item's input types (`inputs`).
|
|
*
|
|
* @example
|
|
* ```ts twoslash
|
|
* import { AbiFunction } from 'ox'
|
|
*
|
|
* const approve = AbiFunction.from('function approve(address, uint256)')
|
|
*
|
|
* const data = AbiFunction.encodeData(
|
|
* approve,
|
|
* ['0xd8da6bf26964af9d7eed9e03e53415d37aa96045', 69420n]
|
|
* )
|
|
* // '0x095ea7b3000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa960450000000000000000000000000000000000000000000000000000000000010f2c'
|
|
*
|
|
* const input = AbiFunction.decodeData(approve, data) // [!code focus]
|
|
* // @log: ['0xd8da6bf26964af9d7eed9e03e53415d37aa96045', 69420n]
|
|
* ```
|
|
*
|
|
* @example
|
|
* ### ABI-shorthand
|
|
*
|
|
* You can also specify an entire ABI object and a function name as parameters to {@link ox#AbiFunction.(decodeData:function)}:
|
|
*
|
|
* ```ts twoslash
|
|
* // @noErrors
|
|
* import { Abi, AbiFunction } from 'ox'
|
|
*
|
|
* const abi = Abi.from([...])
|
|
* const data = '0x...
|
|
*
|
|
* const input = AbiFunction.decodeData(
|
|
* abi, // [!code focus]
|
|
* 'approve', // [!code focus]
|
|
* data
|
|
* )
|
|
* // @log: ['0xd8da6bf26964af9d7eed9e03e53415d37aa96045', 69420n]
|
|
* ```
|
|
*
|
|
* @param abiFunction - The ABI Item to decode.
|
|
* @param data - The data to decode.
|
|
*/
|
|
export function decodeData<
|
|
const abi extends Abi.Abi | readonly unknown[],
|
|
name extends Name<abi>,
|
|
const args extends
|
|
| AbiItem_internal.ExtractArgs<abi, name>
|
|
| undefined = undefined,
|
|
//
|
|
abiFunction extends AbiFunction = AbiItem.fromAbi.ReturnType<
|
|
abi,
|
|
name,
|
|
args,
|
|
AbiFunction
|
|
>,
|
|
allNames = Name<abi>,
|
|
>(
|
|
abi: abi | Abi.Abi | readonly unknown[],
|
|
name: Hex.Hex | (name extends allNames ? name : never),
|
|
data: Hex.Hex,
|
|
): decodeData.ReturnType<abiFunction>
|
|
export function decodeData<const abiItem extends AbiFunction>(
|
|
abiFunction: abiItem | AbiFunction,
|
|
data: Hex.Hex,
|
|
): decodeData.ReturnType<abiItem>
|
|
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
export function decodeData(
|
|
...parameters:
|
|
| [abi: Abi.Abi | readonly unknown[], name: Hex.Hex | string, data: Hex.Hex]
|
|
| [abiFunction: AbiFunction, data: Hex.Hex]
|
|
) {
|
|
const [abiFunction, data] = (() => {
|
|
if (Array.isArray(parameters[0])) {
|
|
const [abi, name, data] = parameters as [
|
|
Abi.Abi | readonly unknown[],
|
|
Hex.Hex | string,
|
|
Hex.Hex,
|
|
]
|
|
return [fromAbi(abi, name), data]
|
|
}
|
|
return parameters as [AbiFunction, Hex.Hex]
|
|
})()
|
|
|
|
const { overloads } = abiFunction
|
|
|
|
if (Hex.size(data) < 4) throw new AbiItem.InvalidSelectorSizeError({ data })
|
|
if (abiFunction.inputs.length === 0) return undefined
|
|
|
|
const item = overloads
|
|
? fromAbi([abiFunction, ...overloads], data as never)
|
|
: abiFunction
|
|
|
|
if (Hex.size(data) <= 4) return undefined
|
|
return AbiParameters.decode(item.inputs, Hex.slice(data, 4))
|
|
}
|
|
|
|
export declare namespace decodeData {
|
|
type ReturnType<abiFunction extends AbiFunction = AbiFunction> = IsNarrowable<
|
|
abiFunction,
|
|
AbiFunction
|
|
> extends true
|
|
? abiFunction['inputs'] extends readonly []
|
|
? undefined
|
|
:
|
|
| AbiParameters_internal.ToPrimitiveTypes<abiFunction['inputs']>
|
|
| (abiFunction['overloads'] extends readonly AbiFunction[]
|
|
? AbiParameters_internal.ToPrimitiveTypes<
|
|
abiFunction['overloads'][number]['inputs']
|
|
>
|
|
: never)
|
|
: unknown
|
|
|
|
type ErrorType =
|
|
| fromAbi.ErrorType
|
|
| AbiParameters.decode.ErrorType
|
|
| Hex.size.ErrorType
|
|
| Hex.slice.ErrorType
|
|
| Errors.GlobalErrorType
|
|
}
|
|
|
|
/**
|
|
* ABI-decodes a function's result according to the ABI Item's output types (`outputs`).
|
|
*
|
|
* :::tip
|
|
*
|
|
* This function is typically used to decode contract function return values (e.g. the response of an `eth_call` or the `input` property of a Transaction).
|
|
*
|
|
* See the [End-to-end Example](#end-to-end).
|
|
*
|
|
* :::
|
|
*
|
|
* @example
|
|
* ```ts twoslash
|
|
* import { AbiFunction } from 'ox'
|
|
*
|
|
* const data = '0x000000000000000000000000000000000000000000000000000000000000002a'
|
|
*
|
|
* const totalSupply = AbiFunction.from('function totalSupply() returns (uint256)')
|
|
*
|
|
* const output = AbiFunction.decodeResult(totalSupply, data)
|
|
* // @log: 42n
|
|
* ```
|
|
*
|
|
* @example
|
|
* You can extract an ABI Function from a JSON ABI with {@link ox#AbiFunction.(fromAbi:function)}:
|
|
*
|
|
* ```ts twoslash
|
|
* // @noErrors
|
|
* import { Abi, AbiFunction } from 'ox'
|
|
*
|
|
* const data = '0x000000000000000000000000000000000000000000000000000000000000002a'
|
|
*
|
|
* const erc20Abi = Abi.from([...]) // [!code hl]
|
|
* const totalSupply = AbiFunction.fromAbi(erc20Abi, 'totalSupply') // [!code hl]
|
|
*
|
|
* const output = AbiFunction.decodeResult(totalSupply, data)
|
|
* // @log: 42n
|
|
* ```
|
|
*
|
|
* @example
|
|
* ### ABI-shorthand
|
|
*
|
|
* You can also specify an entire ABI object and a function name as parameters to {@link ox#AbiFunction.(decodeResult:function)}:
|
|
*
|
|
* ```ts twoslash
|
|
* // @noErrors
|
|
* import { Abi, AbiFunction } from 'ox'
|
|
*
|
|
* const data = '0x000000000000000000000000000000000000000000000000000000000000002a'
|
|
*
|
|
* const erc20Abi = Abi.from([...])
|
|
*
|
|
* const output = AbiFunction.decodeResult(
|
|
* erc20Abi, // [!code focus]
|
|
* 'totalSupply', // [!code focus]
|
|
* data
|
|
* )
|
|
* // @log: 42n
|
|
* ```
|
|
*
|
|
* @example
|
|
* ### End-to-end
|
|
*
|
|
* Below is an end-to-end example of using `AbiFunction.decodeResult` to decode the result of a `balanceOf` contract call on the [Wagmi Mint Example contract](https://etherscan.io/address/0xfba3912ca04dd458c843e2ee08967fc04f3579c2).
|
|
*
|
|
* ```ts twoslash
|
|
* import 'ox/window'
|
|
* import { Abi, AbiFunction } from 'ox'
|
|
*
|
|
* // 1. Extract the Function from the Contract's ABI.
|
|
* const abi = Abi.from([
|
|
* // ...
|
|
* {
|
|
* name: 'balanceOf',
|
|
* type: 'function',
|
|
* inputs: [{ name: 'account', type: 'address' }],
|
|
* outputs: [{ name: 'balance', type: 'uint256' }],
|
|
* stateMutability: 'view',
|
|
* },
|
|
* // ...
|
|
* ])
|
|
* const balanceOf = AbiFunction.fromAbi(abi, 'balanceOf')
|
|
*
|
|
* // 2. Encode the Function Input.
|
|
* const data = AbiFunction.encodeData(
|
|
* balanceOf,
|
|
* ['0xd2135CfB216b74109775236E36d4b433F1DF507B']
|
|
* )
|
|
*
|
|
* // 3. Perform the Contract Call.
|
|
* const response = await window.ethereum!.request({
|
|
* method: 'eth_call',
|
|
* params: [
|
|
* {
|
|
* data,
|
|
* to: '0xfba3912ca04dd458c843e2ee08967fc04f3579c2',
|
|
* },
|
|
* ],
|
|
* })
|
|
*
|
|
* // 4. Decode the Function Output. // [!code focus]
|
|
* const balance = AbiFunction.decodeResult(balanceOf, response) // [!code focus]
|
|
* // @log: 42n
|
|
* ```
|
|
*
|
|
* :::note
|
|
*
|
|
* For simplicity, the above example uses `window.ethereum.request`, but you can use any
|
|
* type of JSON-RPC interface.
|
|
*
|
|
* :::
|
|
*
|
|
* @param abiFunction - ABI Function to decode
|
|
* @param data - ABI-encoded function output
|
|
* @param options - Decoding options
|
|
* @returns Decoded function output
|
|
*/
|
|
export function decodeResult<
|
|
const abi extends Abi.Abi | readonly unknown[],
|
|
name extends Name<abi>,
|
|
const args extends
|
|
| AbiItem_internal.ExtractArgs<abi, name>
|
|
| undefined = undefined,
|
|
//
|
|
abiFunction extends AbiFunction = AbiItem.fromAbi.ReturnType<
|
|
abi,
|
|
name,
|
|
args,
|
|
AbiFunction
|
|
>,
|
|
allNames = Name<abi>,
|
|
as extends 'Object' | 'Array' = 'Array',
|
|
>(
|
|
abi: abi | Abi.Abi | readonly unknown[],
|
|
name: Hex.Hex | (name extends allNames ? name : never),
|
|
data: Hex.Hex,
|
|
options?: decodeResult.Options<as> | undefined,
|
|
): decodeResult.ReturnType<abiFunction, as>
|
|
export function decodeResult<
|
|
const abiFunction extends AbiFunction,
|
|
as extends 'Object' | 'Array' = 'Array',
|
|
>(
|
|
abiFunction: abiFunction | AbiFunction,
|
|
data: Hex.Hex,
|
|
options?: decodeResult.Options<as> | undefined,
|
|
): decodeResult.ReturnType<abiFunction, as>
|
|
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
export function decodeResult(
|
|
...parameters:
|
|
| [
|
|
abi: Abi.Abi | readonly unknown[],
|
|
name: Hex.Hex | string,
|
|
data: Hex.Hex,
|
|
options?: decodeResult.Options | undefined,
|
|
]
|
|
| [
|
|
abiFunction: AbiFunction,
|
|
data: Hex.Hex,
|
|
options?: decodeResult.Options | undefined,
|
|
]
|
|
) {
|
|
const [abiFunction, data, options = {}] = (() => {
|
|
if (Array.isArray(parameters[0])) {
|
|
const [abi, name, data, options] = parameters as [
|
|
Abi.Abi | readonly unknown[],
|
|
Hex.Hex | string,
|
|
Hex.Hex,
|
|
decodeResult.Options | undefined,
|
|
]
|
|
return [fromAbi(abi, name), data, options]
|
|
}
|
|
return parameters as [
|
|
AbiFunction,
|
|
Hex.Hex,
|
|
decodeResult.Options | undefined,
|
|
]
|
|
})()
|
|
|
|
const values = AbiParameters.decode(abiFunction.outputs, data, options)
|
|
if (values && Object.keys(values).length === 0) return undefined
|
|
if (values && Object.keys(values).length === 1) {
|
|
if (Array.isArray(values)) return values[0]
|
|
return Object.values(values)[0]
|
|
}
|
|
return values
|
|
}
|
|
|
|
export declare namespace decodeResult {
|
|
type Options<as extends 'Object' | 'Array' = 'Array'> = {
|
|
/**
|
|
* Whether the decoded values should be returned as an `Object` or `Array`.
|
|
*
|
|
* @default "Array"
|
|
*/
|
|
as?: as | 'Array' | 'Object' | undefined
|
|
}
|
|
|
|
type ReturnType<
|
|
abiFunction extends AbiFunction = AbiFunction,
|
|
as extends 'Object' | 'Array' = 'Array',
|
|
> = IsNarrowable<abiFunction, AbiFunction> extends true
|
|
? abiFunction['outputs'] extends readonly []
|
|
? undefined
|
|
: abiFunction['outputs'] extends readonly [
|
|
infer type extends abitype.AbiParameter,
|
|
]
|
|
? abitype.AbiParameterToPrimitiveType<type>
|
|
: AbiParameters.decode.ReturnType<
|
|
abiFunction['outputs'],
|
|
as
|
|
> extends infer types
|
|
? types extends readonly []
|
|
? undefined
|
|
: types extends readonly [infer type]
|
|
? type
|
|
: types
|
|
: never
|
|
: unknown
|
|
|
|
type ErrorType = AbiParameters.decode.ErrorType | Errors.GlobalErrorType
|
|
}
|
|
|
|
/**
|
|
* ABI-encodes function arguments (`inputs`), prefixed with the 4 byte function selector.
|
|
*
|
|
* :::tip
|
|
*
|
|
* This function is typically used to encode a contract function and its arguments for contract calls (e.g. `data` parameter of an `eth_call` or `eth_sendTransaction`).
|
|
*
|
|
* See the [End-to-end Example](#end-to-end).
|
|
*
|
|
* :::
|
|
*
|
|
* @example
|
|
* ```ts twoslash
|
|
* import { AbiFunction } from 'ox'
|
|
*
|
|
* const approve = AbiFunction.from('function approve(address, uint256)')
|
|
*
|
|
* const data = AbiFunction.encodeData( // [!code focus]
|
|
* approve, // [!code focus]
|
|
* ['0xd8da6bf26964af9d7eed9e03e53415d37aa96045', 69420n] // [!code focus]
|
|
* ) // [!code focus]
|
|
* // @log: '0x095ea7b3000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa960450000000000000000000000000000000000000000000000000000000000010f2c'
|
|
* ```
|
|
*
|
|
* @example
|
|
* You can extract an ABI Function from a JSON ABI with {@link ox#AbiFunction.(fromAbi:function)}:
|
|
*
|
|
* ```ts twoslash
|
|
* // @noErrors
|
|
* import { Abi, AbiFunction } from 'ox'
|
|
*
|
|
* const erc20Abi = Abi.from([...]) // [!code hl]
|
|
* const approve = AbiFunction.fromAbi(erc20Abi, 'approve') // [!code hl]
|
|
*
|
|
* const data = AbiFunction.encodeData(
|
|
* approve,
|
|
* ['0xd8da6bf26964af9d7eed9e03e53415d37aa96045', 69420n]
|
|
* )
|
|
* // @log: '0x095ea7b3000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa960450000000000000000000000000000000000000000000000000000000000010f2c'
|
|
* ```
|
|
*
|
|
* @example
|
|
* ### ABI-shorthand
|
|
*
|
|
* You can specify an entire ABI object and a function name as parameters to {@link ox#AbiFunction.(encodeData:function)}:
|
|
*
|
|
* ```ts twoslash
|
|
* // @noErrors
|
|
* import { Abi, AbiFunction } from 'ox'
|
|
*
|
|
* const erc20Abi = Abi.from([...])
|
|
*
|
|
* const data = AbiFunction.encodeData(
|
|
* erc20Abi, // [!code focus]
|
|
* 'approve', // [!code focus]
|
|
* ['0xd8da6bf26964af9d7eed9e03e53415d37aa96045', 69420n]
|
|
* )
|
|
* ```
|
|
*
|
|
* @example
|
|
* ### End-to-end
|
|
*
|
|
* Below is an end-to-end example of using `AbiFunction.encodeData` to encode the input of a `balanceOf` contract call on the [Wagmi Mint Example contract](https://etherscan.io/address/0xfba3912ca04dd458c843e2ee08967fc04f3579c2).
|
|
*
|
|
* ```ts twoslash
|
|
* import 'ox/window'
|
|
* import { Abi, AbiFunction } from 'ox'
|
|
*
|
|
* // 1. Extract the Function from the Contract's ABI.
|
|
* const abi = Abi.from([
|
|
* // ...
|
|
* {
|
|
* name: 'balanceOf',
|
|
* type: 'function',
|
|
* inputs: [{ name: 'account', type: 'address' }],
|
|
* outputs: [{ name: 'balance', type: 'uint256' }],
|
|
* stateMutability: 'view',
|
|
* },
|
|
* // ...
|
|
* ])
|
|
* const balanceOf = AbiFunction.fromAbi(abi, 'balanceOf')
|
|
*
|
|
* // 2. Encode the Function Input. // [!code focus]
|
|
* const data = AbiFunction.encodeData( // [!code focus]
|
|
* balanceOf, // [!code focus]
|
|
* ['0xd2135CfB216b74109775236E36d4b433F1DF507B'] // [!code focus]
|
|
* ) // [!code focus]
|
|
*
|
|
* // 3. Perform the Contract Call.
|
|
* const response = await window.ethereum!.request({
|
|
* method: 'eth_call',
|
|
* params: [
|
|
* {
|
|
* data,
|
|
* to: '0xfba3912ca04dd458c843e2ee08967fc04f3579c2',
|
|
* },
|
|
* ],
|
|
* })
|
|
*
|
|
* // 4. Decode the Function Output.
|
|
* const balance = AbiFunction.decodeResult(balanceOf, response)
|
|
* ```
|
|
*
|
|
* :::note
|
|
*
|
|
* For simplicity, the above example uses `window.ethereum.request`, but you can use any
|
|
* type of JSON-RPC interface.
|
|
*
|
|
* :::
|
|
*
|
|
* @param abiFunction - ABI Function to encode
|
|
* @param args - Function arguments
|
|
* @returns ABI-encoded function name and arguments
|
|
*/
|
|
export function encodeData<
|
|
const abi extends Abi.Abi | readonly unknown[],
|
|
name extends Name<abi>,
|
|
const args extends
|
|
| AbiItem_internal.ExtractArgs<abi, name>
|
|
| undefined = undefined,
|
|
//
|
|
abiFunction extends AbiFunction = AbiItem.fromAbi.ReturnType<
|
|
abi,
|
|
name,
|
|
args,
|
|
AbiFunction
|
|
>,
|
|
allNames = Name<abi>,
|
|
>(
|
|
abi: abi | Abi.Abi | readonly unknown[],
|
|
name: Hex.Hex | (name extends allNames ? name : never),
|
|
...args: encodeData.Args<abiFunction>
|
|
): Hex.Hex
|
|
export function encodeData<const abiFunction extends AbiFunction>(
|
|
abiFunction: abiFunction | AbiFunction,
|
|
...args: encodeData.Args<abiFunction>
|
|
): Hex.Hex
|
|
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
export function encodeData(
|
|
...parameters:
|
|
| [
|
|
abi: Abi.Abi | readonly unknown[],
|
|
name: Hex.Hex | string,
|
|
...args: readonly unknown[],
|
|
]
|
|
| [abiFunction: AbiFunction, ...args: readonly unknown[]]
|
|
) {
|
|
const [abiFunction, args = []] = (() => {
|
|
if (Array.isArray(parameters[0])) {
|
|
const [abi, name, args] = parameters as [
|
|
Abi.Abi | readonly unknown[],
|
|
Hex.Hex | string,
|
|
readonly unknown[],
|
|
]
|
|
return [fromAbi(abi, name, { args }), args]
|
|
}
|
|
const [abiFunction, args] = parameters as [AbiFunction, readonly unknown[]]
|
|
return [abiFunction, args]
|
|
})()
|
|
|
|
const { overloads } = abiFunction
|
|
|
|
const item = overloads
|
|
? (fromAbi([abiFunction as AbiFunction, ...overloads], abiFunction.name, {
|
|
args,
|
|
}) as AbiFunction)
|
|
: abiFunction
|
|
|
|
const selector = getSelector(item)
|
|
|
|
const data =
|
|
args.length > 0 ? AbiParameters.encode(item.inputs, args) : undefined
|
|
|
|
return data ? Hex.concat(selector, data) : selector
|
|
}
|
|
|
|
export declare namespace encodeData {
|
|
type Args<abiFunction extends AbiFunction = AbiFunction> = IsNarrowable<
|
|
abiFunction,
|
|
AbiFunction
|
|
> extends true
|
|
?
|
|
| (abitype.AbiParametersToPrimitiveTypes<
|
|
abiFunction['inputs']
|
|
> extends readonly []
|
|
? []
|
|
: [abitype.AbiParametersToPrimitiveTypes<abiFunction['inputs']>])
|
|
| (abiFunction['overloads'] extends readonly AbiFunction[]
|
|
? [
|
|
abitype.AbiParametersToPrimitiveTypes<
|
|
abiFunction['overloads'][number]['inputs']
|
|
>,
|
|
]
|
|
: [])
|
|
: readonly unknown[]
|
|
|
|
type ErrorType = Errors.GlobalErrorType
|
|
}
|
|
|
|
/**
|
|
* ABI-encodes a function's result (`outputs`).
|
|
*
|
|
* @example
|
|
* ```ts twoslash
|
|
* import { AbiFunction } from 'ox'
|
|
*
|
|
* const totalSupply = AbiFunction.from('function totalSupply() returns (uint256)')
|
|
* const output = AbiFunction.decodeResult(totalSupply, '0x000000000000000000000000000000000000000000000000000000000000002a')
|
|
* // 42n
|
|
*
|
|
* const data = AbiFunction.encodeResult(totalSupply, 42n) // [!code focus]
|
|
* // @log: '0x000000000000000000000000000000000000000000000000000000000000002a'
|
|
* ```
|
|
*
|
|
* @example
|
|
* ### ABI-shorthand
|
|
*
|
|
* You can also specify an entire ABI object and a function name as parameters to {@link ox#AbiFunction.(encodeResult:function)}:
|
|
*
|
|
* ```ts twoslash
|
|
* // @noErrors
|
|
* import { Abi, AbiFunction } from 'ox'
|
|
*
|
|
* const abi = Abi.from([...])
|
|
*
|
|
* const data = AbiFunction.encodeResult(
|
|
* abi, // [!code focus]
|
|
* 'totalSupply', // [!code focus]
|
|
* 42n
|
|
* )
|
|
* // @log: '0x000000000000000000000000000000000000000000000000000000000000002a'
|
|
* ```
|
|
*
|
|
* @param abiFunction - The ABI item to encode the function output for.
|
|
* @param output - The function output to encode.
|
|
* @param options - Encoding options.
|
|
* @returns The encoded function output.
|
|
*/
|
|
export function encodeResult<
|
|
const abi extends Abi.Abi | readonly unknown[],
|
|
name extends Name<abi>,
|
|
const args extends
|
|
| AbiItem_internal.ExtractArgs<abi, name>
|
|
| undefined = undefined,
|
|
as extends 'Object' | 'Array' = 'Array',
|
|
//
|
|
abiFunction extends AbiFunction = AbiItem.fromAbi.ReturnType<
|
|
abi,
|
|
name,
|
|
args,
|
|
AbiFunction
|
|
>,
|
|
allNames = Name<abi>,
|
|
>(
|
|
abi: abi | Abi.Abi | readonly unknown[],
|
|
name: Hex.Hex | (name extends allNames ? name : never),
|
|
output: encodeResult.Output<abiFunction, as>,
|
|
options?: encodeResult.Options<as>,
|
|
): Hex.Hex
|
|
export function encodeResult<
|
|
const abiFunction extends AbiFunction,
|
|
as extends 'Object' | 'Array' = 'Array',
|
|
>(
|
|
abiFunction: abiFunction | AbiFunction,
|
|
output: encodeResult.Output<abiFunction, as>,
|
|
options?: encodeResult.Options<as>,
|
|
): Hex.Hex
|
|
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
export function encodeResult(
|
|
...parameters:
|
|
| [
|
|
abi: Abi.Abi | readonly unknown[],
|
|
name: Hex.Hex | string,
|
|
output: any,
|
|
options?: encodeResult.Options<any> | undefined,
|
|
]
|
|
| [
|
|
abiFunction: AbiFunction,
|
|
output: any,
|
|
options?: encodeResult.Options<any> | undefined,
|
|
]
|
|
) {
|
|
const [abiFunction, output, options = {}] = (() => {
|
|
if (Array.isArray(parameters[0])) {
|
|
const [abi, name, output, options] = parameters as [
|
|
Abi.Abi | readonly unknown[],
|
|
Hex.Hex | string,
|
|
any,
|
|
encodeResult.Options<any> | undefined,
|
|
]
|
|
return [fromAbi(abi, name), output, options]
|
|
}
|
|
return parameters as [
|
|
AbiFunction,
|
|
any,
|
|
encodeResult.Options<any> | undefined,
|
|
]
|
|
})()
|
|
|
|
const { as = 'Array' } = options
|
|
|
|
const values = (() => {
|
|
if (abiFunction.outputs.length === 1) return [output]
|
|
if (Array.isArray(output)) return output
|
|
if (as === 'Object') return Object.values(output as any)
|
|
return [output]
|
|
})()
|
|
|
|
return AbiParameters.encode(abiFunction.outputs, values)
|
|
}
|
|
|
|
export declare namespace encodeResult {
|
|
type Output<
|
|
abiFunction extends AbiFunction = AbiFunction,
|
|
as extends 'Object' | 'Array' = 'Array',
|
|
> = abiFunction['outputs'] extends readonly []
|
|
? never
|
|
: abiFunction['outputs']['length'] extends 1
|
|
? AbiParameters_internal.ToPrimitiveTypes<abiFunction['outputs']>[0]
|
|
: as extends 'Object'
|
|
? AbiParameters_internal.ToObject<abiFunction['outputs']>
|
|
: AbiParameters_internal.ToPrimitiveTypes<abiFunction['outputs']>
|
|
|
|
type Options<as extends 'Object' | 'Array'> = {
|
|
as?: as | 'Object' | 'Array' | undefined
|
|
}
|
|
|
|
type ErrorType = AbiParameters.encode.ErrorType | Errors.GlobalErrorType
|
|
}
|
|
|
|
/**
|
|
* Formats an {@link ox#AbiFunction.AbiFunction} into a **Human Readable ABI Function**.
|
|
*
|
|
* @example
|
|
* ```ts twoslash
|
|
* import { AbiFunction } from 'ox'
|
|
*
|
|
* const formatted = AbiFunction.format({
|
|
* type: 'function',
|
|
* name: 'approve',
|
|
* stateMutability: 'nonpayable',
|
|
* inputs: [
|
|
* {
|
|
* name: 'spender',
|
|
* type: 'address',
|
|
* },
|
|
* {
|
|
* name: 'amount',
|
|
* type: 'uint256',
|
|
* },
|
|
* ],
|
|
* outputs: [{ type: 'bool' }],
|
|
* })
|
|
*
|
|
* formatted
|
|
* // ^?
|
|
*
|
|
*
|
|
* ```
|
|
*
|
|
* @param abiFunction - The ABI Function to format.
|
|
* @returns The formatted ABI Function.
|
|
*/
|
|
export function format<const abiFunction extends AbiFunction>(
|
|
abiFunction: abiFunction | AbiFunction,
|
|
): abitype.FormatAbiItem<abiFunction> {
|
|
return abitype.formatAbiItem(abiFunction) as never
|
|
}
|
|
|
|
export declare namespace format {
|
|
type ErrorType = Errors.GlobalErrorType
|
|
}
|
|
|
|
/**
|
|
* Parses an arbitrary **JSON ABI Function** or **Human Readable ABI Function** into a typed {@link ox#AbiFunction.AbiFunction}.
|
|
*
|
|
* @example
|
|
* ### JSON ABIs
|
|
*
|
|
* ```ts twoslash
|
|
* import { AbiFunction } from 'ox'
|
|
*
|
|
* const approve = AbiFunction.from({
|
|
* type: 'function',
|
|
* name: 'approve',
|
|
* stateMutability: 'nonpayable',
|
|
* inputs: [
|
|
* {
|
|
* name: 'spender',
|
|
* type: 'address',
|
|
* },
|
|
* {
|
|
* name: 'amount',
|
|
* type: 'uint256',
|
|
* },
|
|
* ],
|
|
* outputs: [{ type: 'bool' }],
|
|
* })
|
|
*
|
|
* approve
|
|
* //^?
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
* ```
|
|
*
|
|
* @example
|
|
* ### Human Readable ABIs
|
|
*
|
|
* A Human Readable ABI can be parsed into a typed ABI object:
|
|
*
|
|
* ```ts twoslash
|
|
* import { AbiFunction } from 'ox'
|
|
*
|
|
* const approve = AbiFunction.from(
|
|
* 'function approve(address spender, uint256 amount) returns (bool)' // [!code hl]
|
|
* )
|
|
*
|
|
* approve
|
|
* //^?
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
* ```
|
|
*
|
|
* @example
|
|
* It is possible to specify `struct`s along with your definitions:
|
|
*
|
|
* ```ts twoslash
|
|
* import { AbiFunction } from 'ox'
|
|
*
|
|
* const approve = AbiFunction.from([
|
|
* 'struct Foo { address spender; uint256 amount; }', // [!code hl]
|
|
* 'function approve(Foo foo) returns (bool)',
|
|
* ])
|
|
*
|
|
* approve
|
|
* //^?
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
* ```
|
|
*
|
|
*
|
|
*
|
|
* @param abiFunction - The ABI Function to parse.
|
|
* @returns Typed ABI Function.
|
|
*/
|
|
export function from<
|
|
const abiFunction extends AbiFunction | string | readonly string[],
|
|
>(
|
|
abiFunction: (abiFunction | AbiFunction | string | readonly string[]) &
|
|
(
|
|
| (abiFunction extends string ? internal.Signature<abiFunction> : never)
|
|
| (abiFunction extends readonly string[]
|
|
? internal.Signatures<abiFunction>
|
|
: never)
|
|
| AbiFunction
|
|
),
|
|
options: from.Options = {},
|
|
): from.ReturnType<abiFunction> {
|
|
return AbiItem.from(abiFunction as AbiFunction, options) as never
|
|
}
|
|
|
|
export declare namespace from {
|
|
type Options = {
|
|
/**
|
|
* Whether or not to prepare the extracted function (optimization for encoding performance).
|
|
* When `true`, the `hash` property is computed and included in the returned value.
|
|
*
|
|
* @default true
|
|
*/
|
|
prepare?: boolean | undefined
|
|
}
|
|
|
|
type ReturnType<
|
|
abiFunction extends AbiFunction | string | readonly string[],
|
|
> = AbiItem.from.ReturnType<abiFunction>
|
|
|
|
type ErrorType = AbiItem.from.ErrorType | Errors.GlobalErrorType
|
|
}
|
|
|
|
/**
|
|
* Extracts an {@link ox#AbiFunction.AbiFunction} from an {@link ox#Abi.Abi} given a name and optional arguments.
|
|
*
|
|
* @example
|
|
* ### Extracting by Name
|
|
*
|
|
* ABI Functions can be extracted by their name using the `name` option:
|
|
*
|
|
* ```ts twoslash
|
|
* import { Abi, AbiFunction } 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 = AbiFunction.fromAbi(abi, 'foo') // [!code focus]
|
|
* // ^?
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
* ```
|
|
*
|
|
* @example
|
|
* ### Extracting by Selector
|
|
*
|
|
* ABI Functions can be extract by their selector when {@link ox#Hex.Hex} is provided to `name`.
|
|
*
|
|
* ```ts twoslash
|
|
* import { Abi, AbiFunction } 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 = AbiFunction.fromAbi(abi, '0x095ea7b3') // [!code focus]
|
|
* // ^?
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
* ```
|
|
*
|
|
* :::note
|
|
*
|
|
* Extracting via a hex selector is useful when extracting an ABI Function from an `eth_call` RPC response or
|
|
* from a Transaction `input`.
|
|
*
|
|
* :::
|
|
*
|
|
* @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, AbiFunction> {
|
|
const item = AbiItem.fromAbi(abi, name, options as any)
|
|
if (item.type !== 'function')
|
|
throw new AbiItem.NotFoundError({ name, type: 'function' })
|
|
return item as never
|
|
}
|
|
|
|
export declare namespace fromAbi {
|
|
type ErrorType = AbiItem.fromAbi.ErrorType | Errors.GlobalErrorType
|
|
}
|
|
|
|
/**
|
|
* Computes the [4-byte selector](https://solidity-by-example.org/function-selector/) for an {@link ox#AbiFunction.AbiFunction}.
|
|
*
|
|
* Useful for computing function selectors for calldata.
|
|
*
|
|
* @example
|
|
* ```ts twoslash
|
|
* import { AbiFunction } from 'ox'
|
|
*
|
|
* const selector = AbiFunction.getSelector('function ownerOf(uint256 tokenId)')
|
|
* // @log: '0x6352211e'
|
|
* ```
|
|
*
|
|
* @example
|
|
* ```ts twoslash
|
|
* import { AbiFunction } from 'ox'
|
|
*
|
|
* const selector = AbiFunction.getSelector({
|
|
* inputs: [{ type: 'uint256' }],
|
|
* name: 'ownerOf',
|
|
* outputs: [],
|
|
* stateMutability: 'view',
|
|
* type: 'function'
|
|
* })
|
|
* // @log: '0x6352211e'
|
|
* ```
|
|
*
|
|
* @param abiItem - The ABI item to compute the selector for.
|
|
* @returns The first 4 bytes of the {@link ox#Hash.(keccak256:function)} hash of the function signature.
|
|
*/
|
|
export function getSelector(abiItem: string | AbiFunction): Hex.Hex {
|
|
return AbiItem.getSelector(abiItem)
|
|
}
|
|
|
|
export declare namespace getSelector {
|
|
type ErrorType = AbiItem.getSelector.ErrorType | Errors.GlobalErrorType
|
|
}
|