Raxol.Payments.Assets (Raxol Payments v0.2.0)

Copy Markdown View Source

Asset decimals registry.

Centralizes the atomic-vs-human unit conversion that every payment protocol needs but no single protocol owns. Without this, SpendingPolicy caps written in human dollars (Decimal.new("1.00")) get compared against raw atomic amounts (USDC 1_000_000 = $1), and small policy caps reject every legitimate payment.

Lookups support two shapes:

  • decimals(chain_id, contract_address) -- for protocols that carry both the chain id and the ERC-20 contract address (x402).
  • decimals(ticker) -- for protocols that only carry a currency symbol (MPP).

Unknown assets default to 6 decimals (USDC convention on the major networks). This is the safe-ish default for stablecoin flows and the wrong default for native-token flows -- always pass the contract or ticker for non-USDC payments.

Adding new assets

Add to @addresses (chain+contract) or @tickers (symbol). Keep addresses lowercase. Tests in test/raxol/payments/assets_test.exs pin the known set.

Summary

Functions

Resolve a token symbol to its contract address on chain_id.

Human-readable chain name, or "Chain <id>" when unknown.

Look up decimals by ticker symbol. Returns @default_decimals (6) when unknown. Case-insensitive.

Look up decimals by chain id and ERC-20 contract address.

The solver-fillable token table: symbol -> chain id -> lowercase address. Exposed for fallback derivation; prefer known?/2 / address/2 for lookups.

True when (chain_id, address) is a registered contract, i.e. decimals/2 returns a pinned value rather than the @default_decimals fallback. An unregistered token resolves to 6 decimals, which is wrong for an 18-decimal token like WETH. Case-insensitive; accepts an integer chain id or a CAIP-2 string. EVM only.

The native gas token decimals for chain_id. Every supported EVM chain uses 18; unknown chains default to 18.

The native gas token symbol for chain_id (e.g. "ETH", "POL"). Accepts an integer chain id or a CAIP-2 string; returns nil for an unknown chain.

The EVM chain ids covered by the solver-fillable table, ascending. This is the static universe Raxol.Payments.Xochi.Capabilities.fallback/0 derives from; live corridor availability comes from the capabilities endpoint.

Resolve a (chain_id, contract_address) back to its token symbol: the reverse of address/2. Covers the solver-fillable set (USDC, USDT, WETH, plus USDG on Robinhood Chain) on the six EVM chains. Case-insensitive; accepts an integer chain id or a CAIP-2 string. Returns nil for an unregistered pair, so a caller can tell "same asset" from "unknown" without guessing.

The token symbols with a resolvable address/2 (USDC, USDT, WETH).

Convert a human-decimal amount to atomic units.

Convert an atomic-unit amount to a human-decimal Decimal.t/0.

True when address is a known USDC contract on chain_id. Case-insensitive; accepts an integer chain id or a CAIP-2 string. EVM only.

Functions

address(chain_id, symbol)

@spec address(integer() | String.t() | nil, String.t() | nil) ::
  {:ok, String.t()} | :error

Resolve a token symbol to its contract address on chain_id.

Covers the solver-fillable set (USDC, USDT, WETH, plus USDG on Robinhood Chain) across the six supported EVM chains (1, 10, 137, 8453, 42161, 4663). The symbol is case-insensitive; the chain id accepts an integer or a CAIP-2 string. Returns :error for an unknown (chain, symbol) pair.

chain_name(chain_id)

@spec chain_name(integer() | String.t() | nil) :: String.t()

Human-readable chain name, or "Chain <id>" when unknown.

decimals(ticker)

@spec decimals(String.t() | nil) :: pos_integer()

Look up decimals by ticker symbol. Returns @default_decimals (6) when unknown. Case-insensitive.

decimals(chain_id, contract_address)

@spec decimals(integer() | String.t() | nil, String.t() | nil) :: pos_integer()

Look up decimals by chain id and ERC-20 contract address.

Both chain_id integer and CAIP-2 string ("eip155:8453") are accepted. Address case is normalized. Returns @default_decimals (6) when unknown.

evm_tokens()

@spec evm_tokens() :: %{
  required(String.t()) => %{required(pos_integer()) => String.t()}
}

The solver-fillable token table: symbol -> chain id -> lowercase address. Exposed for fallback derivation; prefer known?/2 / address/2 for lookups.

known?(chain_id, address)

@spec known?(integer() | String.t() | nil, String.t() | nil) :: boolean()

True when (chain_id, address) is a registered contract, i.e. decimals/2 returns a pinned value rather than the @default_decimals fallback. An unregistered token resolves to 6 decimals, which is wrong for an 18-decimal token like WETH. Case-insensitive; accepts an integer chain id or a CAIP-2 string. EVM only.

native_decimals(chain_id)

@spec native_decimals(integer() | String.t() | nil) :: pos_integer()

The native gas token decimals for chain_id. Every supported EVM chain uses 18; unknown chains default to 18.

native_symbol(chain_id)

@spec native_symbol(integer() | String.t() | nil) :: String.t() | nil

The native gas token symbol for chain_id (e.g. "ETH", "POL"). Accepts an integer chain id or a CAIP-2 string; returns nil for an unknown chain.

supported_chain_ids()

@spec supported_chain_ids() :: [pos_integer()]

The EVM chain ids covered by the solver-fillable table, ascending. This is the static universe Raxol.Payments.Xochi.Capabilities.fallback/0 derives from; live corridor availability comes from the capabilities endpoint.

symbol_for(chain_id, address)

@spec symbol_for(integer() | String.t() | nil, String.t() | nil) :: String.t() | nil

Resolve a (chain_id, contract_address) back to its token symbol: the reverse of address/2. Covers the solver-fillable set (USDC, USDT, WETH, plus USDG on Robinhood Chain) on the six EVM chains. Case-insensitive; accepts an integer chain id or a CAIP-2 string. Returns nil for an unregistered pair, so a caller can tell "same asset" from "unknown" without guessing.

symbols()

@spec symbols() :: [String.t()]

The token symbols with a resolvable address/2 (USDC, USDT, WETH).

to_atomic(amount, decimals)

@spec to_atomic(integer() | String.t() | Decimal.t(), pos_integer()) ::
  non_neg_integer()

Convert a human-decimal amount to atomic units.

E.g. to_atomic("0.01", 6) -> 10_000.

to_human(amount, decimals)

@spec to_human(integer() | String.t() | Decimal.t(), pos_integer()) :: Decimal.t()

Convert an atomic-unit amount to a human-decimal Decimal.t/0.

E.g. to_human(10_000, 6) -> Decimal.new("0.01").

usdc?(chain_id, address)

@spec usdc?(integer() | String.t() | nil, String.t() | nil) :: boolean()

True when address is a known USDC contract on chain_id. Case-insensitive; accepts an integer chain id or a CAIP-2 string. EVM only.