Polytraders Dev Guide
internal
Phase 1 · 9/109 wired

Non-custodial signing

The platform holds no user funds and no user keys. A bot trades because the user granted a narrowly scoped, revocable session key — not because we hold their wallet. This page specifies what that means concretely, because non-custodial is easy to claim and easy to quietly break.

The test. If the platform's entire infrastructure were compromised tomorrow, an attacker must not be able to move user funds anywhere the user had not already authorised. If any design fails that test, it is not non-custodial regardless of what the marketing page says.

Where keys live

MaterialLivesPlatform can read?Notes
Wallet private keyThe user's walletNeverWe never see, request, transmit, or store it.
Session key (private half)Platform HSM / KMS, per userSign onlyNon-exportable. Signs only whitelisted methods within granted scope.
Session key grantOn chainReadThe authority is the on-chain grant, not a database row.
Venue API credentialsPlatform secret storeYesRead-only market data. Cannot move funds.
Anything held by a user botNothing. A user bot receives no key material of any kind.

The session key is the only thing that can sign, it can only sign what the grant allows, and the grant is on chain where the user can inspect and revoke it without our cooperation. That last clause is what makes it real.

Scope of a grant

A grant is not "this bot may trade". It is a specific, bounded authority, and every bound is independently enforced at signing time even though the chain already checked it.

SessionKeyGrant {
  user:            0xUSER…
  sessionKey:      0xSESS…
  venue:           CTFExchangeV2 @ 0xEXCH…   // exact address, no wildcards
  methods:         [ postOrder, cancelOrder ] // whitelist, see below
  maxNotionalUsd:  5000                       // aggregate over grant lifetime
  maxPerOrderUsd:  500
  collateral:      pUSD @ 0xPUSD…             // exact token, no wildcards
  marketScope:     [ … ]                      // cluster or ≤50 explicit ids
  expiresAt:       now + 30d                  // grants always expire
  revocable:       true                       // always true; never offer otherwise
}

Method whitelist

MethodAllowedWhy
postOrderYesThe point of the grant.
cancelOrderYesRequired for the kill switch to mean anything.
transfer / transferFromNeverThis is custody. There is no legitimate reason a trading bot needs it.
approve (unbounded)NeverAn unbounded approval is custody with extra steps.
approve (exact, per grant)Only at grant time, by the userBounded to maxNotionalUsd, never widened by us.
Any upgrade, admin, or delegate callNeverNot in scope for trading; a bypass vector.
Anything not listedNeverDefault deny. New methods require an explicit review.

The failure mode that matters. If the whitelist is not enforced at the signer — if it is only checked in the caller, or only validated in the UI — a compromised strategy or a bug in the order path can sign a transfer. Enforce it at the signer, where it is the last thing that happens before a signature exists.

Grant and revoke

  1. Preview. The user is shown the grant in plain English before signing: which venue, which markets, which methods, what ceilings, when it expires. Generated from the grant object so it cannot drift from what is actually requested.
  2. Grant. The user signs from their own wallet. This is the only moment their wallet key is used. We never proxy it.
  3. Use. The session key signs whitelisted calls within scope. Every signature is logged with the correlation id of the decision that caused it, so any order can be traced back to the rule that proposed it.
  4. Revoke. One action, effective immediately, available in the product and independently on chain. Revocation must not require our servers to be up.
  5. Expire. Grants expire on their own. A user who walks away is not left with an open-ended authority.

Behaviour on revoke

This is the sequence most likely to be got wrong, because the tempting behaviour — "flatten everything to be safe" — is the one that breaks the custody guarantee.

Signature refused mid-run

A signature can be refused for reasons that are not errors: the grant expired, the notional ceiling is reached, the market fell outside scope, the user revoked. A strategy must treat refusal as a normal outcome.

CauseCodeCorrect handling
Grant expiredSEC_GRANT_EXPIREDPause the bot. Prompt the user to re-grant. Do not retry.
Notional ceiling reachedSEC_NOTIONAL_EXHAUSTEDStop opening. Allow cancels. Tell the user the ceiling was hit.
Market outside scopeSEC_SCOPE_VIOLATIONReject the proposal and count a strike — the bot should not have proposed it.
User revokedSEC_GRANT_REVOKEDFollow the revoke sequence above.
Signer unavailableSEC_SIGNER_UNAVAILABLEFail closed. No order. Alert operations, not the user.

Never retry a refused signature in a loop, and never fall back to a different key. Both patterns turn a bounded authority into an unbounded one.

What we log, and what we must not

the four invariants · these are not negotiable

  1. 1

    The platform never holds user funds.

    Keys stay in the user's wallet. There is no platform-controlled omnibus account, no internal ledger of custodied balances, and no code path that can move a user's assets without a signature the user authorised. If a feature needs custody to work, it does not ship.

  2. 2

    A user bot never reaches the venue directly.

    User-authored logic produces a proposal. It never holds a venue client, an API key, or a signer. Exactly one component signs and submits, and it is ours.

  3. 3

    Guardrails cannot be configured away.

    The user tunes thresholds inside bounds we set. The user cannot remove a guardrail from the chain, reorder the chain, or lower a hard cap below the platform floor.

  4. 4

    AI never holds Trade authority.

    Model output is advisory. An AI suggestion enters the same RiskGate as any bot intent and is subject to the same rejects. There is no path from a model response to a signed order that skips the chain.

If a design requires breaking one of these, the design is wrong. Escalate before writing code — do not work around it. See RiskGate and the signing model.