> ## 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.

# Managed Markets

> Claim a fenced maker lease and refresh the server-bounded quote set on an operator-managed launch

```typescript theme={null}
import { MonacoSDK } from "@0xmonaco/core";

const inventory = await sdk.managedMarkets.listAssignments({ page: 1, pageSize: 100 });

// Claim a lease, then keep refreshing quotes under it
const lease = await sdk.managedMarkets.claimLease(launchId, { ownerId });
const status = await sdk.managedMarkets.refreshQuotes(launchId, {
  ownerId,
  generation: lease.generation,
  // Derive the next revision from the lease the engine just acknowledged.
  // A renewal keeps the generation's existing revision, so a hard-coded "1"
  // restates the quote set once the held revision is 1 and is refused
  // outright once it has advanced past it.
  quoteRevision: (BigInt(lease.quoteRevision) + 1n).toString(),
});
```

An operator-managed market launch is provisioned by Monaco and quoted by a maker
wallet the operator configured for that application. This namespace is that
maker's control surface: it reports the assignments the caller is expected to
quote, hands out a short **lease** that fences one process at a time, and asks
the engine to refresh a quote set whose prices, sizes, fees and risk bucket are
all chosen server-side. It never places an order you describe and never moves
funding.

***

All three methods require an authenticated **wallet** session, and delegated-agent
sessions are refused with `403` on every one of them, permanently rather than as
a rollout state.

The maker requirement applies differently per method. `claimLease` and
`refreshQuotes` are launch-scoped and need a session whose user *and* application
both match that launch's configured maker; a launch the caller is not the maker
for reads as `404` rather than `403`, so the endpoint never confirms a launch the
caller has no part in. `listAssignments` takes any authenticated wallet and
simply filters by it — a non-maker gets an **empty page**, not an error.

`ownerId` is a **process fence, not authorization**. It is a UUID you choose to
identify the process holding the lease; authorization is derived entirely from
the authenticated session's user and application binding and never from any
identity in the request body, so no `ownerId` widens what a caller can reach.

It does not let a standby seize a live lease either. A claim from a **different**
`ownerId` succeeds only once the current lease has expired; until then it answers
`409`. So a takeover is "wait out the remaining lease, then claim", not "claim
harder". Claiming after expiry bumps `generation`, which is what makes the
previous holder's next refresh fail as stale.

A claim from the **current** owner behaves differently depending on
`expectedGeneration`. Supply it and the call **renews**: the lease's expiry moves
out and the generation is unchanged. Omit it while you still hold an unexpired
lease and the call **reconciles rather than renews** — it replays the existing
reservation and returns its original `expiresAt`. A loop that keeps omitting the
generation expecting a renewal will let its own lease lapse.

`generation` and `quoteRevision` are `uint64` fences carried as **exact decimal
strings** in JSON (`ManagedMarketFence`) and as `uint64` over gRPC. Keep the
wire value as text, but **do not order or increment it as text** — `"10" < "2"`
lexically — and do not round-trip it through `Number`, which loses values above
2^53. Use `BigInt`:

```typescript theme={null}
const isStale = BigInt(local.generation) !== BigInt(server.generation);
const nextRevision = (BigInt(current) + 1n).toString();
```

<ResponseField name="listAssignments" type="(request?: ListManagedMarketAssignmentsRequest) => Promise<ListManagedMarketAssignmentsResponse>">
  List the launches this maker wallet is assigned to, for the authenticated
  application.

  **Parameters:** all optional

  * `page?`: number — 1-based page number, `1`–`10000` (default `1`)
  * `pageSize?`: number — `1`–`100`; this endpoint defaults it to `100` rather than the shared `20`

  Either bound broken is the `400` below: `page must be >= 1`, `page must be <= 10000`, or `pageSize must be between 1 and 100`.

  **Returns:** `ListManagedMarketAssignmentsResponse`:

  * `items`: `ManagedMarketAssignment[]` — `launchId`, `tradingPairId`, `symbol`, `phase`, `refreshIntervalMs`, `allocationUsdc` and `quoteEnabled`
  * `total`, `page`, `pageSize`: number
  * `inventoryObservedAt`: string — when the server read this inventory (ISO 8601)

  The list is **expected inventory**, not a ready-to-quote set: a row with
  `quoteEnabled: false` is still yours and still listed, but `claimLease` and
  `refreshQuotes` on it answer `409` 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** — keep quoting through
  activation, which is the phase that depends on those quotes.

  **`lease` is declared on the type but never populated by this endpoint.** The
  handler sets it to `None` on every row on purpose — the database reservation
  cannot prove the engine's current generation or quote revision, and a
  fabricated zero would read as a usable fence — and the field is
  `skip_serializing_if = "Option::is_none"`, so the **key is absent from the JSON
  entirely** rather than present as `null`. `claimLease` is the only source of a
  lease you may act on.

  `phase` here is the **launch** phase: `"pending"`, `"validating"`,
  `"registering"`, `"warming"`, `"activating"`, `"live"` or `"failed"`. The
  phase on a quote refresh is a different, narrower vocabulary — see
  `refreshQuotes` below.

  **Errors:** `401` (auth required), `403` (delegated-agent session), `400`
  (invalid pagination), `429` (this is an authenticated `GET`, so it is charged
  to your per-account **read** budget before the handler runs — a polling loop
  here is bounded like any other read), `500`.

  A wallet that is simply not this application's configured maker is not an
  error: it gets an empty page. `403` is for delegated-agent sessions only.
