> ## 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` is cursor-paginated, oldest first: send an empty `page_token` to start, then each response's `next_page_token` until it comes back empty; an empty token is definitive. `page_size` defaults to `100` and must be 1–1000, and a larger value is rejected rather than clamped. The walk seeks on `(created_at, id)` ascending, so a launch assigned mid-walk cannot make it repeat or drop a row. There is no `page` field: a stub generated from an older proto that still sets it has the unknown field discarded and reads the first page on every call, so regenerate it.
* `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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.