Skip to content

Commit 541d6ef

Browse files
committed
feat: add human-readable abi*
1 parent 8c68d23 commit 541d6ef

File tree

17 files changed

+3263
-3
lines changed

17 files changed

+3263
-3
lines changed

src/core/AbiParameter.ts

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
import type * as abitype from 'abitype'
2+
3+
import * as Errors from './Errors.js'
4+
import type { AssertName } from './internal/humanReadable/types/signatures.js'
5+
import type { Modifier } from './internal/humanReadable/types/signatures.js'
6+
import * as internal_regex from './internal/regex.js'
7+
import type { IsNarrowable, Join } from './internal/types.js'
8+
9+
/** Root type for ABI parameters. */
10+
export type AbiParameter = abitype.AbiParameter
11+
12+
/**
13+
*
14+
*/
15+
export function format<
16+
const abiParameter extends AbiParameter | abitype.AbiEventParameter,
17+
>(abiParameter: abiParameter): format.ReturnType<abiParameter>
18+
19+
// eslint-disable-next-line jsdoc/require-jsdoc
20+
export function format(abiParameter: AbiParameter): string {
21+
let type = abiParameter.type
22+
if (
23+
internal_regex.tupleAbiParameterType.test(abiParameter.type) &&
24+
'components' in abiParameter
25+
) {
26+
type = '('
27+
const length = abiParameter.components.length as number
28+
for (let i = 0; i < length; i++) {
29+
const component = abiParameter.components[i]!
30+
type += format(component)
31+
if (i < length - 1) type += ', '
32+
}
33+
const result = internal_regex.execTyped<{ array?: string }>(
34+
internal_regex.tupleAbiParameterType,
35+
abiParameter.type,
36+
)
37+
type += `)${result?.array ?? ''}`
38+
return format({
39+
...abiParameter,
40+
type,
41+
})
42+
}
43+
// Add `indexed` to type if in `abiParameter`
44+
if ('indexed' in abiParameter && abiParameter.indexed)
45+
type = `${type} indexed`
46+
// Return human-readable ABI parameter
47+
if (abiParameter.name) return `${type} ${abiParameter.name}`
48+
return type
49+
}
50+
51+
export declare namespace format {
52+
type ReturnType<
53+
abiParameter extends AbiParameter | abitype.AbiEventParameter,
54+
> = abiParameter extends {
55+
name?: infer name extends string
56+
type: `tuple${infer array}`
57+
components: infer components extends readonly AbiParameter[]
58+
indexed?: infer indexed extends boolean
59+
}
60+
? format.ReturnType<
61+
{
62+
type: `(${Join<
63+
{
64+
[key in keyof components]: format.ReturnType<
65+
{
66+
type: components[key]['type']
67+
} & (IsNarrowable<components[key]['name'], string> extends true
68+
? { name: components[key]['name'] }
69+
: unknown) &
70+
(components[key] extends {
71+
components: readonly AbiParameter[]
72+
}
73+
? { components: components[key]['components'] }
74+
: unknown)
75+
>
76+
},
77+
', '
78+
>})${array}`
79+
} & (IsNarrowable<name, string> extends true
80+
? { name: name }
81+
: unknown) &
82+
(IsNarrowable<indexed, boolean> extends true
83+
? { indexed: indexed }
84+
: unknown)
85+
>
86+
: `${abiParameter['type']}${abiParameter extends { indexed: true }
87+
? ' indexed'
88+
: ''}${abiParameter['name'] extends infer name extends string
89+
? name extends ''
90+
? ''
91+
: ` ${AssertName<name>}`
92+
: ''}`
93+
94+
type ErrorType = Errors.GlobalErrorType
95+
}
96+
97+
export class InvalidAbiParameterError extends Errors.BaseError {
98+
override readonly name = 'AbiParameter.InvalidAbiParameterError'
99+
100+
constructor({ param }: { param: string | object }) {
101+
super('Failed to parse ABI parameter.', {
102+
details: `parseAbiParameter(${JSON.stringify(param, null, 2)})`,
103+
})
104+
}
105+
}
106+
107+
export class InvalidAbiParametersError extends Errors.BaseError {
108+
override readonly name = 'AbiParameter.InvalidAbiParametersError'
109+
110+
constructor({ params }: { params: string | object }) {
111+
super('Failed to parse ABI parameters.', {
112+
details: `parseAbiParameters(${JSON.stringify(params, null, 2)})`,
113+
})
114+
}
115+
}
116+
117+
export class InvalidParameterError extends Errors.BaseError {
118+
override readonly name = 'AbiParameter.InvalidParameterError'
119+
120+
constructor({ param }: { param: string }) {
121+
super('Invalid ABI parameter.', {
122+
details: param,
123+
})
124+
}
125+
}
126+
127+
export class SolidityProtectedKeywordError extends Errors.BaseError {
128+
override readonly name = 'AbiParameter.SolidityProtectedKeywordError'
129+
130+
constructor({ param, name }: { param: string; name: string }) {
131+
super('Invalid ABI parameter.', {
132+
details: param,
133+
metaMessages: [
134+
`"${name}" is a protected Solidity keyword. More info: https://docs.soliditylang.org/en/latest/cheatsheet.html`,
135+
],
136+
})
137+
}
138+
}
139+
140+
export class InvalidModifierError extends Errors.BaseError {
141+
override readonly name = 'AbiParameter.InvalidModifierError'
142+
143+
constructor({
144+
param,
145+
type,
146+
modifier,
147+
}: {
148+
param: string
149+
type?: abitype.AbiItemType | 'struct' | undefined
150+
modifier: Modifier
151+
}) {
152+
super('Invalid ABI parameter.', {
153+
details: param,
154+
metaMessages: [
155+
`Modifier "${modifier}" not allowed${
156+
type ? ` in "${type}" type` : ''
157+
}.`,
158+
],
159+
})
160+
}
161+
}
162+
163+
export class InvalidFunctionModifierError extends Errors.BaseError {
164+
override readonly name = 'AbiParameter.InvalidFunctionModifierError'
165+
166+
constructor({
167+
param,
168+
type,
169+
modifier,
170+
}: {
171+
param: string
172+
type?: abitype.AbiItemType | 'struct' | undefined
173+
modifier: Modifier
174+
}) {
175+
super('Invalid ABI parameter.', {
176+
details: param,
177+
metaMessages: [
178+
`Modifier "${modifier}" not allowed${
179+
type ? ` in "${type}" type` : ''
180+
}.`,
181+
`Data location can only be specified for array, struct, or mapping types, but "${modifier}" was given.`,
182+
],
183+
})
184+
}
185+
}
186+
187+
export class InvalidAbiTypeParameterError extends Errors.BaseError {
188+
override readonly name = 'AbiParameter.InvalidAbiTypeParameterError'
189+
190+
constructor({
191+
abiParameter,
192+
}: {
193+
abiParameter: AbiParameter & { indexed?: boolean | undefined }
194+
}) {
195+
super('Invalid ABI parameter.', {
196+
details: JSON.stringify(abiParameter, null, 2),
197+
metaMessages: ['ABI parameter type is invalid.'],
198+
})
199+
}
200+
}

