WakeTrades

API for AI Agents

Sign In With a Wallet

Sign in to the WakeTrades API with a message signed by the agent's wallet (EIP-191). The first sign-in creates the account. No API key, email or password.

The agent proves it controls a wallet by signing a short text message. The first sign-in of a wallet creates its WakeTrades account.

Steps

  1. POST /api/v1/session/nonce with { "address": "0x…" }. The answer holds a nonce (valid 10 minutes) and the exact message to sign.
  2. Sign message as is with the wallet (EIP-191 personal_sign).
  3. POST /api/v1/session/verify with { "address", "nonce", "message", "signature" }. The answer holds a session token (on the site that issued it only) and the account. From a script or agent (no Origin header), the token is valid until your plan or free trial ends, 6 months at most, and 7 days before the trial starts; paying extends it without a new sign-in. Signed in on the website, it lasts 7 days. Tokens issued before 13 October 2026 no longer work: sign in again once.
  4. Send the token on every other request: Authorization: Bearer <token>.

Helping a person who keeps their wallet? Don't sign in: they connect you from Profile › AI Agent on waketrades.com and give you a code. Use it as the token (Authorization: Bearer <code>). It works until their plan or free trial ends, 180 days at most, and stops at once if they disconnect you or their plan stops (401 agent_code_expired: ask them for a new code). A View only code can't change anything: 403 view_only (Connect Your AI Agent).

POST /api/v1/session/revoke-all signs out every session of the wallet (browsers, agents, links opened from the Telegram bot), the one that calls it included. Use it if a token may have leaked; copying is not affected. On the website: Profile › Sign out of all sessions.

The message only signs you in: it places no order and grants no permission. It follows the Sign-In with Ethereum standard (EIP-4361), so wallets check that the site asking for it is waketrades.com. Sign it exactly as returned, without changing a character. It looks like this:

waketrades.com wants you to sign in with your Ethereum account:
0x1234…

Sign-in only. No order will be placed and no permission is granted.

URI: https://waketrades.com
Version: 1
Chain ID: 42161
Nonce: …
Issued At: 2026-10-10T12:00:00.000Z
Expiration Time: 2026-10-10T12:10:00.000Z

Python

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

BASE = "https://waketrades.com/api/v1"
acct = Account.from_key(PRIVATE_KEY)

n = requests.post(f"{BASE}/session/nonce", json={"address": acct.address}).json()
sig = "0x" + bytes(acct.sign_message(encode_defunct(text=n["message"])).signature).hex()
s = requests.post(f"{BASE}/session/verify", json={"address": acct.address, "nonce": n["nonce"], "message": n["message"], "signature": sig}).json()
headers = {"Authorization": f"Bearer {s['token']}"}

JavaScript (viem)

import { privateKeyToAccount } from "viem/accounts";

const BASE = "https://waketrades.com/api/v1";
const account = privateKeyToAccount(process.env.PRIVATE_KEY);
const post = (path, body, token) => fetch(BASE + path, { method: "POST", headers: { "content-type": "application/json", ...(token ? { authorization: `Bearer ${token}` } : {}) }, body: JSON.stringify(body) }).then((r) => r.json());

const { nonce, message } = await post("/session/nonce", { address: account.address });
const signature = await account.signMessage({ message });
const { token } = await post("/session/verify", { address: account.address, nonce, message, signature });

JavaScript Without a Wallet Library

No viem, ethers or eth-account at hand? Two small audited libraries are enough for every signature WakeTrades asks for: the sign-in message (EIP-191 personal_sign) and Hyperliquid's typed data (EIP-712) for the trading key and payments. Hyperliquid's typed data only uses flat string, address and uint fields, which keeps the encoder short. Tested with these exact versions (later major versions changed sign):

// npm i @noble/curves@1.9.7 @noble/hashes@1.8.0 — save as sign.mjs
import { secp256k1 } from "@noble/curves/secp256k1.js";
import { keccak_256 } from "@noble/hashes/sha3.js";
import { bytesToHex, hexToBytes, utf8ToBytes, concatBytes } from "@noble/hashes/utils.js";

const KEY = hexToBytes(process.env.PRIVATE_KEY.replace(/^0x/, ""));
export const address = "0x" + bytesToHex(keccak_256(secp256k1.getPublicKey(KEY, false).slice(1)).slice(-20));

// { r, s, v }: r and s with 64 hex digits each, v = 27 or 28
function sign(hash) {
  const sig = secp256k1.sign(hash, KEY);   // deterministic (RFC 6979), low s
  const hex = (n) => "0x" + n.toString(16).padStart(64, "0");
  return { r: hex(sig.r), s: hex(sig.s), v: 27 + sig.recovery };
}

// EIP-191 personal_sign, for POST /session/verify: a 65-byte hex string
export function personalSign(message) {
  const m = utf8ToBytes(message);
  const { r, s, v } = sign(keccak_256(concatBytes(utf8ToBytes(`\x19Ethereum Signed Message:\n${m.length}`), m)));
  return r + s.slice(2) + v.toString(16);
}

// EIP-712 with flat types, for POST /agent/approve and POST /pay: { r, s, v }
const encode = (type, value) =>
  type === "string" ? keccak_256(utf8ToBytes(value))
  : type === "address" ? hexToBytes(value.slice(2).toLowerCase().padStart(64, "0"))
  : hexToBytes(BigInt(value).toString(16).padStart(64, "0"));   // uint64, uint256
const hashStruct = (name, fields, data) => keccak_256(concatBytes(
  keccak_256(utf8ToBytes(`${name}(${fields.map((f) => `${f.type} ${f.name}`).join(",")})`)),
  ...fields.map((f) => encode(f.type, data[f.name]))));
const DOMAIN = [{ name: "name", type: "string" }, { name: "version", type: "string" },
  { name: "chainId", type: "uint256" }, { name: "verifyingContract", type: "address" }];
export function signTypedData({ domain, types, primaryType, message }) {
  return sign(keccak_256(concatBytes(new Uint8Array([0x19, 0x01]),
    hashStruct("EIP712Domain", DOMAIN, domain), hashStruct(primaryType, types[primaryType], message))));
}

Use it like this:

import { address, personalSign, signTypedData } from "./sign.mjs";
// Sign-in: { address, nonce, message, signature: personalSign(message) }
// Trading key (Set Up Copying, step 3): { action, signature: signTypedData(typedData) }
// Payment (GET /pay/prepare returns action and typedData): { plan, action, signature: signTypedData(typedData) }

Our test suite runs this exact code against the server's own signature checks on every release.

Errors

Code Meaning
invalid_address The address isn't a valid Ethereum address.
invalid_nonce The nonce is wrong, older than 10 minutes or already used (one sign-in per signature). Ask for a new one.
invalid_message The signed text isn't the one returned with the nonce.
wrong_signer The signature comes from another wallet.
origin_not_allowed A browser page on another website tried to finish the sign-in. Sign in from waketrades.com, or from a script or agent.
signup_limit Too many new accounts from this IP address today (3 per day).
session_required Missing or expired token on a protected route. Sign in again.
agent_code_expired The code from Profile › AI Agent has ended (180 days, plan end, or plan stopped). Ask the person for a new one.

Last updated October 28, 2026