Skip to content

Commit 1c68afd

Browse files
authored
ENS metadata and reverse ENS record set (#2116)
* ENS metadata and reverse ENS record Signed-off-by: Nischal Sharma <[email protected]> * updated name wrapper contract address Signed-off-by: Nischal Sharma <[email protected]> * set and get ENS text Signed-off-by: Nischal Sharma <[email protected]> * added tests Signed-off-by: Nischal Sharma <[email protected]> * added changelog entry Signed-off-by: Nischal Sharma <[email protected]> * resolved comments Signed-off-by: Nischal Sharma <[email protected]> --------- Signed-off-by: Nischal Sharma <[email protected]>
1 parent 3814e43 commit 1c68afd

File tree

9 files changed

+970
-7
lines changed

9 files changed

+970
-7
lines changed

Diff for: CHANGELOG.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
77

88
### Bug Fixes
99

10-
*
10+
* fixed several code issues found by sonar [#2113](https://github.com/hyperledger/web3j/pull/2113)
11+
* update GitHub actions versions [#2114](https://github.com/hyperledger/web3j/pull/2114)
1112

1213
### Features
1314

1415
* bump snapshot version to 4.12.3 [#2101](https://github.com/hyperledger/web3j/pull/2101)
1516
* Add HSM kms implementation [#2105](https://github.com/hyperledger/web3j/pull/2105)
1617
* Added support for Holesky [#2111](https://github.com/hyperledger/web3j/pull/2111)
18+
* Advance ENS features and metadata retrieval [#2116](https://github.com/hyperledger/web3j/pull/2116)
1719

1820
### BREAKING CHANGES
1921

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* Copyright 2024 Web3 Labs Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*/
13+
package org.web3j.ens;
14+
15+
import com.fasterxml.jackson.core.JsonProcessingException;
16+
import com.fasterxml.jackson.databind.ObjectMapper;
17+
18+
public class EnsMetadataResponse {
19+
public boolean is_normalized;
20+
public String name;
21+
public String description;
22+
public Attribute[] attributes;
23+
public String url;
24+
public long last_request_date;
25+
public int version;
26+
public String background_image;
27+
public String image;
28+
public String image_url;
29+
30+
public boolean isIs_normalized() {
31+
return is_normalized;
32+
}
33+
34+
public void setIs_normalized(boolean is_normalized) {
35+
this.is_normalized = is_normalized;
36+
}
37+
38+
public String getName() {
39+
return name;
40+
}
41+
42+
public void setName(String name) {
43+
this.name = name;
44+
}
45+
46+
public String getDescription() {
47+
return description;
48+
}
49+
50+
public void setDescription(String description) {
51+
this.description = description;
52+
}
53+
54+
public Attribute[] getAttributes() {
55+
return attributes;
56+
}
57+
58+
public void setAttributes(Attribute[] attributes) {
59+
this.attributes = attributes;
60+
}
61+
62+
public String getUrl() {
63+
return url;
64+
}
65+
66+
public void setUrl(String url) {
67+
this.url = url;
68+
}
69+
70+
public long getLast_request_date() {
71+
return last_request_date;
72+
}
73+
74+
public void setLast_request_date(long last_request_date) {
75+
this.last_request_date = last_request_date;
76+
}
77+
78+
public int getVersion() {
79+
return version;
80+
}
81+
82+
public void setVersion(int version) {
83+
this.version = version;
84+
}
85+
86+
public String getBackground_image() {
87+
return background_image;
88+
}
89+
90+
public void setBackground_image(String background_image) {
91+
this.background_image = background_image;
92+
}
93+
94+
public String getImage() {
95+
return image;
96+
}
97+
98+
public void setImage(String image) {
99+
this.image = image;
100+
}
101+
102+
public String getImage_url() {
103+
return image_url;
104+
}
105+
106+
public void setImage_url(String image_url) {
107+
this.image_url = image_url;
108+
}
109+
110+
public static class Attribute {
111+
public String trait_type;
112+
public String display_type;
113+
public Object value;
114+
}
115+
116+
@Override
117+
public String toString() {
118+
try {
119+
return new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(this);
120+
} catch (JsonProcessingException e) {
121+
return "Error serializing EnsMetadataResponse: " + e.getMessage();
122+
}
123+
}
124+
}

Diff for: core/src/main/java/org/web3j/ens/EnsResolver.java

+109-6
Original file line numberDiff line numberDiff line change
@@ -25,25 +25,29 @@
2525
import okhttp3.OkHttpClient;
2626
import okhttp3.Request;
2727
import okhttp3.RequestBody;
28+
import okhttp3.Response;
2829
import okhttp3.ResponseBody;
2930
import org.slf4j.Logger;
3031
import org.slf4j.LoggerFactory;
3132

3233
import org.web3j.abi.DefaultFunctionReturnDecoder;
3334
import org.web3j.abi.datatypes.ens.OffchainLookup;
35+
import org.web3j.crypto.Credentials;
3436
import org.web3j.crypto.Keys;
3537
import org.web3j.crypto.WalletUtils;
3638
import org.web3j.dto.EnsGatewayRequestDTO;
3739
import org.web3j.dto.EnsGatewayResponseDTO;
3840
import org.web3j.ens.contracts.generated.ENS;
3941
import org.web3j.ens.contracts.generated.OffchainResolverContract;
4042
import org.web3j.ens.contracts.generated.PublicResolver;
43+
import org.web3j.ens.contracts.generated.ReverseRegistrar;
4144
import org.web3j.protocol.ObjectMapperFactory;
4245
import org.web3j.protocol.Web3j;
4346
import org.web3j.protocol.core.DefaultBlockParameterName;
4447
import org.web3j.protocol.core.methods.response.EthBlock;
4548
import org.web3j.protocol.core.methods.response.EthSyncing;
4649
import org.web3j.protocol.core.methods.response.NetVersion;
50+
import org.web3j.protocol.core.methods.response.TransactionReceipt;
4751
import org.web3j.tx.ClientTransactionManager;
4852
import org.web3j.tx.TransactionManager;
4953
import org.web3j.tx.gas.DefaultGasProvider;
@@ -150,6 +154,31 @@ protected OffchainResolverContract obtainOffchainResolver(String ensName) {
150154
}
151155
}
152156

157+
protected OffchainResolverContract obtainOffchainResolver(
158+
String ensName, Credentials credentials) {
159+
if (isValidEnsName(ensName, addressLength)) {
160+
boolean isSynced;
161+
162+
try {
163+
isSynced = isSynced();
164+
} catch (Exception e) {
165+
throw new EnsResolutionException("Unable to determine sync status of node", e);
166+
}
167+
168+
if (!isSynced) {
169+
throw new EnsResolutionException("Node is not currently synced");
170+
}
171+
172+
try {
173+
return lookupOffchainResolver(ensName, credentials);
174+
} catch (Exception e) {
175+
throw new EnsResolutionException("Unable to get resolver", e);
176+
}
177+
} else {
178+
throw new EnsResolutionException("EnsName is invalid: " + ensName);
179+
}
180+
}
181+
153182
/**
154183
* Returns the address of the resolver for the specified node.
155184
*
@@ -336,6 +365,19 @@ protected Request buildRequest(String url, String sender, String data)
336365
}
337366
}
338367

368+
public TransactionReceipt setReverseName(String name, Credentials credentials)
369+
throws Exception {
370+
ReverseRegistrar reverseRegistrar = getReverseRegistrarContract(credentials);
371+
return reverseRegistrar.setName(name).send();
372+
}
373+
374+
public TransactionReceipt setReverseName(
375+
String addr, String owner, String resolver, String name, Credentials credentials)
376+
throws Exception {
377+
ReverseRegistrar reverseRegistrar = getReverseRegistrarContract(credentials);
378+
return reverseRegistrar.setNameForAddr(addr, owner, resolver, name).send();
379+
}
380+
339381
/**
340382
* Reverse name resolution as documented in the <a
341383
* href="https://docs.ens.domains/contract-api-reference/reverseregistrar">specification</a>.
@@ -376,13 +418,14 @@ private OffchainResolverContract lookupOffchainResolver(String ensName) throws E
376418
getResolverAddress(ensName), web3j, transactionManager, new DefaultGasProvider());
377419
}
378420

379-
private String getResolverAddress(String ensName) throws Exception {
380-
NetVersion netVersion = web3j.netVersion().send();
381-
String registryContract = Contracts.resolveRegistryContract(netVersion.getNetVersion());
382-
383-
ENS ensRegistry =
384-
ENS.load(registryContract, web3j, transactionManager, new DefaultGasProvider());
421+
private OffchainResolverContract lookupOffchainResolver(String ensName, Credentials credentials)
422+
throws Exception {
423+
return OffchainResolverContract.load(
424+
getResolverAddress(ensName), web3j, credentials, new DefaultGasProvider());
425+
}
385426

427+
public String getResolverAddress(String ensName) throws Exception {
428+
ENS ensRegistry = getRegistryContract();
386429
byte[] nameHash = NameHash.nameHashAsBytes(ensName);
387430
String address = ensRegistry.resolver(nameHash).send();
388431

@@ -393,6 +436,66 @@ private String getResolverAddress(String ensName) throws Exception {
393436
return address;
394437
}
395438

439+
public String getOwnerAddress(String ensName) throws Exception {
440+
ENS ensRegistry = getRegistryContract();
441+
byte[] nameHash = NameHash.nameHashAsBytes(ensName);
442+
return ensRegistry.owner(nameHash).send();
443+
}
444+
445+
private ENS getRegistryContract() throws IOException {
446+
NetVersion netVersion = web3j.netVersion().send();
447+
String registryContract = Contracts.resolveRegistryContract(netVersion.getNetVersion());
448+
449+
return ENS.load(registryContract, web3j, transactionManager, new DefaultGasProvider());
450+
}
451+
452+
protected ReverseRegistrar getReverseRegistrarContract(Credentials credentials)
453+
throws IOException {
454+
NetVersion netVersion = web3j.netVersion().send();
455+
String reverseRegistrarContract =
456+
ReverseRegistrarContracts.resolveReverseRegistrarContract(
457+
netVersion.getNetVersion());
458+
459+
return ReverseRegistrar.load(
460+
reverseRegistrarContract, web3j, credentials, new DefaultGasProvider());
461+
}
462+
463+
public EnsMetadataResponse getEnsMetadata(String name) throws IOException {
464+
NetVersion netVersion = web3j.netVersion().send();
465+
byte[] nameHash = NameHash.nameHashAsBytes(name);
466+
String apiUrl =
467+
NameWrapperUrl.getEnsMetadataApi(netVersion.getNetVersion())
468+
+ Numeric.toHexString(nameHash);
469+
470+
Request request = new Request.Builder().url(apiUrl).get().build();
471+
472+
try (Response response = client.newCall(request).execute()) {
473+
if (!response.isSuccessful()) {
474+
throw new IOException(
475+
"Failed to fetch ENS metadata. HTTP error code: " + response.code());
476+
}
477+
478+
// Parse the JSON response
479+
assert response.body() != null;
480+
String responseBody = response.body().string();
481+
return new ObjectMapper().readValue(responseBody, EnsMetadataResponse.class);
482+
}
483+
}
484+
485+
public String getEnsText(String name, String key) throws Exception {
486+
OffchainResolverContract offchainResolverContract = obtainOffchainResolver(name);
487+
byte[] nameHash = NameHash.nameHashAsBytes(name);
488+
return offchainResolverContract.text(nameHash, key).send();
489+
}
490+
491+
public TransactionReceipt setEnsText(
492+
String name, String key, String value, Credentials credentials) throws Exception {
493+
OffchainResolverContract offchainResolverContract =
494+
obtainOffchainResolver(name, credentials);
495+
byte[] nameHash = NameHash.nameHashAsBytes(name);
496+
return offchainResolverContract.setText(nameHash, key, value).send();
497+
}
498+
396499
boolean isSynced() throws Exception {
397500
EthSyncing ethSyncing = web3j.ethSyncing().send();
398501
if (ethSyncing.isSyncing()) {

Diff for: core/src/main/java/org/web3j/ens/NameWrapperUrl.java

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2024 Web3 Labs Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*/
13+
package org.web3j.ens;
14+
15+
import org.web3j.tx.ChainIdLong;
16+
17+
public class NameWrapperUrl {
18+
19+
public static final String BASE_URL = "https://ens-metadata-service.appspot.com/";
20+
public static final String MAINNET_URL =
21+
BASE_URL + "mainnet/" + "0xD4416b13d2b3a9aBae7AcD5D6C2BbDBE25686401/";
22+
public static final String SEPOLIA_URL =
23+
BASE_URL + "sepolia/" + "0x0635513f179D50A207757E05759CbD106d7dFcE8/";
24+
public static final String HOLESKY_URL =
25+
BASE_URL + "holesky/" + "0xab50971078225D365994dc1Edcb9b7FD72Bb4862/";
26+
27+
private NameWrapperUrl() {}
28+
29+
public static String getEnsMetadataApi(String chainId) {
30+
final Long chainIdLong = Long.parseLong(chainId);
31+
if (chainIdLong.equals(ChainIdLong.MAINNET)) {
32+
return MAINNET_URL;
33+
} else if (chainIdLong.equals(ChainIdLong.SEPOLIA)) {
34+
return SEPOLIA_URL;
35+
} else if (chainIdLong.equals(ChainIdLong.HOLESKY)) {
36+
return HOLESKY_URL;
37+
} else {
38+
throw new EnsResolutionException(
39+
"Unable to get ENS metadata API for network id: " + chainId);
40+
}
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2024 Web3 Labs Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*/
13+
package org.web3j.ens;
14+
15+
import org.web3j.tx.ChainIdLong;
16+
17+
public class ReverseRegistrarContracts {
18+
19+
public static final String MAINNET = "0xa58E81fe9b61B5c3fE2AFD33CF304c454AbFc7Cb";
20+
public static final String SEPOLIA = "0xA0a1AbcDAe1a2a4A2EF8e9113Ff0e02DD81DC0C6";
21+
public static final String HOLESKY = "0x132AC0B116a73add4225029D1951A9A707Ef673f";
22+
public static final String LINEA = "0x08D3fF6E65f680844fd2465393ff6f0d742b67D5";
23+
public static final String LINEA_SEPOLIA = "0x4aAA964D8EB65508ca3DA3b0A3C060c16059E613";
24+
25+
private ReverseRegistrarContracts() {}
26+
27+
public static String resolveReverseRegistrarContract(String chainId) {
28+
final Long chainIdLong = Long.parseLong(chainId);
29+
if (chainIdLong.equals(ChainIdLong.MAINNET)) {
30+
return MAINNET;
31+
} else if (chainIdLong.equals(ChainIdLong.SEPOLIA)) {
32+
return SEPOLIA;
33+
} else if (chainIdLong.equals(ChainIdLong.HOLESKY)) {
34+
return HOLESKY;
35+
} else if (chainIdLong.equals(ChainIdLong.LINEA)) {
36+
return LINEA;
37+
} else if (chainIdLong.equals(ChainIdLong.LINEA_SEPOLIA)) {
38+
return LINEA_SEPOLIA;
39+
} else {
40+
throw new EnsResolutionException(
41+
"Unable to resolve ENS reverse registrar contract for network id: " + chainId);
42+
}
43+
}
44+
}

0 commit comments

Comments
 (0)