# `Onchain.ENS`
[🔗](https://github.com/ZenHive/onchain/blob/v0.5.4/lib/onchain/ens.ex#L1)

ENS (Ethereum Name Service) resolution and namehash computation.

## Does

- Compute EIP-137 namehash for ENS names (`namehash/1`)
- Normalize names (lowercase ASCII, strip trailing dot)
- Validate name structure (reject empty labels, non-ASCII)
- Forward resolution: ENS name → ETH address (`resolve/2`)
- Reverse resolution: ETH address → ENS name (`reverse/2`)
- Text record queries (`text/3`), contenthash, ABI, pubkey retrieval
- Look up resolver contracts (`resolver/2`)
- Configurable registry address via `:registry` opt

## Does Not

- CCIP-Read / EIP-3668 off-chain lookups (future enhancement)
- Wildcard resolution (ENSIP-10)
- ENS name registration or management (write operations)
- Full UTS-46 / ENSIP-15 Unicode normalization (internationalized names)
- Caching — consumers manage their own cache
- Multi-coin address resolution (only ETH via `addr(bytes32)`)

## Functions

| Function | Purpose |
|----------|---------|
| `namehash/1` | ENS name -> 32-byte EIP-137 node hash |
| `namehash!/1` | Same, raises on error |
| `resolver/2` | ENS name -> resolver contract address |
| `resolver!/2` | Same, raises on error |
| `resolve/2` | ENS name -> ETH address (forward resolution) |
| `resolve!/2` | Same, raises on error |
| `reverse/2` | ETH address -> ENS name (reverse resolution) |
| `reverse!/2` | Same, raises on error |
| `text/3` | Retrieve a text record (avatar, url, etc.) |
| `text!/3` | Same, raises on error |
| `contenthash/2` | Retrieve the contenthash record |
| `contenthash!/2` | Same, raises on error |
| `pubkey/2` | Retrieve the ECDSA public key |
| `pubkey!/2` | Same, raises on error |
| `abi/3` | Retrieve ABI data (ENSIP-7) |
| `abi!/3` | Same, raises on error |

## API Functions
| Function | Arity | Description | Param Kinds |
| --- | --- | --- | --- |
| `abi!` | 3 | Retrieve ABI data. Raises on error. | `name: value`, `content_types: value`, `opts: value` |
| `abi` | 3 | Retrieve ABI data from an ENS name's resolver (ENSIP-7). | `name: value`, `content_types: value`, `opts: value` |
| `pubkey!` | 2 | Retrieve the ECDSA public key. Raises on error. | `name: value`, `opts: value` |
| `pubkey` | 2 | Retrieve the ECDSA public key from an ENS name's resolver. | `name: value`, `opts: value` |
| `contenthash!` | 2 | Retrieve the contenthash record. Raises on error. | `name: value`, `opts: value` |
| `contenthash` | 2 | Retrieve the contenthash record from an ENS name's resolver. | `name: value`, `opts: value` |
| `text!` | 3 | Retrieve a text record. Raises on error. | `name: value`, `key: value`, `opts: value` |
| `text` | 3 | Retrieve a text record from an ENS name's resolver. | `name: value`, `key: value`, `opts: value` |
| `reverse!` | 2 | Reverse-resolve an ETH address to an ENS name. Raises on error. | `address: value`, `opts: value` |
| `reverse` | 2 | Reverse-resolve an ETH address to an ENS name. | `address: value`, `opts: value` |
| `resolve!` | 2 | Resolve an ENS name to an ETH address. Raises on error. | `name: value`, `opts: value` |
| `resolve` | 2 | Resolve an ENS name to an ETH address (forward resolution). | `name: value`, `opts: value` |
| `resolver!` | 2 | Look up the resolver contract address. Raises on error. | `name: value`, `opts: value` |
| `resolver` | 2 | Look up the resolver contract address for an ENS name. | `name: value`, `opts: value` |
| `namehash!` | 1 | Compute the EIP-137 namehash. Raises on error. | `name: value` |
| `namehash` | 1 | Compute the EIP-137 namehash for an ENS name. | `name: value` |

# `abi`

```elixir
@spec abi(String.t(), non_neg_integer(), keyword()) ::
  {:ok, {non_neg_integer(), binary()}} | {:error, term()}
```

Retrieve ABI data from an ENS name's resolver (ENSIP-7).

## Parameters

  * `name` - ENS name (e.g., "vitalik.eth") (value)
  * `content_types` - Bitmask of content types: 1=JSON, 2=zlib, 4=CBOR, 8=URI (value)
  * `opts` - Options: :rpc_url, :timeout, :registry (default: `[]`, value)

## Returns

Tuple of \{content_type, abi_data\} (`{:ok, {non_neg_integer(), binary()}} | {:error, term()}`)

```elixir
# descripex:contract
%{
  params: %{
    name: %{description: "ENS name (e.g., \"vitalik.eth\")", kind: :value},
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout, :registry",
      kind: :value
    },
    content_types: %{
      description: "Bitmask of content types: 1=JSON, 2=zlib, 4=CBOR, 8=URI",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, {non_neg_integer(), binary()}} | {:error, term()}",
    description: "Tuple of {content_type, abi_data}"
  }
}
```

# `abi!`

```elixir
@spec abi!(String.t(), non_neg_integer(), keyword()) :: {non_neg_integer(), binary()}
```

Retrieve ABI data. Raises on error.

## Parameters

  * `name` - ENS name (e.g., "vitalik.eth") (value)
  * `content_types` - Bitmask of content types: 1=JSON, 2=zlib, 4=CBOR, 8=URI (value)
  * `opts` - Options: :rpc_url, :timeout, :registry (default: `[]`, value)

## Returns

Tuple of \{content_type, abi_data\} (`{non_neg_integer(), binary()}`)

```elixir
# descripex:contract
%{
  params: %{
    name: %{description: "ENS name (e.g., \"vitalik.eth\")", kind: :value},
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout, :registry",
      kind: :value
    },
    content_types: %{
      description: "Bitmask of content types: 1=JSON, 2=zlib, 4=CBOR, 8=URI",
      kind: :value
    }
  },
  returns: %{
    type: "{non_neg_integer(), binary()}",
    description: "Tuple of {content_type, abi_data}"
  }
}
```

# `contenthash`

```elixir
@spec contenthash(
  String.t(),
  keyword()
) :: {:ok, binary()} | {:error, term()}
```

Retrieve the contenthash record from an ENS name's resolver.

## Parameters

  * `name` - ENS name (e.g., "vitalik.eth") (value)
  * `opts` - Options: :rpc_url, :timeout, :registry (default: `[]`, value)

## Returns

Raw contenthash bytes (ENSIP-7 encoded) (`{:ok, binary()} | {:error, term()}`)

```elixir
# descripex:contract
%{
  params: %{
    name: %{description: "ENS name (e.g., \"vitalik.eth\")", kind: :value},
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout, :registry",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, binary()} | {:error, term()}",
    description: "Raw contenthash bytes (ENSIP-7 encoded)"
  }
}
```

# `contenthash!`

```elixir
@spec contenthash!(
  String.t(),
  keyword()
) :: binary()
```

Retrieve the contenthash record. Raises on error.

## Parameters

  * `name` - ENS name (e.g., "vitalik.eth") (value)
  * `opts` - Options: :rpc_url, :timeout, :registry (default: `[]`, value)

## Returns

Raw contenthash bytes (`binary()`)

```elixir
# descripex:contract
%{
  params: %{
    name: %{description: "ENS name (e.g., \"vitalik.eth\")", kind: :value},
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout, :registry",
      kind: :value
    }
  },
  returns: %{type: "binary()", description: "Raw contenthash bytes"}
}
```

# `namehash`

```elixir
@spec namehash(String.t()) :: {:ok, binary()} | {:error, {:invalid_name, String.t()}}
```

Compute the EIP-137 namehash for an ENS name.

## Parameters

  * `name` - ENS name, e.g. "vitalik.eth" (value)

## Returns

32-byte keccak256 node hash per EIP-137 (`{:ok, <<_::256>>} | {:error, term}`)

```elixir
# descripex:contract
%{
  params: %{
    name: %{description: "ENS name, e.g. \"vitalik.eth\"", kind: :value}
  },
  returns: %{
    type: "{:ok, <<_::256>>} | {:error, term}",
    description: "32-byte keccak256 node hash per EIP-137"
  }
}
```

# `namehash!`

```elixir
@spec namehash!(String.t()) :: binary()
```

Compute the EIP-137 namehash. Raises on error.

## Parameters

  * `name` - ENS name, e.g. "vitalik.eth" (value)

## Returns

32-byte keccak256 node hash (`<<_::256>>`)

```elixir
# descripex:contract
%{
  params: %{
    name: %{description: "ENS name, e.g. \"vitalik.eth\"", kind: :value}
  },
  returns: %{type: "<<_::256>>", description: "32-byte keccak256 node hash"}
}
```

# `pubkey`

```elixir
@spec pubkey(
  String.t(),
  keyword()
) :: {:ok, {binary(), binary()}} | {:error, term()}
```

Retrieve the ECDSA public key from an ENS name's resolver.

## Parameters

  * `name` - ENS name (e.g., "vitalik.eth") (value)
  * `opts` - Options: :rpc_url, :timeout, :registry (default: `[]`, value)

## Returns

Tuple of \{x, y\} 32-byte coordinates (`{:ok, {binary(), binary()}} | {:error, term()}`)

```elixir
# descripex:contract
%{
  params: %{
    name: %{description: "ENS name (e.g., \"vitalik.eth\")", kind: :value},
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout, :registry",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, {binary(), binary()}} | {:error, term()}",
    description: "Tuple of {x, y} 32-byte coordinates"
  }
}
```

# `pubkey!`

```elixir
@spec pubkey!(
  String.t(),
  keyword()
) :: {binary(), binary()}
```

Retrieve the ECDSA public key. Raises on error.

## Parameters

  * `name` - ENS name (e.g., "vitalik.eth") (value)
  * `opts` - Options: :rpc_url, :timeout, :registry (default: `[]`, value)

## Returns

Tuple of \{x, y\} 32-byte coordinates (`{binary(), binary()}`)

```elixir
# descripex:contract
%{
  params: %{
    name: %{description: "ENS name (e.g., \"vitalik.eth\")", kind: :value},
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout, :registry",
      kind: :value
    }
  },
  returns: %{
    type: "{binary(), binary()}",
    description: "Tuple of {x, y} 32-byte coordinates"
  }
}
```

# `resolve`

```elixir
@spec resolve(
  String.t(),
  keyword()
) :: {:ok, String.t()} | {:error, term()}
```

Resolve an ENS name to an ETH address (forward resolution).

## Parameters

  * `name` - ENS name (e.g., "vitalik.eth") (value)
  * `opts` - Options: :rpc_url, :timeout, :registry (default: `[]`, value)

## Returns

EIP-55 checksummed ETH address (`{:ok, String.t()} | {:error, term()}`)

```elixir
# descripex:contract
%{
  params: %{
    name: %{description: "ENS name (e.g., \"vitalik.eth\")", kind: :value},
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout, :registry",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, String.t()} | {:error, term()}",
    description: "EIP-55 checksummed ETH address"
  }
}
```

# `resolve!`

```elixir
@spec resolve!(
  String.t(),
  keyword()
) :: String.t()
```

Resolve an ENS name to an ETH address. Raises on error.

## Parameters

  * `name` - ENS name (e.g., "vitalik.eth") (value)
  * `opts` - Options: :rpc_url, :timeout, :registry (default: `[]`, value)

## Returns

EIP-55 checksummed ETH address (`String.t()`)

```elixir
# descripex:contract
%{
  params: %{
    name: %{description: "ENS name (e.g., \"vitalik.eth\")", kind: :value},
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout, :registry",
      kind: :value
    }
  },
  returns: %{type: "String.t()", description: "EIP-55 checksummed ETH address"}
}
```

# `resolver`

```elixir
@spec resolver(
  String.t(),
  keyword()
) :: {:ok, String.t()} | {:error, term()}
```

Look up the resolver contract address for an ENS name.

## Parameters

  * `name` - ENS name (e.g., "vitalik.eth") (value)
  * `opts` - Options: :rpc_url, :timeout, :registry (default: `[]`, value)

## Returns

EIP-55 checksummed resolver address (`{:ok, String.t()} | {:error, term()}`)

```elixir
# descripex:contract
%{
  params: %{
    name: %{description: "ENS name (e.g., \"vitalik.eth\")", kind: :value},
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout, :registry",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, String.t()} | {:error, term()}",
    description: "EIP-55 checksummed resolver address"
  }
}
```

# `resolver!`

```elixir
@spec resolver!(
  String.t(),
  keyword()
) :: String.t()
```

Look up the resolver contract address. Raises on error.

## Parameters

  * `name` - ENS name (e.g., "vitalik.eth") (value)
  * `opts` - Options: :rpc_url, :timeout, :registry (default: `[]`, value)

## Returns

EIP-55 checksummed resolver address (`String.t()`)

```elixir
# descripex:contract
%{
  params: %{
    name: %{description: "ENS name (e.g., \"vitalik.eth\")", kind: :value},
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout, :registry",
      kind: :value
    }
  },
  returns: %{
    type: "String.t()",
    description: "EIP-55 checksummed resolver address"
  }
}
```

# `reverse`

```elixir
@spec reverse(
  String.t() | binary(),
  keyword()
) :: {:ok, String.t()} | {:error, term()}
```

Reverse-resolve an ETH address to an ENS name.

## Parameters

  * `address` - ETH address as 0x hex string or 20-byte binary (value)
  * `opts` - Options: :rpc_url, :timeout, :registry (default: `[]`, value)

## Returns

ENS name (e.g., "vitalik.eth") (`{:ok, String.t()} | {:error, term()}`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout, :registry",
      kind: :value
    },
    address: %{
      description: "ETH address as 0x hex string or 20-byte binary",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, String.t()} | {:error, term()}",
    description: "ENS name (e.g., \"vitalik.eth\")"
  }
}
```

# `reverse!`

```elixir
@spec reverse!(
  String.t() | binary(),
  keyword()
) :: String.t()
```

Reverse-resolve an ETH address to an ENS name. Raises on error.

## Parameters

  * `address` - ETH address as 0x hex string or 20-byte binary (value)
  * `opts` - Options: :rpc_url, :timeout, :registry (default: `[]`, value)

## Returns

ENS name (e.g., "vitalik.eth") (`String.t()`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout, :registry",
      kind: :value
    },
    address: %{
      description: "ETH address as 0x hex string or 20-byte binary",
      kind: :value
    }
  },
  returns: %{
    type: "String.t()",
    description: "ENS name (e.g., \"vitalik.eth\")"
  }
}
```

