Server-side Sign-In-With-X challenge advertisements.
A challenge is the value a server places under
PaymentRequired.extensions["sign-in-with-x"]: the CAIP-122 message
metadata (info), the chains it accepts proofs from (supportedChains),
and the JSON schema of the proof (schema). build/1 produces one with a
fresh nonce and timestamps; X402.Extensions.SIWX.challenge/1 is the
public entry point.
Summary
Types
A normalized supportedChains entry.
Types
Functions
Builds the sign-in-with-x advertisement map.
Raises NimbleOptions.ValidationError for invalid options (programmer
error). Timestamps are rendered in ISO 8601 with millisecond precision.
Options
:domain(String.t/0) - Required. The server's public host (info.domain), e.g."api.example.com".:uri(String.t/0) - Required. The URI the proof is bound to (info.uri), e.g."https://api.example.com".:supported_chains- Required. Chains proofs are accepted from: a list of maps or keyword lists with:chain_id(CAIP-2,eip155:*orsolana:*), an optional:type("eip191"foreip155,"ed25519"forsolana; derived from the chain when omitted), and an optional:signature_schemehint ("eip191","eip1271","eip6492", or"siws").:statement- Human-readable purpose shown by the wallet (info.statement). The default value isnil.:resources(list ofString.t/0) - URIs associated with the request (info.resources); omitted when empty. The default value is[].:version(String.t/0) - CAIP-122 version. Always"1". The default value is"1".:nonce- Explicit nonce; a freshgenerate_nonce/0value when omitted. The default value isnil.:issued_at- Challenge creation time;DateTime.utc_now/0when omitted. The default value isnil.:expiration_seconds(pos_integer/0) - Seconds afterissued_atat which the challenge expires (info.expirationTime). The default value is300.:not_before- Optionalinfo.notBefore. The default value isnil.:request_id- Optional correlation id (info.requestId). The default value isnil.
Examples
iex> challenge = X402.Extensions.SIWX.Challenge.build(
...> domain: "api.example.com",
...> uri: "https://api.example.com",
...> supported_chains: [%{chain_id: "eip155:8453"}],
...> nonce: "a1b2c3d4e5f67890a1b2c3d4e5f67890",
...> issued_at: ~U[2024-01-15 10:30:00Z]
...> )
iex> challenge["info"]
%{
"domain" => "api.example.com",
"uri" => "https://api.example.com",
"version" => "1",
"nonce" => "a1b2c3d4e5f67890a1b2c3d4e5f67890",
"issuedAt" => "2024-01-15T10:30:00.000Z",
"expirationTime" => "2024-01-15T10:35:00.000Z"
}
iex> challenge["supportedChains"]
[%{"chainId" => "eip155:8453", "type" => "eip191"}]
@spec format_datetime(DateTime.t()) :: String.t()
Formats a DateTime as ISO 8601 UTC with millisecond precision.
Examples
iex> X402.Extensions.SIWX.Challenge.format_datetime(~U[2024-01-15 10:30:00Z])
"2024-01-15T10:30:00.000Z"
iex> X402.Extensions.SIWX.Challenge.format_datetime(~U[2024-01-15 10:30:00.123456Z])
"2024-01-15T10:30:00.123Z"
@spec generate_nonce() :: String.t()
Generates a nonce: 32 lowercase hex characters from 16 random bytes.
Examples
iex> nonce = X402.Extensions.SIWX.Challenge.generate_nonce()
iex> Regex.match?(~r/\A[a-f0-9]{32}\z/, nonce)
true
@spec schema() :: map()
Returns the JSON schema advertised under schema.
Examples
iex> X402.Extensions.SIWX.Challenge.schema()["required"]
["domain", "address", "uri", "version", "chainId", "type", "nonce", "issuedAt", "signature"]