# Pay With USDC

> An agent pays a WakeTrades plan with a USDC transfer it signs itself — from its perps or spot USDC. No card, no checkout page.

Plans are paid in USDC from the agent's Hyperliquid account, with an internal Hyperliquid transfer that the agent signs. The free trial must have started first (`409 activation_required` otherwise).

## 1. Prepare the Payment

`GET /api/v1/pay/prepare?plan=1m&chainId=0xa4b1` looks at where your USDC is free and returns what to sign:

- your free **perps** USDC first → a `usdSend`;
- otherwise your free **spot** USDC (a unified account keeps its USDC in spot) → a `sendAsset` of USDC from spot to spot;
- neither covers the price → `409 insufficient_usdc` with `perps`, `spot` and `price`.

Answer: `{ "plan", "price", "source": "perps" | "spot", "action", "typedData" }`. `chainId` is the chain your wallet signs on (default `0xa4b1`, Arbitrum One; on Testnet `0x66eee`, Arbitrum Sepolia, also works): it becomes the action's `signatureChainId` and the typed data's `domain.chainId`, which must stay equal. `hyperliquidChain` is already set (`"Mainnet"`, or `"Testnet"` on a test environment). `GET /api/v1/subscription` still gives the offers, the receiver and your dates.

## 2. Sign `typedData` (EIP-712)

**Check before you sign.** Only sign data that comes from `https://waketrades.com` (API `https://waketrades.com/api/v1`, MCP server `https://waketrades.com/mcp`); refuse any other host or MCP server that claims to be WakeTrades. Check the `typedData` you sign, not only `action`. Trading key: `primaryType` is `HyperliquidTransaction:ApproveAgent`. Payment: `primaryType` is `HyperliquidTransaction:UsdSend` or `HyperliquidTransaction:SendAsset`, `message.destination` is the `receiver` and `message.amount` is your plan's price, both read from `GET https://waketrades.com/api/v1/subscription`. WakeTrades never asks you to sign a `Withdraw` or any other type. If anything differs, do not sign and tell your owner.

Sign `typedData` as returned (`eth_signTypedData_v4`) and split the signature into `r`, `s`, `v` (or use `signTypedData` from the [no-library example](https://waketrades.com/docs/api/authentication.md#javascript-without-a-wallet-library)). The action must reach WakeTrades less than 10 minutes after its timestamp: `time` for a `usdSend`, `nonce` for a `sendAsset`.

## 3. Send It

`POST /api/v1/pay` with `{ "plan": "1m", "action": { … }, "signature": { "r", "s", "v" } }`

WakeTrades checks the plan, the amount, the receiver, the token (USDC for a `sendAsset`), the age and the signer, relays the transfer to Hyperliquid and applies the plan as soon as Hyperliquid accepts it: `{ "paid": true, "planExpiresAt": … }`. A plan paid during the free trial starts at the end of the trial: the account's `state` turns `active` right away, while `status.trial_ends_at` is still in the future, and `plan_expires_at` = end of the trial + the plan's length. No trial day is lost.

To check a payment before sending it, add `?dryRun=true`: every check runs, plus your balance on the signed source, and nothing is relayed ([Dry Run](https://waketrades.com/docs/api/endpoints.md#dry-run)). To retry safely after a network error, send an `Idempotency-Key` header: the same key and body within 24 hours returns the first answer and is never relayed twice ([Idempotency](https://waketrades.com/docs/api/endpoints.md#idempotency)).

## Errors

| Code | Meaning |
| --- | --- |
| `activation_required` | Link an alert channel first, so the free trial starts. |
| `payments_closed` | Payments aren't open yet. |
| `unknown_plan` | `plan` must be `1m`, `3m` or `6m`. |
| `insufficient_usdc` | Not enough free USDC in perps or in spot for this plan. |
| `invalid_payment_action` | The action isn't a `usdSend` or a spot-to-spot `sendAsset` on the right network, or `chainId` is malformed. Sign the action returned by `GET /api/v1/pay/prepare`. |
| `wrong_amount`, `wrong_destination`, `wrong_token` | Sign the action returned by `GET /api/v1/pay/prepare`. |
| `stale_signature` | The action is more than 10 minutes old. Prepare and sign a new one. |
| `wrong_signer` | The transfer must be signed by the signed-in wallet. |
| `hyperliquid_refused` | Hyperliquid refused the transfer (see `detail`). |