# `text`

```elixir
@spec text(String.t(), String.t(), keyword()) :: {:ok, String.t()} | {:error, term()}
```

Retrieve a text record from an ENS name's resolver.

## Parameters

  * `name` - ENS name (e.g., "vitalik.eth") (value)
  * `key` - Text record key (e.g., "avatar", "url", "com.twitter") (value)
  * `opts` - Options: :rpc_url, :timeout, :registry (default: `[]`, value)

## Returns

Text record value (`{:ok, String.t()} | {:error, term()}`)

```elixir
# descripex:contract
%{
  params: %{
    name: %{description: "ENS name (e.g., \"vitalik.eth\")", kind: :value},
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout, :registry",
      kind: :value
    },
    key: %{
      description: "Text record key (e.g., \"avatar\", \"url\", \"com.twitter\")",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, String.t()} | {:error, term()}",
    description: "Text record value"
  }
}
```

# `text!`

```elixir
@spec text!(String.t(), String.t(), keyword()) :: String.t()
```

Retrieve a text record. Raises on error.

## Parameters

  * `name` - ENS name (e.g., "vitalik.eth") (value)
  * `key` - Text record key (e.g., "avatar", "url", "com.twitter") (value)
  * `opts` - Options: :rpc_url, :timeout, :registry (default: `[]`, value)

## Returns

Text record value (`String.t()`)

```elixir
# descripex:contract
%{
  params: %{
    name: %{description: "ENS name (e.g., \"vitalik.eth\")", kind: :value},
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout, :registry",
      kind: :value
    },
    key: %{
      description: "Text record key (e.g., \"avatar\", \"url\", \"com.twitter\")",
      kind: :value
    }
  },
  returns: %{type: "String.t()", description: "Text record value"}
}
```

---

*Consult [api-reference.md](api-reference.md) for complete listing*
