Skip to main content
Minimal, type-safe HTTP client for the SODAX backend Swaps API.
  • Implements the ISwapsApiV2 contract from @sodax/types over fetch.
  • Validates every response at runtime with valibot, and transforms each chain-specific unsigned tx back to its domain shape (decimal-string → bigint, Injective index-object bytes → Uint8Array).
  • Zero dependency on @sodax/sdk, viem, or wallet providers — only @sodax/types (types) and valibot.
It is the single source of the swaps wire client: @sodax/sdk’s SwapsApiService (sodax.api.swaps) is a thin adapter over this package, adding the SDK’s Result<T> contract, logger, and transport-config resolution on top. Use @sodax/swaps-api directly when you want just the swaps backend without pulling in the full SDK.

Install

valibot is a regular dependency of this package, not a peer — installing it separately is unnecessary.

Usage

baseUrl is required and injected by the caller — the package never hardcodes environment URLs. Optionally set timeout (ms — an overall per-call deadline that includes retries; on expiry the call throws TIMEOUT_ERROR), a custom fetch (for tests or non-standard runtimes; it receives the timeout AbortSignal), extra headers, and an apiKey.

Methods

One method per Swaps API endpoint, mirroring ISwapsApiV2: quoteType is 'exact_input' — exact-output quoting is not supported.

API key

The backend guards POST /swaps/* routes with an x-api-key header check (keys are minted through the partner portal). Pass the key once at construction and the client sends it on every request:
An explicit headers: { 'x-api-key': ... } wins over the apiKey convenience option. For a different key per call, construct another client — instances are cheap and stateless. Keys bundled into a browser app are public by nature: from a browser, point baseUrl at your own backend proxy and attach the key there — see API key good practices. Auth failures surface as HTTP_ERROR with the backend’s status and message on context: 401 (missing or invalid key) and 403 (suspended organisation or missing scope) are terminal — fix the key, don’t retry. The one transient case, a 503 whose message is API key verification is temporarily unavailable (exported as API_KEY_VERIFICATION_UNAVAILABLE_MESSAGE), is retried automatically with a short backoff — for every call, mutations included, since the guard rejects before the route handler runs.

Partner fees

partnerFee has no default — this client forwards the body as given and does not read new Sodax({ fee }) / new Sodax({ swaps: { partnerFee } }). Send the same value on quote and create-intent:
checkAllowance / approve inherit the field but ignore it. See MONETIZE_SDK.md for the orchestrator path.

Errors

Every method throws a SwapsApiError on failure — a single typed error whose code is one of NETWORK_ERROR / TIMEOUT_ERROR / HTTP_ERROR / PARSE_ERROR / VALIDATION_ERROR, with diagnostic context (endpoint, method, path, HTTP status, validation issues) and the underlying failure on .cause. Idempotent calls (reads, polls, pure-compute POSTs like getQuote) retry transient HTTP / network failures up to 3 attempts in total — the first try plus two retries, back to back with no delay; a timeout and mutating calls are never retried — except the apiguard’s verification 503 (see “API key” above), which is replay-safe, retried for every call, and the one retry that backs off. The budget is per call, so a caller that retries too (a React Query hook, say) multiplies against it.
Note: this throwing contract is intentional and distinct from @sodax/sdk’s sodax.api.swaps, which wraps these calls and returns Result<T> instead of throwing.

Response schemas

The valibot schemas the client validates with are exported too, so a caller that already holds a parsed response — or a sibling API whose wire shapes are identical — can reuse them instead of re-declaring the contract. @sodax/sdk’s Leverage Yield client is exactly that case: a leverage-yield deposit/withdraw is an intent-based swap, so it reuses these for its intent-relay, gas, fee, and submit-tx responses.
  • Intent lifecycle: makeCreateIntentResponseSchema, makeCancelIntentResponseSchema, SubmitIntentResponseSchema, StatusResponseSchema, IntentHashResponseSchema, IntentPacketResponseSchema, IntentResponseSchema, RelayExtraDataResponseSchema, IntentStateSchema
  • Quote · deadline · allowance · approve: makeQuoteResponseSchema, DeadlineResponseSchema, AllowanceCheckResponseSchema, makeApproveResponseSchema
  • Gas · fees: GasEstimateResponseSchema, FeeResponseSchema
  • Submit-tx state machine: SubmitTxResponseSchema, SubmitTxStatusResponseSchema
  • Raw transactions: rawTxSchemaForChainKey(chainKey) — the per-chain-family tx schema the make*ResponseSchema factories above take, which also transforms the unsigned tx back to its domain shape (decimal-string → bigint, Injective index-object bytes → Uint8Array)
These are the response shapes only — request bodies are typed, not schema-validated.

Reference app

apps/swap-api-example drives this client end to end against a live Swaps API host.