Skip to content

Commit

Permalink
ENS metadata and reverse ENS record set (#2116)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
NickSneo authored Dec 3, 2024
1 parent 3814e43 commit 1c68afd
Show file tree
Hide file tree
Showing 9 changed files with 970 additions and 7 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline

### Bug Fixes

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

### Features

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

### BREAKING CHANGES

Expand Down
124 changes: 124 additions & 0 deletions core/src/main/java/org/web3j/ens/EnsMetadataResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* Copyright 2024 Web3 Labs Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.web3j.ens;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class EnsMetadataResponse {
public boolean is_normalized;
public String name;
public String description;
public Attribute[] attributes;
public String url;
public long last_request_date;
public int version;
public String background_image;
public String image;
public String image_url;

public boolean isIs_normalized() {
return is_normalized;
}

public void setIs_normalized(boolean is_normalized) {
this.is_normalized = is_normalized;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}

public Attribute[] getAttributes() {
return attributes;
}

public void setAttributes(Attribute[] attributes) {
this.attributes = attributes;
}

public String getUrl() {
return url;
}

public void setUrl(String url) {
this.url = url;
}

public long getLast_request_date() {
return last_request_date;
}

public void setLast_request_date(long last_request_date) {
this.last_request_date = last_request_date;
}

public int getVersion() {
return version;
}

public void setVersion(int version) {
this.version = version;
}

public String getBackground_image() {
return background_image;
}

public void setBackground_image(String background_image) {
this.background_image = background_image;
}

public String getImage() {
return image;
}

public void setImage(String image) {
this.image = image;
}

public String getImage_url() {
return image_url;
}

public void setImage_url(String image_url) {
this.image_url = image_url;
}

public static class Attribute {
public String trait_type;
public String display_type;
public Object value;
}

@Override
public String toString() {
try {
return new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(this);
} catch (JsonProcessingException e) {
return "Error serializing EnsMetadataResponse: " + e.getMessage();
}
}
}
115 changes: 109 additions & 6 deletions core/src/main/java/org/web3j/ens/EnsResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,29 @@
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.web3j.abi.DefaultFunctionReturnDecoder;
import org.web3j.abi.datatypes.ens.OffchainLookup;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.Keys;
import org.web3j.crypto.WalletUtils;
import org.web3j.dto.EnsGatewayRequestDTO;
import org.web3j.dto.EnsGatewayResponseDTO;
import org.web3j.ens.contracts.generated.ENS;
import org.web3j.ens.contracts.generated.OffchainResolverContract;
import org.web3j.ens.contracts.generated.PublicResolver;
import org.web3j.ens.contracts.generated.ReverseRegistrar;
import org.web3j.protocol.ObjectMapperFactory;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.methods.response.EthBlock;
import org.web3j.protocol.core.methods.response.EthSyncing;
import org.web3j.protocol.core.methods.response.NetVersion;
import org.web3j.protocol.core.methods.response.TransactionReceipt;
import org.web3j.tx.ClientTransactionManager;
import org.web3j.tx.TransactionManager;
import org.web3j.tx.gas.DefaultGasProvider;
Expand Down Expand Up @@ -150,6 +154,31 @@ protected OffchainResolverContract obtainOffchainResolver(String ensName) {
}
}

protected OffchainResolverContract obtainOffchainResolver(
String ensName, Credentials credentials) {
if (isValidEnsName(ensName, addressLength)) {
boolean isSynced;

try {
isSynced = isSynced();
} catch (Exception e) {
throw new EnsResolutionException("Unable to determine sync status of node", e);
}

if (!isSynced) {
throw new EnsResolutionException("Node is not currently synced");
}

try {
return lookupOffchainResolver(ensName, credentials);
} catch (Exception e) {
throw new EnsResolutionException("Unable to get resolver", e);
}
} else {
throw new EnsResolutionException("EnsName is invalid: " + ensName);
}
}

