ethers_providers/ext/
ens.rs

1//! [Ethereum Name Service](https://docs.ens.domains/) support
2//! Adapted from <https://github.com/hhatto/rust-ens/blob/master/src/lib.rs>
3
4use ethers_core::{
5    types::{Address, NameOrAddress, Selector, TransactionRequest, H160, H256},
6    utils::keccak256,
7};
8
9/// ENS registry address (`0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e`)
10pub const ENS_ADDRESS: Address = H160([
11    // cannot set type aliases as constructors
12    0, 0, 0, 0, 0, 12, 46, 7, 78, 198, 154, 13, 251, 41, 151, 186, 108, 125, 46, 30,
13]);
14
15// Selectors
16const ENS_REVERSE_REGISTRAR_DOMAIN: &str = "addr.reverse";
17
18/// resolver(bytes32)
19const RESOLVER: Selector = [1, 120, 184, 191];
20
21/// addr(bytes32)
22pub const ADDR_SELECTOR: Selector = [59, 59, 87, 222];
23
24/// name(bytes32)
25pub const NAME_SELECTOR: Selector = [105, 31, 52, 49];
26
27/// text(bytes32, string)
28pub const FIELD_SELECTOR: Selector = [89, 209, 212, 60];
29
30/// supportsInterface(bytes4 interfaceID)
31pub const INTERFACE_SELECTOR: Selector = [1, 255, 201, 167];
32
33/// Returns a transaction request for calling the `resolver` method on the ENS server
34pub fn get_resolver<T: Into<NameOrAddress>>(ens_address: T, name: &str) -> TransactionRequest {
35    // keccak256('resolver(bytes32)')
36    let data = [&RESOLVER[..], &namehash(name).0].concat();
37    TransactionRequest {
38        data: Some(data.into()),
39        to: Some(ens_address.into()),
40        ..Default::default()
41    }
42}
43
44/// Returns a transaction request for checking interface support
45pub fn supports_interface<T: Into<NameOrAddress>>(
46    resolver_address: T,
47    selector: Selector,
48) -> TransactionRequest {
49    let data = [&INTERFACE_SELECTOR[..], &selector[..], &[0; 28]].concat();
50    TransactionRequest {
51        data: Some(data.into()),
52        to: Some(resolver_address.into()),
53        ..Default::default()
54    }
55}
56
57/// Returns a transaction request for calling
58pub fn resolve<T: Into<NameOrAddress>>(
59    resolver_address: T,
60    selector: Selector,
61    name: &str,
62    parameters: Option<&[u8]>,
63) -> TransactionRequest {
64    let data = [&selector[..], &namehash(name).0, parameters.unwrap_or_default()].concat();
65    TransactionRequest {
66        data: Some(data.into()),
67        to: Some(resolver_address.into()),
68        ..Default::default()
69    }
70}
71
72/// Returns the reverse-registrar name of an address.
73pub fn reverse_address(addr: Address) -> String {
74    format!("{addr:?}.{ENS_REVERSE_REGISTRAR_DOMAIN}")[2..].to_string()
75}
76
77/// Returns the ENS namehash as specified in [EIP-137](https://eips.ethereum.org/EIPS/eip-137)
78pub fn namehash(name: &str) -> H256 {
79    if name.is_empty() {
80        return H256::zero()
81    }
82
83    // Remove the variation selector U+FE0F
84    let name = name.replace('\u{fe0f}', "");
85
86    // Generate the node starting from the right
87    name.rsplit('.')
88        .fold([0u8; 32], |node, label| keccak256([node, keccak256(label.as_bytes())].concat()))
89        .into()
90}
91
92/// Returns a number in bytes form with padding to fit in 32 bytes.
93pub fn bytes_32ify(n: u64) -> Vec<u8> {
94    let b = n.to_be_bytes();
95    [[0; 32][b.len()..].to_vec(), b.to_vec()].concat()
96}
97
98/// Returns the ENS record key hash [EIP-634](https://eips.ethereum.org/EIPS/eip-634)
99pub fn parameterhash(name: &str) -> Vec<u8> {
100    let bytes = name.as_bytes();
101    let key_bytes =
102        [&bytes_32ify(64), &bytes_32ify(bytes.len().try_into().unwrap()), bytes].concat();
103    match key_bytes.len() % 32 {
104        0 => key_bytes,
105        n => [key_bytes, [0; 32][n..].to_vec()].concat(),
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    fn assert_hex(hash: H256, val: &str) {
114        assert_eq!(hash.0.to_vec(), hex::decode(val).unwrap());
115    }
116
117    #[test]
118    fn test_namehash() {
119        for (name, expected) in &[
120            ("", "0000000000000000000000000000000000000000000000000000000000000000"),
121            ("foo.eth", "de9b09fd7c5f901e23a3f19fecc54828e9c848539801e86591bd9801b019f84f"),
122            ("eth", "0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae"),
123            ("alice.eth", "0x787192fc5378cc32aa956ddfdedbf26b24e8d78e40109add0eea2c1a012c3dec"),
124            ("ret↩️rn.eth", "0x3de5f4c02db61b221e7de7f1c40e29b6e2f07eb48d65bf7e304715cd9ed33b24"),
125        ] {
126            assert_hex(namehash(name), expected);
127        }
128    }
129
130    #[test]
131    fn test_parametershash() {
132        assert_eq!(
133            parameterhash("avatar").to_vec(),
134            vec![
135                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
136                0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
137                0, 0, 0, 0, 0, 0, 0, 0, 6, 97, 118, 97, 116, 97, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
138                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
139            ]
140        );
141    }
142}