diff --git a/packages/web3-eth-ens/CHANGELOG.md b/packages/web3-eth-ens/CHANGELOG.md index c1fb717c2a8..54253823dfc 100644 --- a/packages/web3-eth-ens/CHANGELOG.md +++ b/packages/web3-eth-ens/CHANGELOG.md @@ -142,3 +142,7 @@ Documentation: - Dependencies updated ## [Unreleased] + +### Added + +- Added function `setAddress` in ENS and Resolver classes (#5956) \ No newline at end of file diff --git a/packages/web3-eth-ens/src/abi/ens/PublicResolver.ts b/packages/web3-eth-ens/src/abi/ens/PublicResolver.ts index 83b927bfb69..b9dba8a249c 100644 --- a/packages/web3-eth-ens/src/abi/ens/PublicResolver.ts +++ b/packages/web3-eth-ens/src/abi/ens/PublicResolver.ts @@ -590,4 +590,22 @@ export const PublicResolverAbi = [ stateMutability: 'view', type: 'function', }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'node', + type: 'bytes32', + }, + { + internalType: 'address', + name: 'a', + type: 'address', + }, + ], + name: 'setAddr', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, ] as const; diff --git a/packages/web3-eth-ens/src/ens.ts b/packages/web3-eth-ens/src/ens.ts index e62fd36c617..822dcab2b41 100644 --- a/packages/web3-eth-ens/src/ens.ts +++ b/packages/web3-eth-ens/src/ens.ts @@ -16,15 +16,18 @@ along with web3.js. If not, see . */ import { Web3Context, Web3ContextObject } from 'web3-core'; -import { ENSNetworkNotSyncedError, ENSUnsupportedNetworkError } from 'web3-errors'; +import { ENSNetworkNotSyncedError, ENSUnsupportedNetworkError, RevertInstructionError } from 'web3-errors'; import { isSyncing } from 'web3-eth'; import { Contract } from 'web3-eth-contract'; import { getId } from 'web3-net'; import { + Address, DEFAULT_RETURN_FORMAT, EthExecutionAPI, FMT_NUMBER, + PayableCallOptions, SupportedProviders, + TransactionReceipt, Web3NetAPI, } from 'web3-types'; import { PublicResolverAbi } from './abi/ens/PublicResolver.js'; @@ -258,4 +261,22 @@ export class ENS extends Web3Context { public get events() { return this._registry.events; } + + /** + * Sets the address of an ENS name in his resolver. + * @param name - The ENS name + * @param address - The address to set + * @param txConfig - (Optional) The transaction config + * @returns - The transaction receipt + * ```ts + * const receipt = await ens.setAddress('web3js.eth','0xe2597eb05cf9a87eb1309e86750c903ec38e527e'); + *``` + */ + public async setAddress( + name: string, + address: Address, + txConfig: PayableCallOptions + ): Promise { + return this._resolver.setAddress(name, address, txConfig); + } } diff --git a/packages/web3-eth-ens/src/resolver.ts b/packages/web3-eth-ens/src/resolver.ts index 3540455f3d5..9f99eae8955 100644 --- a/packages/web3-eth-ens/src/resolver.ts +++ b/packages/web3-eth-ens/src/resolver.ts @@ -19,11 +19,13 @@ import { ResolverMethodMissingError } from 'web3-errors'; import { Contract } from 'web3-eth-contract'; import { isNullish, sha3 } from 'web3-utils'; import { isHexStrict } from 'web3-validator'; +import { Address, PayableCallOptions } from 'web3-types'; import { PublicResolverAbi } from './abi/ens/PublicResolver.js'; import { interfaceIds, methodsInInterface } from './config.js'; import { Registry } from './registry.js'; import { namehash } from './utils.js'; + // Default public resolver // https://github.com/ensdomains/resolvers/blob/master/contracts/PublicResolver.sol @@ -102,4 +104,17 @@ export class Resolver { return resolverContract.methods.contenthash(namehash(ENSName)).call(); } + + public async setAddress( + ENSName: string, + address: Address, + txConfig: PayableCallOptions, + ) { + const resolverContract = await this.getResolverContractAdapter(ENSName); + await this.checkInterfaceSupport(resolverContract, methodsInInterface.setAddr); + + return resolverContract.methods + .setAddr(namehash(ENSName), address) + .send(txConfig); + } } diff --git a/packages/web3-eth-ens/test/unit/constructor.test.ts b/packages/web3-eth-ens/test/unit/constructor.test.ts index 8bb4ec2230d..06836c024d9 100644 --- a/packages/web3-eth-ens/test/unit/constructor.test.ts +++ b/packages/web3-eth-ens/test/unit/constructor.test.ts @@ -41,6 +41,7 @@ describe('ens', () => { const registry = new Registry(object); const resolver = new Resolver(registry); + expect(resolver.setAddress).toBeDefined(); expect(resolver.getAddress).toBeDefined(); expect(resolver.checkInterfaceSupport).toBeDefined(); expect(resolver.supportsInterface).toBeDefined(); @@ -52,6 +53,7 @@ describe('ens', () => { const ens = new ENS(registryAddresses.main, 'http://127.0.0.1:8545'); expect(ens.getResolver).toBeDefined(); + expect(ens.setAddress).toBeDefined(); expect(ens.recordExists).toBeDefined(); expect(ens.getTTL).toBeDefined(); expect(ens.getOwner).toBeDefined(); diff --git a/packages/web3-eth-ens/test/unit/ens.test.ts b/packages/web3-eth-ens/test/unit/ens.test.ts index 9f6f4dd7efe..1c860d72c38 100644 --- a/packages/web3-eth-ens/test/unit/ens.test.ts +++ b/packages/web3-eth-ens/test/unit/ens.test.ts @@ -110,6 +110,22 @@ describe('ens', () => { }); describe('addr', () => { + it('setAddr valid', async () => { + // eslint-disable-next-line @typescript-eslint/no-empty-function + const send = jest.spyOn({ send: () => {} }, 'send'); + + const setAddressMock = jest.spyOn(ens['_resolver'], 'setAddress').mockReturnValue({ + send, + } as unknown as Web3PromiEvent); + + const sendOptions = { from: mockAddress }; + await ens.setAddress(ENS_NAME, mockAddress, sendOptions); + expect(setAddressMock).toHaveBeenCalledWith( + ENS_NAME, + mockAddress, + sendOptions, + ); + }); it('getAddress', async () => { // eslint-disable-next-line @typescript-eslint/no-empty-function const call = jest.spyOn({ call: () => {} }, 'call'); diff --git a/packages/web3-eth-ens/test/unit/resolver.test.ts b/packages/web3-eth-ens/test/unit/resolver.test.ts index 97cf6e2d0b3..a771082b959 100644 --- a/packages/web3-eth-ens/test/unit/resolver.test.ts +++ b/packages/web3-eth-ens/test/unit/resolver.test.ts @@ -105,6 +105,29 @@ describe('resolver', () => { ); }); describe('addr', () => { + it('setAddr valid', async () => { + const checkInteraface = jest.spyOn(resolver, 'checkInterfaceSupport'); + + const setAddrMock = jest.spyOn(contract.methods, 'setAddr').mockReturnValue({ + send: jest.fn(), + } as unknown as NonPayableMethodObject); + + jest.spyOn(contract.methods, 'supportsInterface').mockReturnValue({ + call: jest.fn().mockReturnValue(true), + } as unknown as NonPayableMethodObject); + + // todo when moving this mock in beforeAll, jest calls the actual implementation, how to fix that + // I use this in many places + jest.spyOn(registry, 'getResolver').mockImplementation(async () => { + return new Promise(resolve => { + resolve(contract); + }); + }); + + await resolver.setAddress(ENS_NAME, mockAddress, { from: mockAddress }); + expect(checkInteraface).toHaveBeenCalled(); + expect(setAddrMock).toHaveBeenCalledWith(namehash(ENS_NAME), mockAddress); + }); it('getAddress', async () => { const supportsInterfaceMock = jest .spyOn(contract.methods, 'supportsInterface')