Skip to main content
Ondo uses a session-plus-API-key scheme. The user signs in once with their wallet, and from then on trading executes without a per-trade wallet popup. Three signing methods appear across Ondo’s actions:
  • siwe — the one-time Sign-In-With-Ethereum login. The wallet personal_signs a backend-issued ERC-4361 challenge.
  • session — client-only setup steps the SDK runs directly against the venue with the session credential. No signed step returns to the backend.
  • hmac — every trading action. The SDK signs each venue request with the API key and the backend relays the signed request to the venue.

The SIWE session

The user’s EVM-compatible signer — provided to the SDK via userWallet (createPerpsClient) or setUserWallet() — signs one thing on Ondo: the SIWE login challenge. createAction for siweLogin returns a SiweActionStep carrying the challenge ({ challengeId, message }). The wallet personal_signs the message, and the resulting SiweSignedActionStep ({ action, siwe, signature }) is submitted via executeAction. In exchange the venue issues a session credential the SDK holds client-side — it is never shared with LI.FI.

The API key

The API key is a venue trading key the SDK creates during setup (the registerApiKey step) using the session credential. It is stored client-side and is used to sign every trading request thereafter. What the API key does:
  • Signs every trading action — placeOrder, placeTriggerOrder, cancelOrder, cancelAllOrders, placeTwapOrder, cancelTwapOrder, and updateLeverage.
What the API key does not do:
  • It does not sign the SIWE login — that requires the user’s wallet.
  • It is not transmitted to the LI.FI backend. Only the per-request HMAC material ({ keyId, timestampMs, signature }) crosses the wire; the backend builds the venue’s transport headers from it at relay time.
Lifecycle:
  • The SDK creates the key lazily on first HMAC use, or eagerly when the user runs the registerApiKey setup step — both paths share the same creation logic, so whichever runs first satisfies the other.
  • The venue registers it under the name lifi-perps with scopes ['trade'].
  • Ondo caps an account at 10 API keys. When the account is already at the cap, the SDK reclaims the oldest key it owns (matched by name lifi-perps) before creating a new one — oldest by createdAt, ties broken by the smaller keyId. If no lifi-perps-named key is available to reclaim, key creation fails.
  • If the venue rejects an HMAC-signed request with Unauthorized (the key was revoked or expired venue-side), the SDK evicts its local copy so the next trading action re-creates one automatically.

The HMAC-signed trading relay

Trading actions are HMAC-relayed. createAction returns one or more HmacActionSteps, each an unsigned venue request:
The SDK computes a per-request HMAC signature over the step from the API key and attaches it as a structured hmac field, yielding a HmacSignedActionStep:
request.body is a pre-serialized string that transits verbatim — it is the exact byte string the HMAC signature covers, and it is never re-serialized downstream. Signature and relay: the SDK computes HMAC-SHA256(apiSecret, ${timestampMs}${METHOD}${pathWithQuery}${body}), hex-encoded, where METHOD is upper-cased and the fields are concatenated with no separators. timestampMs is stamped immediately before executeAction; Ondo enforces a 30-second signing window between that timestamp and the venue receiving the request. The backend relays the signed request to the venue as ONDO-KEY-ID, ONDO-TIMESTAMP, and ONDO-SIGN transport headers and returns the venue’s result. There is no retry on failure — a re-sent request would carry the same frozen timestamp and fail the venue’s window check.

Client-only session steps

createDepositAddress, acceptProviderTerms, registerApiKey, and setReferrer are session steps. The SDK performs the venue call itself with the client-held session credential and skips executeAction, but the three fall into two different shapes:
  • acceptProviderTerms and registerApiKey are bare markers — the backend step carries no request material at all. The SDK authors the venue call itself: acceptProviderTerms posts { termsVersion, privacyVersion } (both currently 1) to POST /v1/agreement; registerApiKey runs the same key-creation logic described above.
  • createDepositAddress carries a fixed, backend-authored deposit policy ({ network: 'ethereum', symbol: 'USDC', depositDestination: { wallet: 'margin' } }) but no venue path or credentials. The SDK reads the account (GET /v1/account) to get the venue accountID, provisions the address (POST /v1/provision_address) with the policy and that id, then reads the result back (POST /v1/wallet/deposit_address/list) to confirm the venue can serve it.
  • setReferrer is not a bare marker — the backend authors a real request (POST /v1/account/referral with { code }), and the SDK submits it as-is using the session credential rather than an HMAC signature. It runs as an internal setup step (not user-facing) once the account has no referrer attached.

The session credential’s lifecycle

The session credential has no refresh flow. The SDK evicts it in two cases and both fall back to a logged-out account state, which causes the next checkSetup() to re-stage siweLogin:
  • Client-side expiry — the token store checks expirationSecs * 1000 <= Date.now() on every read and drops an expired token before returning it.
  • Server-side revocation — a mid-call 401 from the venue surfaces as a session-expired error; the SDK evicts the stored token immediately rather than letting a locally-valid-looking token soft-lock the UI.

Who signs what