/**
* Returns the address of the resolver for the specified node.
*
Expand Down Expand Up @@ -336,6 +365,19 @@ protected Request buildRequest(String url, String sender, String data)
}
}

public TransactionReceipt setReverseName(String name, Credentials credentials)
throws Exception {
ReverseRegistrar reverseRegistrar = getReverseRegistrarContract(credentials);
return reverseRegistrar.setName(name).send();
}

public TransactionReceipt setReverseName(
String addr, String owner, String resolver, String name, Credentials credentials)
throws Exception {
ReverseRegistrar reverseRegistrar = getReverseRegistrarContract(credentials);
return reverseRegistrar.setNameForAddr(addr, owner, resolver, name).send();
}

/**
* Reverse name resolution as documented in the <a
* href="https://docs.ens.domains/contract-api-reference/reverseregistrar">specification</a>.
Expand Down Expand Up @@ -376,13 +418,14 @@ private OffchainResolverContract lookupOffchainResolver(String ensName) throws E
getResolverAddress(ensName), web3j, transactionManager, new DefaultGasProvider());
}

private String getResolverAddress(String ensName) throws Exception {
NetVersion netVersion = web3j.netVersion().send();
String registryContract = Contracts.resolveRegistryContract(netVersion.getNetVersion());

ENS ensRegistry =
ENS.load(registryContract, web3j, transactionManager, new DefaultGasProvider());
private OffchainResolverContract lookupOffchainResolver(String ensName, Credentials credentials)
throws Exception {
return OffchainResolverContract.load(
getResolverAddress(ensName), web3j, credentials, new DefaultGasProvider());
}

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

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

public String getOwnerAddress(String ensName) throws Exception {
ENS ensRegistry = getRegistryContract();
byte[] nameHash = NameHash.nameHashAsBytes(ensName);
return ensRegistry.owner(nameHash).send();
}

private ENS getRegistryContract() throws IOException {
NetVersion netVersion = web3j.netVersion().send();
String registryContract = Contracts.resolveRegistryContract(netVersion.getNetVersion());

return ENS.load(registryContract, web3j, transactionManager, new DefaultGasProvider());
}

protected ReverseRegistrar getReverseRegistrarContract(Credentials credentials)
throws IOException {
NetVersion netVersion = web3j.netVersion().send();
String reverseRegistrarContract =
ReverseRegistrarContracts.resolveReverseRegistrarContract(
netVersion.getNetVersion());

return ReverseRegistrar.load(
reverseRegistrarContract, web3j, credentials, new DefaultGasProvider());
}

public EnsMetadataResponse getEnsMetadata(String name) throws IOException {
NetVersion netVersion = web3j.netVersion().send();
byte[] nameHash = NameHash.nameHashAsBytes(name);
String apiUrl =
NameWrapperUrl.getEnsMetadataApi(netVersion.getNetVersion())
+ Numeric.toHexString(nameHash);

Request request = new Request.Builder().url(apiUrl).get().build();

try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException(
"Failed to fetch ENS metadata. HTTP error code: " + response.code());
}

// Parse the JSON response
assert response.body() != null;
String responseBody = response.body().string();
return new ObjectMapper().readValue(responseBody, EnsMetadataResponse.class);
}
}

public String getEnsText(String name, String key) throws Exception {
OffchainResolverContract offchainResolverContract = obtainOffchainResolver(name);
byte[] nameHash = NameHash.nameHashAsBytes(name);
return offchainResolverContract.text(nameHash, key).send();
}

public TransactionReceipt setEnsText(
String name, String key, String value, Credentials credentials) throws Exception {
OffchainResolverContract offchainResolverContract =
obtainOffchainResolver(name, credentials);
byte[] nameHash = NameHash.nameHashAsBytes(name);
return offchainResolverContract.setText(nameHash, key, value).send();
}

boolean isSynced() throws Exception {
EthSyncing ethSyncing = web3j.ethSyncing().send();
if (ethSyncing.isSyncing()) {
Expand Down
42 changes: 42 additions & 0 deletions core/src/main/java/org/web3j/ens/NameWrapperUrl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2024 Web3 Labs Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.web3j.ens;

import org.web3j.tx.ChainIdLong;

public class NameWrapperUrl {

public static final String BASE_URL = "https://ens-metadata-service.appspot.com/";
public static final String MAINNET_URL =
BASE_URL + "mainnet/" + "0xD4416b13d2b3a9aBae7AcD5D6C2BbDBE25686401/";
public static final String SEPOLIA_URL =
BASE_URL + "sepolia/" + "0x0635513f179D50A207757E05759CbD106d7dFcE8/";
public static final String HOLESKY_URL =
BASE_URL + "holesky/" + "0xab50971078225D365994dc1Edcb9b7FD72Bb4862/";

private NameWrapperUrl() {}

public static String getEnsMetadataApi(String chainId) {
final Long chainIdLong = Long.parseLong(chainId);
if (chainIdLong.equals(ChainIdLong.MAINNET)) {
return MAINNET_URL;
} else if (chainIdLong.equals(ChainIdLong.SEPOLIA)) {
return SEPOLIA_URL;
} else if (chainIdLong.equals(ChainIdLong.HOLESKY)) {
return HOLESKY_URL;
} else {
throw new EnsResolutionException(
"Unable to get ENS metadata API for network id: " + chainId);
}
}
}
44 changes: 44 additions & 0 deletions core/src/main/java/org/web3j/ens/ReverseRegistrarContracts.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2024 Web3 Labs Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.web3j.ens;

import org.web3j.tx.ChainIdLong;

public class ReverseRegistrarContracts {

public static final String MAINNET = "0xa58E81fe9b61B5c3fE2AFD33CF304c454AbFc7Cb";
public static final String SEPOLIA = "0xA0a1AbcDAe1a2a4A2EF8e9113Ff0e02DD81DC0C6";
public static final String HOLESKY = "0x132AC0B116a73add4225029D1951A9A707Ef673f";
public static final String LINEA = "0x08D3fF6E65f680844fd2465393ff6f0d742b67D5";
public static final String LINEA_SEPOLIA = "0x4aAA964D8EB65508ca3DA3b0A3C060c16059E613";

private ReverseRegistrarContracts() {}

public static String resolveReverseRegistrarContract(String chainId) {
final Long chainIdLong = Long.parseLong(chainId);
if (chainIdLong.equals(ChainIdLong.MAINNET)) {
return MAINNET;
} else if (chainIdLong.equals(ChainIdLong.SEPOLIA)) {
return SEPOLIA;
} else if (chainIdLong.equals(ChainIdLong.HOLESKY)) {
return HOLESKY;
} else if (chainIdLong.equals(ChainIdLong.LINEA)) {
return LINEA;
} else if (chainIdLong.equals(ChainIdLong.LINEA_SEPOLIA)) {
return LINEA_SEPOLIA;
} else {
throw new EnsResolutionException(
"Unable to resolve ENS reverse registrar contract for network id: " + chainId);
}
}
}
Loading

0 comments on commit 1c68afd

Please sign in to comment.