Packages

An Elixir client for the Polymarket API.

Current section

Files

Jump to
ex_polymarket lib schemas credentials.ex
Raw

lib/schemas/credentials.ex

defmodule Polymarket.Schemas.Credentials do
@moduledoc """
Polymarket CLOB **API credentials**: the `api_key`, `secret` and `passphrase`
returned by `GET /auth/derive-api-key` and `POST /auth/api-key`, plus the
`address` of the EOA that owns them.
These authorise the L2 (HMAC) headers on every authenticated CLOB request, so
the `secret` and `passphrase` are sensitive. To avoid leaking them through logs
or crash reports, only `api_key` and `address` are shown when inspected.
`address` is the wallet that signed the L1 auth (the api-key owner) and is sent
as the `POLY_ADDRESS` L2 header. `from_attrs/1` cannot know it — the
`Polymarket.Clob` auth functions populate it from the signing key — so it is
`nil` on a freshly parsed `Credentials`.
"""
use TypedStruct
alias Polymarket.JsonUtil
# Redact the secret material: inspecting (and JSON-encoding) shows only api_key
# and the (public) address — never the secret/passphrase.
@derive {Inspect, only: [:api_key, :address]}
@derive {Jason.Encoder, only: [:api_key, :address]}
typedstruct do
field(:api_key, String.t(), enforce: true)
field(:secret, String.t(), enforce: true)
field(:passphrase, String.t(), enforce: true)
field(:address, <<_::160>> | nil, default: nil)
end
@doc """
Builds a `Credentials` from the raw (JSON-decoded) attributes returned by the
CLOB auth endpoints. Keys may be `camelCase` (e.g. `apiKey`); they are
normalised to the struct's `snake_case` fields. Returns
`{:error, :invalid_credentials}` if any of the three string fields is missing.
"""
@spec from_attrs(map()) :: {:ok, t()} | {:error, :invalid_credentials}
def from_attrs(attrs) when is_map(attrs) do
normalized = JsonUtil.snake_case_keys(attrs)
with {:ok, api_key} <- fetch_string(normalized, "api_key"),
{:ok, secret} <- fetch_string(normalized, "secret"),
{:ok, passphrase} <- fetch_string(normalized, "passphrase") do
{:ok, %__MODULE__{api_key: api_key, secret: secret, passphrase: passphrase}}
end
end
@spec fetch_string(map(), String.t()) :: {:ok, String.t()} | {:error, :invalid_credentials}
defp fetch_string(map, key) do
case Map.get(map, key) do
value when is_binary(value) -> {:ok, value}
_other -> {:error, :invalid_credentials}
end
end
end