- Implements the
IBridgeApiV2contract from@sodax/typesoverfetch. - Validates every response at runtime with valibot, and
transforms each chain-specific unsigned
txback 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) andvalibot.
@sodax/sdk’s BridgeApiService
(sodax.api.bridge) is a thin adapter over this package, adding the SDK’s
Result<T> contract, logger, and config resolution on top. Use @sodax/bridge-api
directly when you want just the bridge backend without pulling in the full SDK.
Install
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 (see below).
Bridge deltas vs the swaps client worth knowing:
createBridgeIntentreturns{ tx, relayData }— there is nointentstruct (the bridge is vault-backed, not solver-based).submitTxmust carry the fullrelayData { address, payload }envelope received fromcreateBridgeIntent, not just the payload.getSubmitTxStatusstatusis a tolerantstring; compare against the known terminal literals ('executed'/'failed').getFee/getBridgeableAmount/isBridgeableare read-only quotes SDK consumers can get from the localsodax.bridge.*equivalents instead:getFeeandisBridgeablecompute locally, andgetBridgeableAmountreads the hub vaults over RPC rather than calling the backend. These HTTP mirrors exist for non-SDK clients.
API key
The backend guards the/bridge/* 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:
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.
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.
Errors
Every method throws aBridgeApiError 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 getFee) retry transient HTTP / network failures up to
3 attempts in total — the first try plus two retries, back to back with no delay;
mutations are not, with one exception — the apiguard 503 above, which is replay-safe
and the one retry that backs off. A timeout is never retried: it bounds the whole
call, retries included. 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’ssodax.api.bridge, which wraps these calls and returnsResult<T>instead of throwing.