0009. Venue API credentials are derived at startup and held in memory
- Status: Proposed
- Date: 2026-09-30
Context
Phase 2 reads the account: open orders, cash and the User WS need Polymarket CLOB API credentials (key, secret, passphrase). They are created or derived through L1 auth: the signer signs aClobAuth message and the CLOB returns the credentials. With a Session
Key, the credentials belong to the Session Key.
The Phase 0 .env.example named the signer POLYMARKET_PRIVATE_KEY, described it as the
key that controls the account, and suggested saving derived credentials back into .env.
The security review found (F1) that the name invites putting the owner key on the host,
and (F8) that the signer key has no use on the geoblocked development server. Every copy
of a credential on disk is one more place it can leak from.
Decision
- The signer is
POLYMARKET_SESSION_KEY: a CLOB-scoped Polymarket Session Key for the Deposit Wallet inPOLYMARKET_WALLET_ADDRESS. The owner key never goes into the app. - If
POLYMARKET_CLOB_API_KEY,_SECRETand_PASSPHRASEare set, the app uses them. Otherwise a credential actor invenue/polymarketderives them through L1 auth at startup and holds them in memory only. They are never written to.env, SQLite, logs, errors, API responses or WS frames, and they are derived again at each start. - The old name
POLYMARKET_PRIVATE_KEYis refused at startup with a message to rename it. The loader does not read it as a fallback. - Optional
POLYMARKET_OWNER_ADDRESS(public): the loader refuses to start when the signer’s address equals it (F1). - The credential actor is the single source of the credential state (PLAN §3.7). It
publishes
core.CredentialStatus(none,pending,ready,failedwith a fixed reason code) oncore.TopicCredentialStatus; the browser sees it inAccountSummary.credentials. - Without credentials, Data API reads (positions, activity, P&L) still work from the
wallet address. Open orders, cash and the User WS report
credentials_unavailable(503). - The signer key is not configured on the geoblocked development server (F8). Verification there uses the fake venue and the golden vectors; the user verifies with real credentials from a permitted location.
Consequences
- No credential file to protect, back up or rotate besides the Session Key itself. Rotating the key rotates the derived credentials with it.
- Each start makes one L1 request before account data that needs credentials is
available. While it runs the state is
pending; if it fails the state isfailedand those endpoints answer 503 until a later attempt succeeds. The retry schedule is defined by implementation. - A user upgrading from Phase 1 with
POLYMARKET_PRIVATE_KEYin.envmust rename it before the app starts. This is deliberate: renaming is the moment to check that the value is a Session Key and not the owner key. - Configured
POLYMARKET_CLOB_*values must belong to the configured Session Key. After a key rotation they must be cleared or replaced. - The in-memory credentials still sit in process memory; crash dumps stay disabled (secrets and logging policy §6).
Alternatives considered
- Derive once and save the credentials to
.envor SQLite. Rejected. It adds a second secret at rest and a copy that outlives a key rotation, and SQLite holds no secret columns by policy. - Accept
POLYMARKET_PRIVATE_KEYas an alias. Rejected. The name does not say which key belongs there, and a silent alias is a fallback path. - Require the CLOB credentials in the environment. Rejected. The user would have to run L1 auth by hand, and the credentials would be stored beside the key that can derive them anyway.