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

# Trading agent

> chance-trader-dimes — a local Docker agent that trades Dimes leveraged prediction markets under your mandate-verified gate, with live monitoring in the dashboard.

[chance-trader-dimes](https://github.com/chance-markets/chance-trader-dimes) is a dockerized LLM trading agent for [Dimes Multiply](/venues/dimes) — leveraged prediction markets on Polygon. It runs **on your own machine**, holds **your own wallet key**, and is wired to Chance from the first tick: every order it wants to place is submitted to [`POST /api/v1/intent`](/api-reference/introduction) with the `dimes` venue adapter and only executes on an `ALLOW`. A `BLOCK` never trades.

<Note>
  The trader authenticates with a `chance_sk_…` API key, so all of its activity — verifications, verdicts, receipts — lands in the same account and Activity feed as everything else you run through the harness.
</Note>

## Deploy it

The dashboard's **Trader** tab is the deploy surface — it generates a ready-to-paste `.env` (including minting the API key inline) and then monitors the running container.

<Steps>
  <Step title="Clone the repo">
    ```bash theme={null}
    git clone https://github.com/chance-markets/chance-trader-dimes
    cd chance-trader-dimes
    ```
  </Step>

  <Step title="Generate your .env">
    Open **Dashboard → Trader**, choose **Dimes** from the venue gallery to open its wizard, then mint an API key, pick a name, Dimes environment (`sandbox` or `production`), agent model, tick interval and dry-run mode, and copy the generated block into `.env`. Fill in the four placeholders yourself: `DIMES_API_KEY`, `OPENROUTER_API_KEY`, `WALLET_PRIVATE_KEY`, `POLYGON_RPC_URL`.
  </Step>

  <Step title="Set the strategy">
    Edit `config/strategy.md` (how the agent trades), `config/mandate.md` (the rules the Chance judge enforces on every order) and `config/limits.json` (hard caps).
  </Step>

  <Step title="Run">
    ```bash theme={null}
    docker compose up -d
    ```

    The container appears in the Trader tab automatically once its first heartbeat lands. Start with `DRY_RUN=true` — the full loop runs, including verification, but nothing is submitted onchain.
  </Step>
</Steps>

## Heartbeats

The container reports its liveness and headline stats to the harness every tick. Heartbeats are **free** — they never charge a credit; the paid call is `/api/v1/intent`, which every actual trade still goes through.

### Send a heartbeat

```bash theme={null}
curl -X POST https://harness.chance.cc/api/v1/trader/heartbeat \
  -H "x-api-key: chance_sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "instanceId": "7f9c1e0a-3b2d-4c5e-8f6a-1b2c3d4e5f60",
    "name": "my-trader",
    "version": "0.3.1",
    "status": "running",
    "dryRun": true,
    "venue": "dimes",
    "env": "sandbox",
    "walletAddress": "0x397EE1E74FCBE42b76A39B42521E9ef04E16dd83",
    "startedAt": "2026-07-09T08:00:00.000Z",
    "lastTickAt": "2026-07-09T09:55:00.000Z",
    "openPositions": 2,
    "totalExposureUsd": 412.5,
    "dailyRealizedPnlUsd": -3.25,
    "creditsRemaining": 41,
    "note": "watching 3 markets"
  }'
```

```json theme={null}
{ "ok": true }
```

Authentication is the same as every `/api/v1` route — `x-api-key: chance_sk_…` or `Authorization: Bearer`. The server upserts one row per `(account, instanceId)` and stamps `lastSeenAt` itself; an instance is shown **online** while its last heartbeat is under 2 minutes old.

| Field                 | Type           | Notes                                                                   |
| --------------------- | -------------- | ----------------------------------------------------------------------- |
| `instanceId`          | uuid           | Stable per container install                                            |
| `name`                | string         | 1–64 chars, operator-chosen label                                       |
| `version`             | string         | Trader package version                                                  |
| `status`              | enum           | `starting` · `running` · `paused_out_of_credits` · `stopping` · `error` |
| `dryRun`              | boolean        | `true` = nothing is submitted onchain                                   |
| `venue`               | string         | which venue this trader trades, e.g. `dimes`                            |
| `env`                 | enum           | `sandbox` or `production`                                               |
| `walletAddress`       | string         | The trader's Polygon wallet (`0x…`)                                     |
| `startedAt`           | string         | ISO 8601                                                                |
| `lastTickAt`          | string \| null | ISO 8601                                                                |
| `openPositions`       | int ≥ 0        |                                                                         |
| `totalExposureUsd`    | number         |                                                                         |
| `dailyRealizedPnlUsd` | number         |                                                                         |
| `creditsRemaining`    | number \| null | Harness credits, as the trader last saw them                            |
| `note`                | string         | Optional, ≤ 280 chars — e.g. the last error line                        |

Returns `401` on a bad key and `400` (with the first validation issue) on an invalid body.

### List instances

```bash theme={null}
curl https://harness.chance.cc/api/v1/trader/instances \
  -H "x-api-key: chance_sk_live_…"
```

```json theme={null}
{
  "data": [
    {
      "instanceId": "7f9c1e0a-3b2d-4c5e-8f6a-1b2c3d4e5f60",
      "name": "my-trader",
      "version": "0.3.1",
      "status": "running",
      "dryRun": true,
      "venue": "dimes",
      "env": "sandbox",
      "walletAddress": "0x397EE1E74FCBE42b76A39B42521E9ef04E16dd83",
      "startedAt": "2026-07-09T08:00:00.000Z",
      "lastTickAt": "2026-07-09T09:55:00.000Z",
      "lastSeenAt": "2026-07-09T09:55:01.412Z",
      "openPositions": 2,
      "totalExposureUsd": 412.5,
      "dailyRealizedPnlUsd": -3.25,
      "creditsRemaining": 41,
      "note": "watching 3 markets"
    }
  ]
}
```

## Sponsored gas (EIP-7702)

The trader holds a plain EOA key and never needs POL for gas. When an order is
`ALLOW`ed, the harness sponsors the transaction on Polygon through Alchemy's Gas
Manager and returns it as an [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702)
user operation — so the trader EOA stays `msg.sender` (Dimes binds position
ownership to the quote's `wallet_address`). The trader only ever signs hashes.

<Note>
  The Alchemy API key and the gas policy id **stay server-side**. A Dimes signed
  quote lives only \~15s, so you verify the **draft** and then **promote** a fresh,
  signed quote at execution time and **submit those calls** to `/gas/prepare`. The
  harness **binds** the promoted calls to the ALLOWed draft's **policy terms** — the
  `positionSeed`, `signature` and `signatureExpiry` are Dimes-controlled passthrough
  and are ignored by the match — and builds the user operation from the **submitted**
  calls (the draft's stored calls are unsigned and unexecutable). One `ALLOW` can be
  sponsored **at most once**.
</Note>

Sponsorship is bound to a verdict: `/api/v1/gas/prepare` refuses any scan that is
not `ALLOW`, whose action is not an `eip155:137` `evm_calls` batch, whose `from` is
not one of your registered trader-instance wallets, or whose submitted calls don't
**bind-match** the ALLOWed draft. All three endpoints are **free** — the
`/api/v1/intent` verify already charged.

**Bind rule.** The submitted calls must have the same count, order and per-call
`to` as the judged draft, and per call: a `createPosition` matches on **policy
terms only** (`marketId`, `tokenId`, `collateralUsdcUnits`, `leverageBps`,
`notionalUsdcUnits`, and the three fee fields — everything except `positionSeed`,
`signature`, `signatureExpiry`) and its submitted `signature` must be non-empty
with a `signatureExpiry` in the future; an ERC-20 `approve` (token `to` + spender +
amount) and any other vault call (`requestClose`, …) must be **byte-identical**. If
the market moved and the policy terms differ, the harness returns `403
bind_mismatch` — re-verify; it never sponsors un-judged economics.

### 1. Prepare

Submit the `scanId` of the `ALLOW`ed draft **and the promoted calls** (the fresh,
signed approve + `createPosition`). The harness bind-matches them to the draft,
builds the sponsored user operation from the submitted calls, and returns only the
hashes to sign, tagged with their order (`i`).

```bash theme={null}
curl -X POST https://harness.chance.cc/api/v1/gas/prepare \
  -H "x-api-key: chance_sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
        "scanId": "11111111-1111-4111-8111-111111111111",
        "calls": [
          { "to": "0xc011a7e12a19f7b1f670d46f03b03f3342e82dfb", "data": "0x095ea7b3…" },
          { "to": "0xecf933ccdf7ebc6a0658c77e070cfe51ebe5328a", "data": "0xe539dd68…" }
        ]
      }'
```

```json theme={null}
{
  "signatureRequests": [
    {
      "i": 0,
      "type": "personal_sign",
      "rawPayload": "0x2a8073568c8fad3edf15f70c3471a7fff18e1d57c650a501ab47f57a5c1f0e39",
      "data": { "raw": "0xc69e468aa6fafb5c7298c02841e76f976b433018266af39a5807bc29ea9ad392" }
    }
  ]
}
```

A never-before-sponsored (undelegated) EOA gets **two** requests instead — the
EIP-7702 `authorization` first (`type: "eip7702Auth"`), then the user operation.
The trader signs each `rawPayload` directly (`account.sign({ hash: rawPayload })`)
and returns one signature per request, **in the same order**. It decides the
count purely from how many requests came back.

### 2. Submit

Return the signatures, aligned by index. The harness reattaches them to its
**stored** prepared operation (built from the calls you submitted at prepare) and
broadcasts through Alchemy.

```bash theme={null}
curl -X POST https://harness.chance.cc/api/v1/gas/submit \
  -H "x-api-key: chance_sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "scanId": "11111111-1111-4111-8111-111111111111", "signatures": ["0x…"] }'
```

```json theme={null}
{ "callIds": ["0x0000…066eec69e468…ad392"] }
```

### 3. Status

Poll the call bundle until it confirms (`scanId` or `callId`, both scoped to you).

```bash theme={null}
curl "https://harness.chance.cc/api/v1/gas/status?scanId=11111111-1111-4111-8111-111111111111" \
  -H "x-api-key: chance_sk_live_…"
```

```json theme={null}
{ "status": 200, "txHash": "0x8ec2…f517", "confirmed": true }
```

`status` is the [EIP-5792](https://eips.ethereum.org/EIPS/eip-5792) code (`100`
pending, `200` confirmed, `4xx/5xx` failure); `confirmed` is `true` only at `200`,
and `txHash` appears once the operation is mined.

| Endpoint            | Errors                                                                                                                                                                                                                                                                                                                                                       |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `POST /gas/prepare` | `401` auth · `400` missing/empty `calls` or action not an `eip155:137` `evm_calls` batch · `403` verdict not `ALLOW` / `from` not a registered instance wallet / `bind_mismatch` (submitted calls don't match the draft's policy terms, or the promoted `createPosition` is unsigned/expired) / already sponsored · `409` gas not configured · `502` Alchemy |
| `POST /gas/submit`  | `401` auth · `400` signature count mismatch · `403` not prepared / already sponsored · `502` Alchemy                                                                                                                                                                                                                                                         |
| `GET /gas/status`   | `401` auth · `400` no `scanId`/`callId` · `403` not yours · `502` Alchemy                                                                                                                                                                                                                                                                                    |

## Monitoring

The Trader tab polls your instances and shows, per container: online/offline (heartbeat freshness), status — with `paused_out_of_credits` and `error` highlighted — **DRY RUN** vs **LIVE**, sandbox vs production, the trading wallet, open positions, total exposure, signed daily realized PnL, credits remaining, and the trader's last note line. Trades themselves show up in [Activity](https://harness.chance.cc/activity) like every other verified action, each with its provable receipt.
