Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Changes config to add support for bytes input/return types #78

Merged
merged 10 commits into from
Feb 7, 2023
2 changes: 1 addition & 1 deletion src/abi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export type Abi = readonly (AbiFunction | AbiEvent | AbiError)[]
export type TypedDataDomain = {
chainId?: string | number | bigint
name?: string
salt?: ResolvedConfig['BytesType']
salt?: ResolvedConfig['BytesInputType']
verifyingContract?: Address
version?: string
}
Expand Down
7 changes: 5 additions & 2 deletions src/config.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ test('Config', () => {
type AddressType = ResolvedConfig['AddressType']
assertType<AddressType>('0x0000000000000000000000000000000000000000')

type BytesType = ResolvedConfig['BytesType']
assertType<BytesType>('0xfoobarbaz')
type BytesTypeReturn = ResolvedConfig['BytesReturnType']
assertType<BytesTypeReturn>('0xfoobarbaz')

type BytesTypeArgs = ResolvedConfig['BytesInputType']
assertType<BytesTypeArgs>(new Uint8Array(Buffer.from('0xfoobarbaz')))

type IntType = ResolvedConfig['IntType']
assertType<IntType>(123)
Expand Down
21 changes: 16 additions & 5 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ export interface DefaultConfig {

/** TypeScript type to use for `address` values */
AddressType: `0x${string}`
/** TypeScript type to use for `bytes` values */
BytesType: `0x${string}`
/** TypeScript type to use for `bytes` return values */
BytesReturnType: `0x${string}`
/** TypeScript type to use for `bytes` input values */
BytesInputType: `0x${string}` | Uint8Array
/** TypeScript type to use for `int<M>` and `uint<M>` values, where `M > 48` */
BigIntType: bigint
/** TypeScript type to use for `int<M>` and `uint<M>` values, where `M <= 48` */
Expand Down Expand Up @@ -80,13 +82,22 @@ export interface ResolvedConfig {
AddressType: IsUnknown<Config['AddressType']> extends true
? DefaultConfig['AddressType']
: Config['AddressType']

/**
* TypeScript type to use for `bytes` values
* @default `0x${string}`
*/
BytesInputType: IsUnknown<Config['BytesInputType']> extends true
? DefaultConfig['BytesInputType']
: Config['BytesInputType']

/**
* TypeScript type to use for `bytes` values
* @default `0x${string}`
*/
BytesType: IsUnknown<Config['BytesType']> extends true
? DefaultConfig['BytesType']
: Config['BytesType']
BytesReturnType: IsUnknown<Config['BytesReturnType']> extends true
? DefaultConfig['BytesReturnType']
: Config['BytesReturnType']
/**
* TypeScript type to use for `int<M>` and `uint<M>` values, where `M > 48`
* @default bigint
Expand Down
17 changes: 16 additions & 1 deletion src/examples/readContracts.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,21 @@ test('readContracts', () => {
)
})

test('Read bytes input types', () => {
const result = readContracts({
contracts: [
{
address,
abi: wagmiMintExampleAbi,
functionName: 'supportsInterface',
args: [new Uint8Array(Buffer.from('0xfoobarbaz'))],
//^?
},
],
})
assertType<[boolean]>(result)
})

test('two or more', () => {
const result = readContracts({
contracts: [
Expand Down Expand Up @@ -111,7 +126,7 @@ test('readContracts', () => {
},
],
})
assertType<[void, ResolvedConfig['BytesType']]>(result)
assertType<[void, ResolvedConfig['BytesReturnType']]>(result)
})
})

Expand Down
2 changes: 1 addition & 1 deletion src/examples/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export type GetReturnType<
} = TAbi extends Abi
? ExtractAbiFunction<TAbi, TFunctionName>
: AbiFunction & { type: 'function' },
TArgs = AbiParametersToPrimitiveTypes<TAbiFunction['outputs']>,
TArgs = AbiParametersToPrimitiveTypes<TAbiFunction['outputs'], 'outputs'>,
FailedToParseArgs =
| ([TArgs] extends [never] ? true : false)
| (readonly unknown[] extends TArgs ? true : false),
Expand Down
2 changes: 1 addition & 1 deletion src/examples/useContractReads.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ test('useContractReads', () => {
},
],
})
assertType<{ data: [void, ResolvedConfig['BytesType']] }>(result)
assertType<{ data: [void, ResolvedConfig['BytesReturnType']] }>(result)
})
})

Expand Down
2 changes: 1 addition & 1 deletion src/examples/writeContract.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ test('writeContract', () => {
functionName: 'setSubnodeOwner',
args: ['0xfoo', '0xbar', address],
})
assertType<ResolvedConfig['BytesType']>(result)
assertType<ResolvedConfig['BytesReturnType']>(result)
})

test('tuple', () => {
Expand Down
46 changes: 28 additions & 18 deletions src/utils.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ test('AbiTypeToPrimitiveType', () => {
assertType<AbiTypeToPrimitiveType<'bytes1'>>('0xfoo')
assertType<AbiTypeToPrimitiveType<'bytes24'>>('0xfoo')
assertType<AbiTypeToPrimitiveType<'bytes32'>>('0xfoo')
assertType<AbiTypeToPrimitiveType<'bytes24'>>(
new Uint8Array(Buffer.from('0xfoo')),
)
assertType<AbiTypeToPrimitiveType<'bytes', 'outputs'>>(
// @ts-expect-error expect only `0x${string}` for return types.
new Uint8Array(Buffer.from('0xfoo')),
)
})

test('function', () => {
Expand Down Expand Up @@ -135,24 +142,27 @@ test('AbiParameterToPrimitiveType', () => {
})

test('tuple', () => {
type Result = AbiParameterToPrimitiveType<{
components: [
{ name: 'name'; type: 'string' },
{ name: 'symbol'; type: 'string' },
{ name: 'description'; type: 'string' },
{ name: 'imageURI'; type: 'string' },
{ name: 'contentURI'; type: 'string' },
{ name: 'price'; type: 'uint' },
{ name: 'limit'; type: 'uint256' },
{ name: 'fundingRecipient'; type: 'address' },
{ name: 'renderer'; type: 'address' },
{ name: 'nonce'; type: 'uint256' },
{ name: 'fee'; type: 'uint16' },
]
internalType: 'struct IWritingEditions.WritingEdition'
name: 'edition'
type: 'tuple'
}>
type Result = AbiParameterToPrimitiveType<
{
components: [
{ name: 'name'; type: 'string' },
{ name: 'symbol'; type: 'string' },
{ name: 'description'; type: 'string' },
{ name: 'imageURI'; type: 'string' },
{ name: 'contentURI'; type: 'string' },
{ name: 'price'; type: 'uint' },
{ name: 'limit'; type: 'uint256' },
{ name: 'fundingRecipient'; type: 'address' },
{ name: 'renderer'; type: 'address' },
{ name: 'nonce'; type: 'uint256' },
{ name: 'fee'; type: 'uint16' },
]
internalType: 'struct IWritingEditions.WritingEdition'
name: 'edition'
type: 'tuple'
},
'inputs'
>
assertType<Result>({
name: 'Test',
symbol: '$TEST',
Expand Down
37 changes: 27 additions & 10 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,24 @@ import type { Merge, Tuple } from './types'
* @param TAbiType - {@link AbiType} to convert to TypeScript representation
* @returns TypeScript primitive type
*/
export type AbiTypeToPrimitiveType<TAbiType extends AbiType> =
PrimitiveTypeLookup<TAbiType>[TAbiType]
export type AbiTypeToPrimitiveType<
TAbiType extends AbiType,
TAbiParameterType extends 'inputs' | 'outputs' = 'inputs',
> = PrimitiveTypeLookup<TAbiType, TAbiParameterType>[TAbiType]

// Using a map to look up types is faster, than nested conditional types
// s/o https://twitter.com/SeaRyanC/status/1538971176357113858
type PrimitiveTypeLookup<TAbiType extends AbiType> = {
type PrimitiveTypeLookup<
TAbiType extends AbiType,
TAbiParameterType extends 'inputs' | 'outputs',
> = {
[_ in SolidityAddress]: ResolvedConfig['AddressType']
} & {
[_ in SolidityBool]: boolean
} & {
[_ in SolidityBytes]: ResolvedConfig['BytesType']
[_ in SolidityBytes]: TAbiParameterType extends 'inputs'
? ResolvedConfig['BytesInputType']
: ResolvedConfig['BytesReturnType']
} & {
[_ in SolidityFunction]: `${ResolvedConfig['AddressType']}${string}`
} & {
Expand Down Expand Up @@ -75,10 +82,11 @@ type BitsTypeLookup = {
*/
export type AbiParameterToPrimitiveType<
TAbiParameter extends AbiParameter | { name: string; type: unknown },
TAbiParameterType extends 'inputs' | 'outputs' = 'inputs',
> =
// 1. Check to see if type is basic (not tuple or array) and can be looked up immediately.
TAbiParameter['type'] extends Exclude<AbiType, SolidityTuple | SolidityArray>
? AbiTypeToPrimitiveType<TAbiParameter['type']>
? AbiTypeToPrimitiveType<TAbiParameter['type'], TAbiParameterType>
: // 2. Check if type is tuple and covert each component
TAbiParameter extends {
type: SolidityTuple
Expand All @@ -89,7 +97,8 @@ export type AbiParameterToPrimitiveType<
readonly [
...{
[K in keyof TComponents]: AbiParameterToPrimitiveType<
TComponents[K]
TComponents[K],
TAbiParameterType
>
},
]
Expand All @@ -99,7 +108,7 @@ export type AbiParameterToPrimitiveType<
name: string
}
? Component['name']
: never]: AbiParameterToPrimitiveType<Component>
: never]: AbiParameterToPrimitiveType<Component, TAbiParameterType>
}
: // 3. Check if type is array.
/**
Expand Down Expand Up @@ -127,11 +136,15 @@ export type AbiParameterToPrimitiveType<
// and passed back into the function to continue reduing down to the basic types found in Step 1.
Size extends keyof SolidityFixedArraySizeLookup
? Tuple<
AbiParameterToPrimitiveType<Merge<TAbiParameter, { type: Head }>>,
AbiParameterToPrimitiveType<
Merge<TAbiParameter, { type: Head }>,
TAbiParameterType
>,
SolidityFixedArraySizeLookup[Size]
>
: readonly AbiParameterToPrimitiveType<
Merge<TAbiParameter, { type: Head }>
Merge<TAbiParameter, { type: Head }>,
TAbiParameterType
>[]
: never
: // 4. If type is not basic, tuple, or array, we don't know what the type is.
Expand Down Expand Up @@ -164,11 +177,15 @@ type _HasUnnamedAbiParameter<TAbiParameters extends readonly AbiParameter[]> =
*/
export type AbiParametersToPrimitiveTypes<
TAbiParameters extends readonly AbiParameter[],
TAbiParameterType extends 'inputs' | 'outputs' = 'inputs',
> = {
// TODO: Convert to labeled tuple so parameter names show up in autocomplete
// e.g. [foo: string, bar: string]
// https://github.com/microsoft/TypeScript/issues/44939
[K in keyof TAbiParameters]: AbiParameterToPrimitiveType<TAbiParameters[K]>
[K in keyof TAbiParameters]: AbiParameterToPrimitiveType<
TAbiParameters[K],
TAbiParameterType
>
}

/**
Expand Down