diff --git a/packages/ensjs/deploy/00_register_legacy.cjs b/packages/ensjs/deploy/00_register_legacy.cjs index 64fed5c0..655fb6ba 100644 --- a/packages/ensjs/deploy/00_register_legacy.cjs +++ b/packages/ensjs/deploy/00_register_legacy.cjs @@ -149,7 +149,10 @@ const dummyABI = [ * abi?: { * contentType: 1 | 2 | 4 | 8 | 256 * data: object | string - * } + * } | { + * contentType: 1 | 2 | 4 | 8 | 256 + * data: string + * }[] * } * duration?: number * subnames?: Subname[] @@ -308,6 +311,31 @@ const names = [ }, }, }, + { + label: 'with-type-all-abi', + namedOwner: 'owner', + namedAddr: 'owner', + records: { + abi: [ + { + contentType: 1, + data: dummyABI, + }, + { + contentType: 2, + data: dummyABI, + }, + { + contentType: 4, + data: dummyABI, + }, + { + contentType: 8, + data: 'https://example.com', + }, + ], + }, + }, { label: 'expired', namedOwner: 'owner', @@ -428,28 +456,33 @@ const func = async function (hre) { await setContenthashTx.wait() } if (records.abi) { - console.log('ABI') - /** - * @type {string | Buffer | Uint8Array} - */ - let data - if (records.abi.contentType === 1 || records.abi.contentType === 256) { - data = JSON.stringify(records.abi.data) - } else if (records.abi.contentType === 2) { - data = pako.deflate(JSON.stringify(records.abi.data)) - } else if (records.abi.contentType === 4) { - data = cbor.encode(records.abi.data) - } else { - data = records.abi.data + const abis = Array.isArray(records.abi) ? records.abi : [records.abi] + for (const abi of abis) { + /** + * @type {string | Buffer | Uint8Array} + */ + let data + if ( + abi.contentType === 1 || + abi.contentType === 256 + ) { + data = JSON.stringify(abi.data) + } else if (abi.contentType === 2) { + data = pako.deflate(JSON.stringify(abi.data)) + } else if (abi.contentType === 4) { + data = cbor.encode(abi.data) + } else { + data = abi.data + } + if (typeof data === 'string') data = toBytes(data) + const setABITx = await _publicResolver.setABI( + hash, + abi.contentType, + data, + ) + console.log(` - ${abi.contentType} (tx: ${setABITx.hash})...`) + await setABITx.wait() } - if (typeof data === 'string') data = toBytes(data) - const setABITx = await _publicResolver.setABI( - hash, - records.abi.contentType, - data, - ) - console.log(` - ${records.abi.contentType} (tx: ${setABITx.hash})...`) - await setABITx.wait() } } diff --git a/packages/ensjs/src/functions/public/_getAbi.ts b/packages/ensjs/src/functions/public/_getAbi.ts index eddbb058..a02d5ea7 100644 --- a/packages/ensjs/src/functions/public/_getAbi.ts +++ b/packages/ensjs/src/functions/public/_getAbi.ts @@ -19,22 +19,25 @@ import { namehash } from '../../utils/normalise.js' export type InternalGetAbiParameters = { /** Name to get ABI record for */ name: string + /** Supported content types as bitwise + * ID 1: JSON + * ID 2: zlib compressed JSON + * ID 4: CBOR + * ID 8: URI + */ + supportedContentTypes?: bigint /** Whether or not to throw decoding errors */ strict?: boolean } export type InternalGetAbiReturnType = Prettify -// Supported content types as bitwise OR -// ID 1: JSON -// ID 2: zlib compressed JSON -// ID 4: CBOR -// ID 8: URI -const supportedContentTypes = 0xfn - const encode = ( _client: ClientWithEns, - { name }: Omit, + { + name, + supportedContentTypes = 0xfn, + }: Omit, ): SimpleTransactionRequest => { return { to: EMPTY_ADDRESS, diff --git a/packages/ensjs/src/functions/public/getAbiRecord.test.ts b/packages/ensjs/src/functions/public/getAbiRecord.test.ts index a4ae6d97..a6b8ff35 100644 --- a/packages/ensjs/src/functions/public/getAbiRecord.test.ts +++ b/packages/ensjs/src/functions/public/getAbiRecord.test.ts @@ -2,6 +2,7 @@ import { RawContractError } from 'viem' import type { ClientWithEns } from '../../contracts/consts.js' import { publicClient } from '../../test/addTestContracts.js' import getAbiRecord from './getAbiRecord.js' +import { generateSupportedContentTypes } from '../../utils/generateSupportedContentTypes.js' const dummyABI = [ { @@ -173,6 +174,53 @@ describe('getAbiRecord()', () => { }) expect(result).toBeNull() }) + it('should return the result of type 1 abi if the name has multiple abi records', async () => { + const result = await getAbiRecord(publicClient, { + name: 'with-type-all-abi.eth', + }) + expect(result).toBeTruthy() + if (result) { + expect(result.contentType).toBe(1) + expect(result.decoded).toBe(true) + expect(result.abi).toMatchObject(dummyABI) + } + }) + it('should return the result of type 2 abi if supportedContentTypes is zlib', async () => { + const result = await getAbiRecord(publicClient, { + name: 'with-type-all-abi.eth', + supportedContentTypes: generateSupportedContentTypes('zlib'), + }) + expect(result).toBeTruthy() + if (result) { + expect(result.contentType).toBe(2) + expect(result.decoded).toBe(true) + expect(result.abi).toMatchObject(dummyABI) + } + }) + it('should return the result of type 4 abi if supportedContentTypes is cbor', async () => { + const result = await getAbiRecord(publicClient, { + name: 'with-type-all-abi.eth', + supportedContentTypes: generateSupportedContentTypes('cbor'), + }) + expect(result).toBeTruthy() + if (result) { + expect(result.contentType).toBe(4) + expect(result.decoded).toBe(true) + expect(result.abi).toMatchObject(dummyABI) + } + }) + it('should return the result of type 8 abi if supportedContentTypes is uri', async () => { + const result = await getAbiRecord(publicClient, { + name: 'with-type-all-abi.eth', + supportedContentTypes: generateSupportedContentTypes('uri'), + }) + expect(result).toBeTruthy() + if (result) { + expect(result.contentType).toBe(8) + expect(result.decoded).toBe(true) + expect(result.abi).toBe('https://example.com') + } + }) it('should return null on error when strict is false', async () => { await expect( getAbiRecord.decode( diff --git a/packages/ensjs/src/functions/public/getAbiRecord.ts b/packages/ensjs/src/functions/public/getAbiRecord.ts index 61235723..8e658d5b 100644 --- a/packages/ensjs/src/functions/public/getAbiRecord.ts +++ b/packages/ensjs/src/functions/public/getAbiRecord.ts @@ -26,9 +26,13 @@ export type GetAbiRecordReturnType = Prettify const encode = ( client: ClientWithEns, - { name, gatewayUrls }: Omit, + { + name, + supportedContentTypes, + gatewayUrls, + }: Omit, ): SimpleTransactionRequest => { - const prData = _getAbi.encode(client, { name }) + const prData = _getAbi.encode(client, { name, supportedContentTypes }) return universalWrapper.encode(client, { name, data: prData.data, diff --git a/packages/ensjs/src/functions/wallet/setRecords.test.ts b/packages/ensjs/src/functions/wallet/setRecords.test.ts index e4e0c6ea..b9d283fa 100644 --- a/packages/ensjs/src/functions/wallet/setRecords.test.ts +++ b/packages/ensjs/src/functions/wallet/setRecords.test.ts @@ -144,6 +144,28 @@ it('should return a transaction to the resolver and delete successfully', async expect(records.coins).toHaveLength(0) expect(records.texts).toHaveLength(0) }) +it('should return a transaction to the resolver and delete all abis successfully', async () => { + const tx = await setRecords(walletClient, { + name: 'with-type-all-abi.eth', + resolverAddress: (await getResolver(publicClient, { + name: 'with-type-all-abi.eth', + }))!, + abi: [ + await encodeAbi({ encodeAs: 'json', data: null }), + await encodeAbi({ encodeAs: 'cbor', data: null }), + await encodeAbi({ encodeAs: 'zlib', data: null }), + await encodeAbi({ encodeAs: 'uri', data: null }), + ], + account: accounts[1], + }) + await waitForTransaction(tx) + + const records = await getRecords(publicClient, { + name: 'test123.eth', + abi: true, + }) + expect(records.abi).toBeNull() +}) it('should error if there are no records to set', async () => { await expect( setRecords(walletClient, { diff --git a/packages/ensjs/src/utils/generateRecordCallArray.test.ts b/packages/ensjs/src/utils/generateRecordCallArray.test.ts index e50f21ef..2ad3285f 100644 --- a/packages/ensjs/src/utils/generateRecordCallArray.test.ts +++ b/packages/ensjs/src/utils/generateRecordCallArray.test.ts @@ -105,3 +105,20 @@ it('adds abi call when data is not empty', async () => { ] `) }) +it('adds multiple abi calls when multiple abis are added', async () => { + const result = [ + await encodeAbi({ encodeAs: 'json', data: { foo: 'bar' } }), + await encodeAbi({ encodeAs: 'uri', data: null }), + ] + expect( + generateRecordCallArray({ + namehash: namehash('test.eth'), + abi: result, + }), + ).toMatchInlineSnapshot(` + [ + "0x623195b0eb4f647bea6caa36333c816d7b46fdcb05f9466ecacc140ea8c66faf15b3d9f100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000d7b22666f6f223a22626172227d00000000000000000000000000000000000000", + "0x623195b0eb4f647bea6caa36333c816d7b46fdcb05f9466ecacc140ea8c66faf15b3d9f1000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + ] + `) +}) diff --git a/packages/ensjs/src/utils/generateRecordCallArray.ts b/packages/ensjs/src/utils/generateRecordCallArray.ts index 61656375..b7d65803 100644 --- a/packages/ensjs/src/utils/generateRecordCallArray.ts +++ b/packages/ensjs/src/utils/generateRecordCallArray.ts @@ -26,7 +26,7 @@ export type RecordOptions = Prettify<{ /** Array of coin records */ coins?: Omit[] /** ABI value */ - abi?: EncodedAbi + abi?: EncodedAbi | EncodedAbi[] }> export const generateRecordCallArray = ({ @@ -49,8 +49,11 @@ export const generateRecordCallArray = ({ } if (abi !== undefined) { - const data = encodeSetAbi({ namehash, ...abi } as EncodeSetAbiParameters) - if (data) calls.push(data) + const abis = Array.isArray(abi) ? abi : [abi] + for (const abi_ of abis) { + const data = encodeSetAbi({ namehash, ...abi_ } as EncodeSetAbiParameters) + if (data) calls.push(data) + } } if (texts && texts.length > 0) { diff --git a/packages/ensjs/src/utils/generateSupportedContentTypes.test.ts.ts b/packages/ensjs/src/utils/generateSupportedContentTypes.test.ts.ts new file mode 100644 index 00000000..2970c0e0 --- /dev/null +++ b/packages/ensjs/src/utils/generateSupportedContentTypes.test.ts.ts @@ -0,0 +1,24 @@ +import { generateSupportedContentTypes } from './generateSupportedContentTypes.js' + +type FunctionParameters = Parameters +type EncodeAsParameter = FunctionParameters[0] + +describe('generateSupportedContentTypes', () => { + it.each([ + ['json', 1n], + ['zlib', 2n], + ['cbor', 4n], + ['uri', 8n], + ['unknown', 0n], + [['json', 'zlib'], 3n], + [['zlib', 'cbor'], 6n], + [['cbor', 'uri'], 12n], + [['json', 'zlib', 'cbor', 'uri'], 15n], + ] as [EncodeAsParameter, bigint][])( + 'should return the correct bitwise value supportedContentTypes %p (%p)', + (encodedAs, expected) => { + const result = generateSupportedContentTypes(encodedAs) + expect(result).toEqual(expected) + }, + ) +}) diff --git a/packages/ensjs/src/utils/generateSupportedContentTypes.ts b/packages/ensjs/src/utils/generateSupportedContentTypes.ts new file mode 100644 index 00000000..9262f082 --- /dev/null +++ b/packages/ensjs/src/utils/generateSupportedContentTypes.ts @@ -0,0 +1,23 @@ +import type { EncodeAbiParameters } from './encoders/encodeAbi.js' + +type AbiEncodeAs = EncodeAbiParameters['encodeAs'] + +const abiEncodeMap: { [key in AbiEncodeAs]: bigint } = { + json: 1n, + zlib: 2n, + cbor: 4n, + uri: 8n, +} as const + +export const generateSupportedContentTypes = ( + encodeAsItemOrList: AbiEncodeAs | AbiEncodeAs[], +): bigint => { + const encodeAsList = Array.isArray(encodeAsItemOrList) + ? encodeAsItemOrList + : [encodeAsItemOrList] + return encodeAsList.reduce((result, encodeAs) => { + const contentType = abiEncodeMap[encodeAs] + if (contentType) result |= contentType + return result + }, 0n) +}