</ResponseField>

<ResponseField name="claimLease" type="(launchId: string, request: ClaimManagedMarketLeaseRequest) => Promise<ManagedMarketLease>">
  Claim, renew or reconcile the maker lease on one launch. A lease runs for 30
  seconds and is the fence every quote refresh is checked against.

  **Parameters:**

  * `ownerId`: string — UUID identifying the claiming process
  * `expectedGeneration?`: `ManagedMarketFence` — **required when renewing** an unexpired lease you already hold; omit it for an initial claim or after expiry

  **Returns:** `ManagedMarketLease` — `launchId`, `ownerId`, the engine-observed
  `generation` and `quoteRevision`, `expiresAt`, `refreshIntervalMs` and the
  launch `phase`.

  The reply is the **engine's** view, not the database reservation: the call
  reserves the lease, asks the matching engine to fence it, and then rechecks
  ownership, so a lease that expired in flight fails rather than being reported
  as held. An initial claim whose response was lost replays its existing
  reservation instead of incrementing `generation`, so a transport retry does
  not cost you the lease you already hold.

  Read `generation` back from this reply and carry it into `refreshQuotes`
  rather than assuming it survived from an earlier call.

  **Errors:** `400` (`launchId` or `ownerId` is not a UUID), `401`, `403`
  (delegated-agent session), `404` (no such launch for this maker and
  application), `409` (`Managed market is not ready for maker quotes`, or
  `Managed market lease changed or expired; reconcile the lease`), `429` (the
  claim costs one item against your order-creation budget), `500`, `503` (the
  matching engine is unreachable).
</ResponseField>

<ResponseField name="refreshQuotes" type="(launchId: string, request: RefreshManagedMarketQuotesRequest) => Promise<ManagedMarketQuoteStatus>">
  Ask the engine to restate this launch's bounded quote set under a lease you
  hold, and report what it now holds.

  **Parameters:**

  * `ownerId`: string — the same process UUID the lease was claimed with
  * `generation`: `ManagedMarketFence` — the generation from `claimLease`
  * `quoteRevision`: `ManagedMarketFence` — must be positive and at least the revision the engine already holds for this generation. A **lower** one is refused; re-sending the one it holds is an accepted idempotent no-op. Derive it from `claimLease`'s reply (`BigInt(lease.quoteRevision) + 1n`) rather than starting at `"1"`: a renewal keeps the generation's existing revision, so `"1"` merely restates the quote set while the held revision is exactly `1`, and is refused once it has advanced past that. Only a claim that bumped the generation resets the revision to `0`

  **Returns:** `ManagedMarketQuoteStatus` — `launchId`, the acknowledged
  `generation` and `quoteRevision`, `phase`, `bidNotional`, `askNotional`,
  `oracleReady`, `readyToActivate` and `observedAt`.

  `phase` on this reply is the **runtime** phase and takes only `"warming"`,
  `"live"` or `"halted"` — a narrower set than the launch phase on an
  assignment. Anything else the engine reports is answered as `503` with
  `Managed market status unavailable; reconcile the lease`.

  The refresh places one bounded bid and one bounded ask and costs **two** items
  against your order-creation budget. The engine also enforces the launch's own
  cadence and **refuses** a refresh that *advances* the revision less than
  `refreshIntervalMs` after the last one — a rejection, not a silently dropped
  no-op — so pace the loop on `refreshIntervalMs`. Re-sending the revision the
  engine already holds is exempt: that equality check runs before the cadence
  check, so an idempotent retry succeeds inside the window rather than being
  refused.

  **You cannot tell the engine's refusals apart, and there are two `409` paths.**
  Before the engine is called at all, the handler re-checks the launch's
  persisted readiness, so a launch already recorded as halted (or otherwise not
  quotable) answers `409 Managed market is not ready for maker quotes`. Every
  refusal that comes back *from* the engine — a stale revision, an early refresh,
  an expired lease, a market the engine has halted since — is folded into a
  single `409 Managed market lease changed or expired; reconcile the lease`. Both
  are `409`, so never branch on the message: call `claimLease` and act on the
  `generation` and `quoteRevision` it returns.

  Prices, sizes, fees and the risk bucket are all server-derived — the request
  carries no price, quantity or identity to override them.

  **Treat an ambiguous failure as an unknown outcome.** On a timeout or a `503`
  you do not know whether the intent reached the engine, so you do not know which
  revision it now holds. Call `claimLease` and read the engine-observed
  `quoteRevision` back before choosing the next one. Blindly re-sending the same
  revision is not itself dangerous — the engine treats an equal revision as a
  no-op and refuses a lower one — it simply tells you nothing, and leaves you
  advancing from a number you never confirmed.

  **Errors:** `400` (`launchId` / `ownerId` not a UUID, or
  `quoteRevision must be positive`), `401`, `403` (delegated-agent session),
  `404`, `409` (launch not ready to quote, or the lease changed or expired),
  `429` (the two items this call costs against your order-creation budget — the
  engine's cadence refusal is **not** a `429`; it arrives as the `409` above),
  `500`, `503`.
</ResponseField>

## See Also

* [Managed Markets protobuf reference](/grpc/protos/managed-markets) — the gRPC service and message shapes
* [Rate limits](/developers/rate-limits) — the order-creation budget these calls draw on
