Raxol.Payments.Xochi.Capabilities (Raxol Payments v0.2.0)

Copy Markdown View Source

Client for the solver capability matrix -- the single source of truth for which chains, tokens, and pull methods the Xochi/Riddler stack can settle right now.

The Xochi worker proxies Riddler's GET /xochi/capabilities at GET /api/capabilities, wrapping the solver's wire payload verbatim in %{"source" => "live" | "fallback", "capabilities" => matrix}. This module fetches that route (never riddler.axol.io directly -- the worker is the sole agent surface), parses the matrix tolerantly, and exposes the corridor predicates ACP offerings route through instead of the hardcoded Raxol.Payments.Assets tables. New solver chains (Tron, Solana) then light up for the agent with zero raxol redeploy.

Wire tolerance

  • The current matrix emits chains as chain_id + chain_name only; vm_type and address_format arrive with Riddler's WP-E. Absent vm_type defaults to :evm.
  • Unknown keys are dropped; individually malformed chain/token entries are skipped; a structurally unusable body parses to :error and callers fall back to fallback/0.
  • Both the worker-wrapped shape and a bare matrix are accepted.

Degrade policy

fallback/0 is derived from Raxol.Payments.Assets (the legacy hardcoded mirror of Riddler's token registry), so an unreachable capabilities endpoint degrades to exactly today's behavior -- never to rejecting jobs the solver could fill yesterday.

Caching

get/1 is an ETS-backed read-through with a TTL (default 300s), following the Raxol.Payments.Checkpoint.ETS lazy-table pattern. Pure fetch/1 / parse/1 stay side-effect-free for tests and callers that manage their own lifetime.

Summary

Functions

Chain ids the solver currently offers.

The pinned deposit-route attestation signer (lowercased 0x), or nil when the matrix does not publish one (a deposit-route verify then fails closed).

Static fallback derived from Raxol.Payments.Assets -- the six EVM chains and the solver-fillable token set, direction-blind (both roles), exactly reproducing the pre-capabilities gate.

Fetch and parse the capability matrix from the Xochi worker.

True when (chain_id, token_address) is a solver-fillable corridor leg for role. A token qualifies when the matrix registers that address on that chain with the given role. Address comparison is case-insensitive for EVM legs and case-sensitive otherwise (base58 is case-significant).

Cached capabilities: serve the ETS-cached matrix while fresh, refresh from the worker when stale, and degrade to the previous cached value -- or fallback/0 when nothing was ever fetched -- on any failure.

Parse a capability payload -- the worker's wrapped shape or a bare matrix.

Drop the cached matrix (tests / operator refresh).

Structural address validity for a chain, dispatched on the chain's VM family: EVM -> 0x + 40 hex; TVM -> full Base58Check verification via Raxol.Payments.Tron.Address.valid?/1; SVM -> base58, 32-44 chars.

VM family for a chain id; :evm when the chain is unknown.

Types

chain()

@type chain() :: %{
  chain_id: pos_integer(),
  chain_name: String.t(),
  vm_type: vm_type()
}

role()

@type role() :: :origin | :destination

t()

@type t() :: %{
  source: :live | :fallback,
  chains: [chain()],
  tokens: [token()],
  deposit_attestation_signer: String.t() | nil
}

token()

@type token() :: %{
  symbol: String.t(),
  roles: [role()],
  addresses: %{optional(pos_integer()) => String.t()}
}

vm_type()

@type vm_type() :: :evm | :tvm | :svm

Functions

chain_ids(map)

@spec chain_ids(t()) :: [pos_integer()]

Chain ids the solver currently offers.

deposit_attestation_signer(arg1)

@spec deposit_attestation_signer(t()) :: String.t() | nil

The pinned deposit-route attestation signer (lowercased 0x), or nil when the matrix does not publish one (a deposit-route verify then fails closed).

fallback()

@spec fallback() :: t()

Static fallback derived from Raxol.Payments.Assets -- the six EVM chains and the solver-fillable token set, direction-blind (both roles), exactly reproducing the pre-capabilities gate.

fetch(config, opts \\ [])

@spec fetch(
  map(),
  keyword()
) :: {:ok, t()} | {:error, term()}

Fetch and parse the capability matrix from the Xochi worker.

config needs :base_url (e.g. %{base_url: "https://api.xochi.fi"}); optional :req_options merge into the request (e.g. plug: for Req.Test). Auth is not required -- the route is public.

Returns {:ok, t()} or {:error, reason}; callers wanting the degrade behavior use get/1 or fallback/0 instead of handling the error.

fillable?(caps, chain_id, token_address, role)

@spec fillable?(t(), pos_integer() | String.t() | nil, String.t() | nil, role()) ::
  boolean()

True when (chain_id, token_address) is a solver-fillable corridor leg for role. A token qualifies when the matrix registers that address on that chain with the given role. Address comparison is case-insensitive for EVM legs and case-sensitive otherwise (base58 is case-significant).

get(config, opts \\ [])

@spec get(
  map() | nil,
  keyword()
) :: t()

Cached capabilities: serve the ETS-cached matrix while fresh, refresh from the worker when stale, and degrade to the previous cached value -- or fallback/0 when nothing was ever fetched -- on any failure.

config as in fetch/1; nil config skips the network entirely and returns fallback/0, so callers without a configured worker keep the static behavior. Options: :ttl_ms (default 300000).

parse(matrix)

@spec parse(term()) :: {:ok, t()} | :error

Parse a capability payload -- the worker's wrapped shape or a bare matrix.

Returns {:ok, t()} or :error when the body is structurally unusable (callers fall back). Individually malformed entries are dropped, not fatal.

reset()

@spec reset() :: :ok

Drop the cached matrix (tests / operator refresh).

valid_address?(caps, chain_id, address)

@spec valid_address?(t(), pos_integer() | String.t() | nil, String.t() | nil) ::
  boolean()

Structural address validity for a chain, dispatched on the chain's VM family: EVM -> 0x + 40 hex; TVM -> full Base58Check verification via Raxol.Payments.Tron.Address.valid?/1; SVM -> base58, 32-44 chars.

vm_type(map, chain_id)

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

VM family for a chain id; :evm when the chain is unknown.