session_id. Uplink is one such gateway; the hosted API at api.mor.org is another (different product, same shape). The proxy-router exposes a small set of opt-in features for that companion. Every one of them is inert until you switch it on.
Stock nodes are unaffected. With no
GATEWAY_* environment variable, no X-Gateway-Operation header, and no maxStakeWei field, the proxy-router behaves exactly as before: stock stake calculation, no journal, no wallet lock, native session-expiry cleanup, and response bodies unchanged. Only GET /config gains a GatewayCapabilities array.Capability discovery
GET /config (permission system_config) now returns a GatewayCapabilities array a companion can feature-detect against instead of parsing version strings:
Stake cap on open-by-bid (maxStakeWei)
POST /blockchain/bids/{id}/session accepts an optional maxStakeWei alongside sessionDuration. If the stake the node computes for that duration exceeds the cap, the open is rejected before any approval or open transaction is built — nothing is broadcast.
maxStakeWei also switches that open onto the gateway stake formula (see below). Leave it out and the stock formula applies.
Gateway stake formula
When managed mode is on (GATEWAY_JOURNAL_PATH set) or the request carries maxStakeWei, the node:
- refreshes total MOR supply and today’s emissions budget immediately before computing the stake, so a quote is not stale by the time approval + open have mined;
- rounds the stake up with one basis point (0.01%) of headroom, so the contract’s round-down of stipend and duration cannot deliver fewer seconds than requested;
- rejects
sessionDurationbelow the contract minimum of 300 seconds up front instead of letting the open revert.
ceil(supply × price × (duration + 1) / budget) — one second of headroom and a round-up, described in How much MOR does an open lock?. The two formulas differ by well under a tenth of a percent on any normal session, but they are not byte-identical, so a companion that quotes users a stake figure should either call GET /blockchain/models/:id/session/estimate on a node running in managed mode (the estimate then uses the gateway formula; on an unmanaged node it returns the stock figure), apply the gateway rounding itself (ceil(supply × price × duration × 10001 / (budget × 10000))), or set maxStakeWei a little above its own estimate.
Operation journal (X-Gateway-Operation)
The problem this solves: a gateway sends “open session”, the HTTP connection drops, and the gateway has no idea whether the approval or open transaction was broadcast. Retrying blindly can open a second session and lock stake twice.
With the journal enabled, the gateway attaches an idempotency key and the node records every stage durably before acting on it.
Enable
Use
SendX-Gateway-Operation: <32 lowercase hex chars> on:
POST /blockchain/bids/{id}/sessionPOST /blockchain/sessions/{id}/closePOST /blockchain/stakes/withdraw
- Any journal problem —
GATEWAY_JOURNAL_PATHunset, malformed ID, directory not writable, or an ID that already has a journal file — answers409 {"error":"operation exists or journal unavailable"}and performs no chain action. The duplicate check survives node restarts because it is the file itself. Generate a fresh ID per logical operation; a repeated ID is your signal to read the existing journal, never a retry. - Without the header, the same endpoints behave exactly as before and their bodies contain no
progressfield.
Progress in responses
When an operation was journaled, success and error bodies on those three endpoints include aprogress object:
needs_reconcile
If the node recorded a transaction hash and then failed to write the journal (disk gone, permissions), it does not pretend the open failed. stage becomes needs_reconcile, journal_write_error carries the cause, and the hashes stay in the response. The companion must treat this as “chain may have advanced” and reconcile from the hashes — not retry.
Conversely, a build failure before any hash of that kind exists resets the stage to not_submitted; a build failure after a hash exists does not, so an ambiguous broadcast can never authorise a second session under the same operation.
The journal contains stage, status, session ID, and transaction hashes only — never key material or prompt content.
Wallet lock (managed mode)
WhileGATEWAY_JOURNAL_PATH is set, OpenSession, CloseSession, and WithdrawUserStakes are serialised through a process-local lock so two concurrent gateway calls cannot race the same nonce. A call that is still waiting when its request context is cancelled returns the context error and never touches the chain.
Who closes expired sessions? (GATEWAY_OWN_CLEANUP)
By default the proxy-router runs its own expiry loop that closes sessions past endsAt — and it keeps doing so in managed mode. Setting GATEWAY_JOURNAL_PATH alone does not hand cleanup to the companion; both may close, which is safe (a second close simply fails).
Set GATEWAY_OWN_CLEANUP=1 only when the companion actually reconciles and closes expired sessions itself. The node then rehydrates its session list from chain at boot and suspends the native loop.
Mode summary
maxStakeWei on a request enables the gateway stake formula for that open in any of the three columns.
Related
- Sessions: stake, close, claim — what the stake and day-lock actually are.
- API endpoints — the open / close / withdraw calls this page decorates.
- Env: proxy-router — the two variables in the full table.
- API auth — per-agent allowances, which are independent of this mechanism.