Skip to main content
chance-trader-dimes is a dockerized LLM trading agent for Dimes Multiply — 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 with the dimes venue adapter and only executes on an ALLOW. A BLOCK never trades.
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.

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.
1

Clone the repo

2

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.
3

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).
4

Run

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.

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

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. Returns 401 on a bad key and 400 (with the first validation issue) on an invalid body.

List instances

The trader holds a plain EOA key and never needs POL for gas. When an order is ALLOWed, the harness sponsors the transaction on Polygon through Alchemy’s Gas Manager and returns it as an 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.
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.
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 ALLOWed 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).
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.

3. Status

Poll the call bundle until it confirms (scanId or callId, both scoped to you).
status is the EIP-5792 code (100 pending, 200 confirmed, 4xx/5xx failure); confirmed is true only at 200, and txHash appears once the operation is mined.

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 like every other verified action, each with its provable receipt.