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

# ManagedMarketsService

> Fenced maker leases and server-bounded quote refreshes on operator-managed launches

Package: `monaco.api.managed_markets`

Source: `protos/api/managed_markets.proto`

Use `ManagedMarketsService` to drive the maker side of an operator-managed market launch: read the launches this wallet is assigned to, hold a short lease that fences one process at a time, and ask the engine to restate a quote set whose prices, sizes, fees and risk bucket are all chosen server-side. It never places a quote the caller describes and never moves funding.

```protobuf theme={null}
service ManagedMarketsService {
  rpc ListAssignments(ListManagedMarketAssignmentsRequest) returns (ListManagedMarketAssignmentsResponse);
  rpc ClaimLease(ClaimManagedMarketLeaseRequest) returns (ManagedMarketLease);
  rpc RefreshQuotes(RefreshManagedMarketQuotesRequest) returns (ManagedMarketQuoteStatus);
}
```

## Notes

* All three RPCs require an authenticated **wallet** session, and a delegated-agent session is refused on every one of them with `PERMISSION_DENIED` (REST `403`) — permanently, not as a rollout state. The maker requirement is narrower: `ClaimLease` and `RefreshQuotes` are launch-scoped and need a session whose user *and* application both match that launch's configured maker, while `ListAssignments` accepts any authenticated wallet and filters by it, so a non-maker gets an empty page rather than an error.
* `owner_id` is a **process fence, never authorization**. It is a UUID the caller chooses to identify the process holding the lease; authorization is derived solely from the authenticated session's user and application binding and never from an identity in the request body, so no `owner_id` widens what a caller can reach. A value that is not a UUID returns `INVALID_ARGUMENT`, as does a non-UUID `launch_id` on `ClaimLease` or `RefreshQuotes`, a non-positive `quote_revision`, and `ListAssignments` pagination outside `page 1..=10000` or `page_size 1..=100`.
* **A different `owner_id` cannot seize a live lease.** `claim_maker` admits a claim only when the lease is unset, already expired, or held by that same owner, so a standby claiming under a new `owner_id` gets `ALREADY_EXISTS` (REST `409`) until the current lease lapses. Takeover is "wait out the remaining lease, then claim". A claim after expiry increments `maker_generation`, which is what makes the previous holder's next refresh fail as stale. A claim by the **current** owner splits on `expected_generation`: supplied, it renews through `renew_maker` and moves the expiry out; omitted while the lease is still unexpired, it neither claims nor renews — it replays the existing reservation and returns its original expiry, so a loop that always omits it will let its own lease lapse.
* A launch this session is not the configured maker for answers `NOT_FOUND`, not `PERMISSION_DENIED`, so the service never confirms the existence of a launch the caller has no part in. `ListAssignments` is the exception in shape rather than in rule: it filters by the caller, so a wallet with no assignments gets an **empty page** rather than any error. `PERMISSION_DENIED` is reserved for delegated-agent sessions.
* `generation` and `quote_revision` are `uint64` fences. Over gRPC they are `uint64` fields; over REST they are **exact decimal strings**. A JSON client should keep the wire value as text but must not order or increment it *as* text — `"10" < "2"` lexically — and must not round-trip it through a double, which loses values above 2^53. Convert to an exact 64-bit integer type (`BigInt` in JavaScript) for every comparison and increment.
* `ListAssignments` returns **expected inventory**, not a ready-to-quote set: a row with `quote_enabled = false` is still assigned and still listed, but `ClaimLease` and `RefreshQuotes` on it return `ALREADY_EXISTS` (REST `409`, `Managed market is not ready for maker quotes`) until the launch is ready to quote: its launch phase is `warming`, `activating` or `live`, its runtime phase is `warming` or `live`, and its allocated collateral has reached the configured allocation in full. **`activating` counts** — a maker must keep quoting through activation, which is the phase that depends on those quotes. Its `lease` field is **declared on the message but never populated**: the released handler serializes it as absent on every row, deliberately, because the database reservation cannot prove the engine's current generation or quote revision and a fabricated zero would read as a usable fence. `ClaimLease` is the only source of a lease a caller may act on.
* The two `phase` fields are **different vocabularies**. On `ManagedMarketAssignment` and `ManagedMarketLease` it is the launch phase: `pending`, `validating`, `registering`, `warming`, `activating`, `live` or `failed`. On `ManagedMarketQuoteStatus` it is the engine's runtime phase and takes only `warming`, `live` or `halted`; anything else the engine reports is answered as `UNAVAILABLE` with `Managed market status unavailable; reconcile the lease`.
* `ClaimLease` reserves the lease, fences it in the matching engine, then rechecks ownership before replying, so the reply is the engine's view rather than the database reservation — a lease that expired in flight fails instead of being reported as held. The lease runs for 30 seconds. `expected_generation` is required when renewing an unexpired lease you hold and omitted for an initial claim or after expiry.
* An initial claim whose reply was lost replays its existing durable reservation instead of incrementing `generation`, so a transport retry never costs you the lease you already hold.
* `RefreshQuotes` places one bounded bid and one bounded ask. `quote_revision` must be positive (`INVALID_ARGUMENT` otherwise) and at least the revision the engine holds for that generation: a **lower** one is refused, and re-sending the one it already holds is an accepted idempotent no-op. Derive it from the `ClaimLease` reply rather than starting at `1` — a renewal keeps the generation's existing revision, so `1` restates the quote set when the held revision is exactly `1` and is refused once it has advanced past it. Only a claim that bumped the generation resets the revision to `0`.
* The engine also enforces the launch's own cadence, refusing a refresh that *advances* the revision less than `refresh_interval_ms` after the last one. That is a rejection rather than a dropped no-op, so pace the loop on the assignment's `refresh_interval_ms`. An equal-revision retry is exempt — `quote_intent` returns on the equality check before it reaches the interval check — so an idempotent replay succeeds inside the window.
* **Two `ALREADY_EXISTS` paths, and the engine's own refusals are indistinguishable.** The handler re-checks the launch's persisted readiness *before* calling the engine, so a launch already recorded as halted or otherwise not quotable answers `Managed market is not ready for maker quotes`. Everything the engine itself refuses is folded by `handlers::managed_markets::engine_error` — `ABORTED`, `ALREADY_EXISTS`, `FAILED_PRECONDITION` and `INVALID_ARGUMENT` all become one `ALREADY_EXISTS` carrying `Managed market lease changed or expired; reconcile the lease` — so a stale revision, an early refresh, an expired lease and a market halted since the readiness check are one status and one message. Both paths are `ALREADY_EXISTS`: do not branch on the text, reconcile through `ClaimLease` and act on the generation and revision it returns.
* **Cost:** `ClaimLease` admits one item and `RefreshQuotes` two against the caller's ordinary order-creation budget, and `ListAssignments` draws the per-account **read** budget like every other authenticated read, charged before the handler runs. Those two budgets are the only sources of `RESOURCE_EXHAUSTED` here — the engine's cadence refusal is **not** one of them; it arrives as the folded `ALREADY_EXISTS` below. Either way it carries a `google.rpc.RetryInfo` detail — the same interval REST returns as `retryAfter` — so honor that rather than guessing. See [Rate limits](/developers/rate-limits).
* `ALREADY_EXISTS` (REST `409`) also covers `Managed market lease changed or expired; reconcile the lease`: the lease moved to another owner or generation, or lapsed. Reconcile through `ClaimLease` and read the engine-observed `generation` and `quote_revision` back before continuing.
* Treat a `RefreshQuotes` timeout or `UNAVAILABLE` as an **unknown outcome**, not a rejection: the intent may have reached the engine. Reconcile through `ClaimLease` and take the engine's `quote_revision` rather than advancing your own and retrying blind.
* `UNAVAILABLE` (REST `503`) means the matching engine could not be reached or answered with a status this service cannot act on. `INTERNAL` is a generic server-side failure and can happen before the engine is contacted at all, so unlike `UNAVAILABLE` it says nothing about engine state.
* Quote intents are server-derived end to end: a fixed preallocated risk bucket, server-chosen prices, sizes and the application's canonical taker fee. The request carries no price, quantity or identity that could override any of it, and no path here funds an account.
