Skip to main content

The model

The delegation is the existing gTrade one-click-trading primitive, setTradingDelegate(agent): the Diamond only lets the trader’s delegate call delegatedTradingAction, so the trader names the agent as delegate, once per chain. A trader who signs with its own wallet (the agent address is the trader address) needs no delegation at all. The delegation is all-or-nothing on-chain; the API adds an off-chain scope per agent, checked when a transaction is prepared. It is a guardrail, not a barrier: an agent that builds its own calldata is bound only by the chain.

Authorising an agent

POST /v1/{chain}/agents with a body signed by the master wallet (EIP-712 AgentAuthorization, see the SDK’s agentAuthorizationTypedData). The response contains the agent record and delegationTx, the setTradingDelegate(agent) transaction the master wallet must send to the Diamond (null when the agent is the trader itself).
GET /v1/{chain}/agents/{address} reports the status:

Scope

Scope violations answer 403 with SCOPE_MARKET_NOT_ALLOWED, SCOPE_LEVERAGE_EXCEEDED, SCOPE_SPEND_CAP_EXCEEDED or SCOPE_READ_ONLY, before any transaction is sent.

Revoking

DELETE /v1/{chain}/agents/{address}, signed by the master wallet (EIP-712 AgentRevocation), disables the agent in the API immediately and returns the removeTradingDelegate transaction. Send it to close the on-chain delegation too.

Signing a request

Every private request carries four headers: The signed struct binds the request, so a captured signature cannot be replayed on another path, body, chain or deployment:
  • verifyingContract is the Gains Diamond of the chain. GET /v1/{chain} publishes it together with the chain id, the field list and the empty-body hash, so nothing needs to be hard-coded; the SDK ships the public values as CHAINS and reads them from that endpoint whenever it is pointed at another baseUrl (a staging or preview deployment signs for its own Diamond).
  • primaryType is the operation id from the OpenAPI document (createOrder, cancelOrder, listPositions, …). The fields are the same for every operation; a wallet prompt shows the operation name.
  • path is the path with the query string, exactly as sent, starting with /v1.
  • bodyHash is keccak256 of the exact bytes of the request body. For a request without a body (GET) it is keccak256("") = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470. Send the body bytes you hashed; the server hashes what it receives and never re-serialises it.
The WebSocket handshake signs the same struct with primaryType: "wsAuth", method: "WS", path: "/v1/{chain}/ws" and the empty-body hash. Signature failures answer 401 with INVALID_SIGNATURE, TIMESTAMP_OUT_OF_WINDOW or NONCE_REUSED.

Read-only API keys

For dashboards and monitoring you can read private data without signing. POST /v1/{chain}/api-keys, signed once by the master wallet (EIP-712 ApiKeyIssue), returns a key that is shown once and stored hashed. Send it as X-Gains-Api-Key on GET /v1/{chain}/account/... and GET /v1/{chain}/orders/{id}, or as {"op":"auth","apiKey":"..."} on the WebSocket. An API key can never place an order.

What the API does not do

  • Hold keys: the agent key lives with you, the master key never leaves the trader.
  • Move funds: delegatedTradingAction cannot transfer collateral to the delegate; withdrawn PnL goes to the trader. The agent holds ETH for gas and nothing else.
  • Hide the delegation: an agent is usable only after the trader’s own setTradingDelegate(agent) transaction. This is the one on-chain setup step of the integration and it is intentional. removeTradingDelegate from the trader cuts the agent, whatever the API says.