Cascade

Authentication

Authenticate with Cascade using wallets and JWTs

Authentication Flow

Cascade issues short‑lived JWTs after you prove wallet ownership. The JWT becomes your bearer token for both HTTP and WebSocket requests regardless of whether you are building a browser app, server, or script.

The /auth route is public; everything under /account/* requires a valid bearer token.

Key Concepts

  • Account: Your deposit address (not your wallet address). This is the address you use in all API calls and signed messages.
  • Delegate: A separate EOA that can sign requests on behalf of your account. Configure delegates in the trading UI.

Authenticate with the Server

No API key

Manual Authentication Steps

1. Request a challenge

curl "https://matcher.cascade.xyz/auth?account=<DEPOSIT_ADDRESS>"

The server responds with:

{
  "message": {
    "account": "0x037466fe85a8042f2da8f912b585b2278f8f153c",
    "nonce": "e1231189-12d6-4efc-867d-53a3075a1d8b",
    "timestamp": "2026-01-19T16:33:15.662842941+00:00"
  },
  "server_signature": "VJfLidgINtN1Q-EaTaR7U5nMy74...",
  "signing_payload": "{\"account\":\"0x037466fe85a8042f2da8f912b585b2278f8f153c\",\"nonce\":\"e1231189-12d6-4efc-867d-53a3075a1d8b\",\"timestamp\":\"2026-01-19T16:33:15.662842941+00:00\"}"
}
  • signing_payload is a JSON string that you sign with eth_sign (EIP-191 personal sign).
  • nonce expires after five minutes. You must post the signature before it expires.

2. Sign and POST the proof

Use your preferred signing stack (hardware wallet, headless account, Custody, etc.) to EIP‑191 sign the signing_payload, then POST the payload, signature, and server signature to /auth. If verification succeeds the response contains { token, claims }. Store the token somewhere secure (environment variable, encrypted store, Secrets Manager, etc.) and attach it to every protected request until it expires.

3. Use the JWT for REST calls

Authorization: Bearer <token>

All /account/* endpoints already enforce the middleware. When you receive an HTTP 401 refresh by re-running steps 1–2.

4. Authenticate WebSockets

After you open the Cascade WebSocket send:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "auth",
  "params": { "bearer": "<token>" }
}

Only authenticated connections may create/cancel orders, pull positions, etc. Re-run the flow when you receive an “Invalid Bearer Token” response.

Implementation tips

  • Cache the JWT and its expiry so you can refresh proactively and avoid gaps in coverage.
  • Share the token between HTTP and WebSocket clients so both stacks stay in sync.
  • Treat bearer tokens like credentials: encrypt them at rest and wipe them on logout or shutdown.

Examples

JavaScript (viem)

import { createWalletClient, custom } from "viem";

const ethereumProvider = /* browser wallet, WalletConnect, embedded signer, etc. */;
const wallet = createWalletClient({
  transport: custom(ethereumProvider),
});

// Use your deposit address (from the trading UI), not your wallet address
const depositAddress = "0x...";
// Use your delegate address for signing
const [delegateAddress] = await wallet.getAddresses();

const challenge = await fetch(
  `https://matcher.cascade.xyz/auth?account=${depositAddress}`
).then((res) => res.json());

// signing_payload is a JSON string - sign it directly with eth_sign
const signature = await wallet.signMessage({
  account: delegateAddress,
  message: challenge.signing_payload,
});

const session = await fetch("https://matcher.cascade.xyz/auth", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    message: challenge.message,
    signature,
    server_signature: challenge.server_signature,
  }),
}).then((res) => res.json());

const token = session.token;

await fetch("https://matcher.cascade.xyz/account/orders", {
  headers: { Authorization: `Bearer ${token}` },
});

Python (web3.py)

import os
import requests
from eth_account import Account
from eth_account.messages import encode_defunct

MATCHER_HTTP_BASE_URL = "https://matcher.cascade.xyz"

# Use your deposit address (from the trading UI)
DEPOSIT_ADDRESS = os.environ["DEPOSIT_ADDRESS"]

# Use your delegate private key for signing
delegate = Account.from_key(os.environ["DELEGATE_PRIVATE_KEY"])

challenge = requests.get(
    f"{MATCHER_HTTP_BASE_URL}/auth",
    params={"account": DEPOSIT_ADDRESS},
    timeout=10,
).json()

# signing_payload is a JSON string - sign it with eth_sign (personal_sign)
message = encode_defunct(text=challenge["signing_payload"])
signature = Account.sign_message(message, private_key=delegate.key).signature.hex()

session = requests.post(
    f"{MATCHER_HTTP_BASE_URL}/auth",
    json={
        "message": challenge["message"],
        "signature": signature,
        "server_signature": challenge["server_signature"],
    },
    timeout=10,
).json()

token = session["token"]

requests.get(
    f"{MATCHER_HTTP_BASE_URL}/account/orders",
    headers={"Authorization": f"Bearer {token}"},
    timeout=10,
)