# `Ethers.Siwe`
[🔗](https://github.com/ExWeb3/elixir_ethers/blob/v0.8.0/lib/ethers/siwe.ex#L1)

Sign-In with Ethereum ([EIP-4361](https://eips.ethereum.org/EIPS/eip-4361)) messages.

SIWE is the standard way for a wallet to authenticate to an off-chain service: the backend
issues a nonce, the wallet signs a structured plaintext message containing it, and the
backend parses, validates and verifies the returned message and signature.

This module covers the message lifecycle:

- `new/1` / `new!/1` - build an `Ethers.Siwe.Message`
- `generate_nonce/0` - generate a cryptographically random nonce
- `to_message/1` - render the EIP-4361 string for the wallet to sign
- `parse/1` - parse a message string received from a client
- `validate/2` - stateless validation (validity window, domain/nonce/address binding)
- `verify/3` - the one-call backend flow: parse, validate and check the signature
  (including ERC-1271/ERC-6492 smart-contract wallets)

See the [Sign-In with Ethereum guide](siwe.html) for a complete Phoenix integration recipe.

## Example

    iex> {:ok, message} =
    ...>   Ethers.Siwe.new(
    ...>     domain: "example.com",
    ...>     address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
    ...>     statement: "Sign in to Example",
    ...>     uri: "https://example.com/login",
    ...>     chain_id: 1,
    ...>     nonce: "32891756",
    ...>     issued_at: "2021-09-30T16:25:24.000Z"
    ...>   )
    iex> Ethers.Siwe.to_message(message)
    "example.com wants you to sign in with your Ethereum account:\n0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\n\nSign in to Example\n\nURI: https://example.com/login\nVersion: 1\nChain ID: 1\nNonce: 32891756\nIssued At: 2021-09-30T16:25:24.000Z"

# `generate_nonce`

```elixir
@spec generate_nonce() :: String.t()
```

Generates a cryptographically random alphanumeric nonce with at least 8 characters.

Uses 128 bits of entropy from `:crypto.strong_rand_bytes/1` encoded in base 36.

# `new`

```elixir
@spec new(Keyword.t() | map()) :: {:ok, Ethers.Siwe.Message.t()} | {:error, atom()}
```

Builds a new `Ethers.Siwe.Message` from a keyword list or map.

Accepts atom keys (`:chain_id`) or string keys (`"chain_id"`). Required fields are
`:domain`, `:address`, `:uri`, `:chain_id` and `:nonce`. `:version` defaults to `"1"`
(the only defined version) and `:issued_at` defaults to the current UTC time.

The address is normalized to its EIP-55 checksummed form. Timestamp fields accept either
`DateTime` structs or RFC 3339 strings (kept verbatim).

Returns `{:error, reason}` with a descriptive atom (e.g. `:missing_domain`,
`:invalid_nonce`) when a field is missing or invalid.

## Examples

    iex> {:ok, message} =
    ...>   Ethers.Siwe.new(
    ...>     domain: "example.com",
    ...>     address: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
    ...>     uri: "https://example.com",
    ...>     chain_id: 1,
    ...>     nonce: Ethers.Siwe.generate_nonce()
    ...>   )
    iex> message.address
    "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
    iex> message.version
    "1"

    iex> Ethers.Siwe.new(domain: "example.com")
    {:error, :missing_address}

# `new!`

```elixir
@spec new!(Keyword.t() | map()) :: Ethers.Siwe.Message.t() | no_return()
```

Same as `new/1` but raises `ArgumentError` on error.

# `parse`

```elixir
@spec parse(String.t()) :: {:ok, Ethers.Siwe.Message.t()} | {:error, atom()}
```

Parses an EIP-4361 message string into an `Ethers.Siwe.Message`.

Parsing is strict: field order, the EIP-55 checksummed address, the nonce format, RFC 3339
timestamps and RFC 3986 domain/URIs are all enforced. A successfully parsed message
re-renders byte-for-byte identical via `to_message/1`.

## Examples

    iex> raw =
    ...>   "example.com wants you to sign in with your Ethereum account:\n" <>
    ...>     "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\n\n\n" <>
    ...>     "URI: https://example.com\nVersion: 1\nChain ID: 1\n" <>
    ...>     "Nonce: 32891756\nIssued At: 2021-09-30T16:25:24.000Z"
    iex> {:ok, message} = Ethers.Siwe.parse(raw)
    iex> {message.domain, message.chain_id, message.statement}
    {"example.com", 1, nil}

# `to_message`

```elixir
@spec to_message(Ethers.Siwe.Message.t()) :: String.t()
```

Renders the message as the EIP-4361 string the wallet will sign.

The `String.Chars` protocol is also implemented for `Ethers.Siwe.Message`, so
`to_string/1` and string interpolation work as well.

# `validate`

```elixir
@spec validate(Ethers.Siwe.Message.t(), Keyword.t()) :: :ok | {:error, atom()}
```

Statelessly validates a message against the current time and the expected binding values.

This performs no signature verification and no RPC requests - it only checks the message
fields themselves.

## Options

- `:time` - the `DateTime` to check the validity window against. Defaults to
  `DateTime.utc_now/0`. The message is invalid at and after `expiration_time` (exclusive
  upper bound) and before `not_before` (inclusive lower bound).
- `:domain` - the domain this backend expects (exact match).
- `:scheme` - the scheme this backend expects (exact match).
- `:nonce` - the nonce this backend issued for the session (exact match).
- `:address` - the expected signing address (case-insensitive comparison).

Omitted options are not checked.

Returns `:ok` or `{:error, reason}` where reason is one of `:invalid_version`, `:expired`,
`:not_yet_valid`, `:invalid_expiration_time`, `:invalid_not_before`, `:domain_mismatch`,
`:scheme_mismatch`, `:nonce_mismatch`, `:address_mismatch` or `:invalid_address`.

# `verify`

```elixir
@spec verify(String.t() | Ethers.Siwe.Message.t(), binary(), Keyword.t()) ::
  {:ok, Ethers.Siwe.Message.t()} | {:error, term()}
```

Verifies a SIWE message end-to-end: parse (when given a string), validate the fields and
check the signature.

This is the one-call backend flow. The signature check uses
`Ethers.Signature.verify_message/4`, so EOA signatures verify locally without any RPC
round-trip while smart-contract wallets ([ERC-1271](https://eips.ethereum.org/EIPS/eip-1271),
deployed or not — [ERC-6492](https://eips.ethereum.org/EIPS/eip-6492)) are verified with a
single `eth_call`.

## Parameters

- `message`: The raw EIP-4361 message string received from the client, or an already-parsed
  `Ethers.Siwe.Message`. When given a string, the signature is verified over that exact
  string.
- `signature`: The signature as a `0x`-prefixed hex string or raw binary. ERC-6492-wrapped
  signatures are supported.
- `opts`: Options.

## Options

All of `validate/2`'s options (`:time`, `:domain`, `:scheme`, `:nonce`, `:address`) plus
the RPC options forwarded to `Ethers.Signature.verify_hash/4` for smart-wallet
verification: `:rpc_client`, `:rpc_opts` and `:block`.

## Returns

- `{:ok, message}` with the parsed/validated `Ethers.Siwe.Message` on success — trust
  `message.address` afterwards.
- `{:error, :invalid_signature}` if the signature does not verify for `message.address`.
- `{:error, reason}` for parse errors, `validate/2` errors or RPC transport failures.

---

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