CAIP-122 message text for Sign-In-With-X challenges.
Builds the exact text a wallet signs for a SIWX proof, routed by the
CAIP-2 namespace of chainId:
eip155:*— EIP-4361 (Sign-In With Ethereum), whoseChain IDline carries the numeric chain reference. When no statement is present the address is followed by two blank lines, as the EIP-4361 ABNF (and the referencesiwelibrary) produce.solana:*— Sign-In With Solana, identical apart from theSolana accountheader and the genesis-hash chain reference. Without a statement the address is followed by a single blank line, matching the reference x402 implementation.
Field values are read from the wire (camelCase string) keys or their
snake_case atom equivalents, so both a decoded SIGN-IN-WITH-X payload
and a locally built map work.
Summary
Functions
Builds the CAIP-122 message text for a fields map.
Returns the chain reference rendered on the Chain ID line.
Returns the chain family of a CAIP-2 chain id.
Returns the signature type a chain family authenticates with.
Types
@type build_error() :: :invalid_fields | :invalid_chain_id | :unsupported_chain
Errors returned by build/1.
@type family() :: :eip155 | :solana
Chain family derived from a CAIP-2 chain id.
Functions
@spec build(map()) :: {:ok, String.t()} | {:error, build_error()}
Builds the CAIP-122 message text for a fields map.
Requires domain, address, uri, version, chainId, nonce, and
issuedAt; statement, expirationTime, notBefore, requestId, and
resources are optional and omitted from the text when absent (an empty
resources list is treated as absent).
Examples
iex> {:ok, text} = X402.Extensions.SIWX.Message.build(%{
...> "domain" => "api.example.com",
...> "address" => "0x857b06519E91e3A54538791bDbb0E22373e36b66",
...> "statement" => "Sign in to access premium data",
...> "uri" => "https://api.example.com/premium-data",
...> "version" => "1",
...> "chainId" => "eip155:8453",
...> "nonce" => "a1b2c3d4e5f67890a1b2c3d4e5f67890",
...> "issuedAt" => "2024-01-15T10:30:00.000Z",
...> "expirationTime" => "2024-01-15T10:35:00.000Z",
...> "resources" => ["https://api.example.com/premium-data"]
...> })
iex> String.split(text, "\n")
[
"api.example.com wants you to sign in with your Ethereum account:",
"0x857b06519E91e3A54538791bDbb0E22373e36b66",
"",
"Sign in to access premium data",
"",
"URI: https://api.example.com/premium-data",
"Version: 1",
"Chain ID: 8453",
"Nonce: a1b2c3d4e5f67890a1b2c3d4e5f67890",
"Issued At: 2024-01-15T10:30:00.000Z",
"Expiration Time: 2024-01-15T10:35:00.000Z",
"Resources:",
"- https://api.example.com/premium-data"
]
iex> X402.Extensions.SIWX.Message.build(%{"domain" => "api.example.com"})
{:error, :invalid_fields}
Returns the chain reference rendered on the Chain ID line.
Examples
iex> X402.Extensions.SIWX.Message.chain_reference("eip155:8453")
"8453"
iex> X402.Extensions.SIWX.Message.chain_reference("solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp")
"5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
Returns the chain family of a CAIP-2 chain id.
Examples
iex> X402.Extensions.SIWX.Message.family("eip155:8453")
{:ok, :eip155}
iex> X402.Extensions.SIWX.Message.family("solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp")
{:ok, :solana}
iex> X402.Extensions.SIWX.Message.family("eip155:base")
{:error, :invalid_chain_id}
iex> X402.Extensions.SIWX.Message.family("cosmos:hub")
{:error, :unsupported_chain}
Returns the signature type a chain family authenticates with.
Examples
iex> X402.Extensions.SIWX.Message.signature_type(:eip155)
"eip191"
iex> X402.Extensions.SIWX.Message.signature_type(:solana)
"ed25519"