> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gains.trade/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> From zero to a filled test order on Arbitrum Sepolia, with the TypeScript SDK or plain HTTP.

You need a wallet with testnet USDC on Arbitrum Sepolia (the trader, called the **master wallet**), a little testnet ETH for the agent key (it pays the gas of its own transactions) and Node 20 or newer. Ask for testnet USDC in the Discord `#testnet` channel; the trading contracts on Sepolia are the same as mainnet.

<Steps>
  <Step title="Install the SDK">
    ```bash theme={null}
    npm install @gainstrade/api viem
    ```

    The SDK is generated from the API's OpenAPI contract; every request and response is typed. If you prefer another language, skip to [plain HTTP](#the-same-flow-in-plain-http) or generate a client from the spec ([SDKs](/developer/trading-api/sdks)).
  </Step>

  <Step title="Create an agent key">
    The agent is a fresh EVM key that signs API requests and broadcasts the transactions the API prepares, so it needs a little ETH for gas. It never holds collateral. Keep it in an environment variable or a secret manager, never in source.

    ```ts theme={null}
    import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";

    const agentKey = generatePrivateKey();
    console.log("GAINS_AGENT_KEY=" + agentKey);
    const agent = privateKeyToAccount(agentKey);
    console.log("agent address", agent.address);
    ```
  </Step>

  <Step title="Authorise the agent with the master wallet">
    Two things happen: the API records the agent and its scope (signed by the master wallet), and the master wallet sends one on-chain transaction, `setTradingDelegate(agent)`, which names the agent as the trader's delegate. The API returns that transaction ready to send. The client is built with a `sender`: the agent's wallet client, which will sign and broadcast every prepared transaction.

    ```ts theme={null}
    import { GainsClient, viemSender } from "@gainstrade/api";
    import { createWalletClient, http } from "viem";
    import { privateKeyToAccount } from "viem/accounts";
    import { arbitrumSepolia } from "viem/chains";

    const master = privateKeyToAccount(process.env.MASTER_KEY as `0x${string}`);
    const agentWallet = createWalletClient({ account: agent, chain: arbitrumSepolia, transport: http() });
    const client = new GainsClient({ chain: "arbitrum-sepolia", agent, sender: viemSender(agentWallet) });

    const { agent: registered, delegationTx } = await client.agents.authorize(master, {
      agent: agent.address,
      name: "quickstart-bot",
      scope: {
        markets: ["BTC/USD", "ETH/USD"],
        maxLeverage: "20",
        dailySpendCapUsd: "1000",
        expiresAt: Date.now() + 30 * 24 * 3600 * 1000,
        permissions: "trade",
      },
    });
    console.log(registered.status); // "pending_delegation"

    if (delegationTx !== null) {
      const wallet = createWalletClient({ account: master, chain: arbitrumSepolia, transport: http() });
      const hash = await wallet.sendTransaction({ to: delegationTx.to, data: delegationTx.data });
      console.log("delegation tx", hash);
    }
    ```

    Once the transaction is mined, `client.agents.get(agent.address)` reports `status: "active"`. The delegation is per trader and per chain, and `removeTradingDelegate` cuts it. A trader who wants to sign with its own wallet registers itself as agent: no delegation, `delegationTx` is null.
  </Step>

  <Step title="Approve USDC once">
    The trader's collateral stays in the trader's wallet. The Diamond pulls it when an order opens, so the master wallet must approve the Diamond for USDC once (any amount, `maxUint256` is common). `GET /v1/{chain}` returns the Diamond address (`diamond`) and `GET /v1/{chain}/markets` the USDC token address (`collaterals[].address`); both are also in the [contracts reference](/developer/integrators/trading-contracts).
  </Step>

  <Step title="Place a market order and wait for the fill">
    ```ts theme={null}
    const btc = await client.markets.get("BTC/USD");
    console.log(btc.pricePrecision, btc.sizePrecision, btc.minPositionUsd);

    const order = await client.orders.create({
      market: "BTC/USD",
      side: "long",
      type: "market",
      size: "0.001",
      leverage: "10",
      collateral: "USDC",
      slippage: "1",
      reduceOnly: false,
      clientId: crypto.randomUUID(),
    });
    console.log(order.id, order.status); // "5316911983139663" "pending"

    const filled = await order.waitForFill({ timeoutMs: 90_000 });
    console.log(filled.fill?.price, filled.fill?.size, filled.fill?.positionId);
    ```

    `size` is in base asset (here BTC). The API derives the USDC collateral from `size × price / leverage`, simulates the order from the agent and returns the transaction; `orders.create` signs and broadcasts it with the `sender`, reports the hash (`POST /orders/{id}/submit`) and returns the `pending` order. `waitForFill` polls `GET /orders/{id}` and throws `OrderNotFilledError` on `rejected`, `timed_out`, `canceled` or `expired`, so an unfilled order never looks like a filled one.
  </Step>

  <Step title="Read the position, then close it">
    ```ts theme={null}
    const positions = await client.account.positions(master.address);
    const position = positions.find((p) => p.market === "BTC/USD")!;
    console.log(position.unrealizedPnl, position.liquidationPrice);

    const close = await client.positions.close(position.id, { slippage: "1" });
    await close.waitForFill();
    ```
  </Step>
</Steps>

## The same flow in plain HTTP

Every private request carries four headers. The signature is EIP-712 over the request itself; see [Authentication](/developer/trading-api/authentication) for the exact struct.

```
X-Gains-Agent:     0xAgentAddress
X-Gains-Timestamp: 1700000000000        (ms, within 30 s of server time)
X-Gains-Nonce:     1700000000000001     (unique per agent inside the replay window)
X-Gains-Signature: 0x...                (65-byte EIP-712 signature)
```

```bash theme={null}
# signing domain of the chain (chain id, Diamond, field list)
curl https://api-testnet.gains.trade/v1/arbitrum-sepolia

# public: no headers
curl https://api-testnet.gains.trade/v1/arbitrum-sepolia/markets/BTC-USD

# private write
curl -X POST https://api-testnet.gains.trade/v1/arbitrum-sepolia/orders \
  -H 'content-type: application/json' \
  -H 'X-Gains-Agent: 0x...' -H 'X-Gains-Timestamp: ...' -H 'X-Gains-Nonce: ...' -H 'X-Gains-Signature: 0x...' \
  -d '{"market":"BTC/USD","side":"long","type":"market","size":"0.001","leverage":"10","collateral":"USDC","slippage":"1","reduceOnly":false}'
```

Market symbols in paths are written `BTC-USD` (or URL-encoded `BTC%2FUSD`); in JSON bodies they are `BTC/USD`.

The write above answers `201` with `{ order, transaction }`. Sign and broadcast `transaction` with the agent key (`eth_sendTransaction` with `to`, `data`, `value: "0"`), then report the hash:

```bash theme={null}
curl -X POST https://api-testnet.gains.trade/v1/arbitrum-sepolia/orders/5316911983139663/submit \
  -H 'content-type: application/json' -H 'X-Gains-Agent: 0x...' -H 'X-Gains-Timestamp: ...' -H 'X-Gains-Nonce: ...' -H 'X-Gains-Signature: 0x...' \
  -d '{"txHash":"0x..."}'
```

## Next

* [Authentication](/developer/trading-api/authentication): scopes, revocation, read-only keys, the signature spec.
* [Orders and positions](/developer/trading-api/orders): limit and stop orders, partial closes, SL/TP, leverage changes.
* [WebSocket](/developer/trading-api/websocket): stream prices, the book and your order updates instead of polling.