src/core/Address.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ import * as Caches from './Caches.js'
44
import * as Errors from './Errors.js'
55
import * as Hash from './Hash.js'
66
import * as PublicKey from './PublicKey.js'
7-
8-
const addressRegex = /*#__PURE__*/ /^0x[a-fA-F0-9]{40}$/
7+
import * as internal_regex from './internal/regex.js'
98

109
/** Root type for Address. */
1110
export type Address = abitype_Address
@@ -37,7 +36,7 @@ export function assert(
3736
): asserts value is Address {
3837
const { strict = true } = options
3938

40-
if (!addressRegex.test(value))
39+
if (!internal_regex.address.test(value))
4140
throw new InvalidAddressError({
4241
address: value,
4342
cause: new InvalidInputError(),

src/core/_test/AbiParameter.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { AbiParameter } from 'ox'
2+
import { describe, expect, test } from 'vitest'
3+
4+
describe('format', () => {
5+
test('default', () => {
6+
const formatted = AbiParameter.format({
7+
name: 'spender',
8+
type: 'address',
9+
})
10+
expect(formatted).toMatchInlineSnapshot(`"address spender"`)
11+
})
12+
})
13+
14+
test('exports', () => {
15+
expect(Object.keys(AbiParameter)).toMatchInlineSnapshot(`
16+
[
17+
"format",
18+
]
19+
`)
20+
})

0 commit comments

Comments
 (0)