> For the complete documentation index, see [llms.txt](https://mute-sh.gitbook.io/mute.sh/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mute-sh.gitbook.io/mute.sh/integration-guides/cashout.md).

# Cashout

**Cashout** lets a Market Maker buy out a *live, partly-resolved* parlay's taker side before the parlay finishes — the sports-book "Cash Out" button, on-chain and trustless. The MM pays the taker now and **takes over the position** (a secondary-market *novation*): the MM now wins or loses the remaining legs at settlement.

Cashout is a **competitive, open market** — any registered MM can post an offer on any live position, including the parlay's own original maker (see the [Self-bid example](#example-2-the-original-maker-buys-back-its-own-book) below). The only restriction is that the *current taker itself* can never be the buyer (no self-novation by the seller).

> **Testnet only**, same as the rest of Parlay. Cashout requires the `novate`-enabled `ParlayEscrow`; a parlay can only be cashed out in the escrow it was created in.

***

## How it works

![Cashout novation flow](/files/MdCtZhXYKjKfW9SCIA1X)

The relay is the **neutral recipient + persister** of offers (it never prices or signs, and never holds funds). Both the MM's offer and the taker's acceptance are EIP-712 signatures the escrow verifies on-chain. Unlike placing a new parlay — where `accept` returns immediately and the fill confirmation arrives later over a separate async push — the cashout `/cashout/accept` call **waits for the `novate` transaction to be mined** before responding, so its response already reflects the on-chain outcome.

**The payout is a brand-new side payment, not a cut of the pot.** The MM's `payout` is pulled from the **MM's own wallet** and credited to the taker's balance; the original escrowed pot (`takerCollateral + makerCollateral`) is completely untouched by novation. Cashout only ever changes *who owns the claim* to that pot — it never splits, refunds, or reduces it.

***

## The two signed structs

A cashout mirrors the parlay's two-signature model. Both are EIP-712 over the `ParlayEscrow` domain.

**`CashoutOffer`** — the MM signs this:

| Field              | Type      | Notes                                                             |
| ------------------ | --------- | ----------------------------------------------------------------- |
| `maker`            | `address` | the MM buying the position (becomes the new taker)                |
| `taker`            | `address` | the current taker being bought out (the on-chain `parlay.taker`)  |
| `parlayId`         | `uint256` | the on-chain parlay being novated                                 |
| `payout`           | `uint256` | USDC paid to the taker now (6dp), pulled from the MM's own wallet |
| `resolvedWonCount` | `uint8`   | **pins** the parlay's won-leg count at offer time                 |
| `voidedCount`      | `uint8`   | **pins** the void-leg count at offer time                         |
| `nonce`            | `uint256` | maker-scoped replay/cancel nonce                                  |
| `deadline`         | `uint256` | offer validity, unix seconds                                      |

**`CashoutAcceptance`** — the taker signs this:

| Field       | Type      | Notes                                                    |
| ----------- | --------- | -------------------------------------------------------- |
| `taker`     | `address` | the accepting taker (must equal the signer)              |
| `offerHash` | `bytes32` | EIP-712 struct hash of the exact `CashoutOffer` accepted |
| `nonce`     | `uint256` | taker-scoped nonce                                       |
| `deadline`  | `uint256` | acceptance validity, unix seconds                        |

**Staleness / anti-snipe guards — two layers.** An offer prices the residual position against a specific resolution state, so the escrow rejects it the instant that state moves:

1. **Pinned counters.** `resolvedWonCount`/`voidedCount` pin the parlay's recorded resolution state at offer time. `novate` reverts ("Resolution state changed") the instant a resolved leg is recorded on-chain and the counters move.
2. **Live published-result check.** A leg's outcome typically becomes public *before* it's recorded on-chain (`resolveLeg` can only record it once the dispute window has elapsed — potentially hours later). Relying on the pinned counters alone would leave a standing offer technically "valid" throughout that whole window even though the result is already public — letting an informed taker dump a known-dead position, or an MM snipe a known-winning one. To close that gap, `novate` additionally rejects the moment **any still-unresolved leg already has a published (even provisional) result** in the resolution registry, without waiting for the dispute window. This check is veto-safe: if a provisional result is later vetoed, the leg resets to unresolved and cashout re-opens.

A stale, mispriced offer can therefore never be accepted under either guard. The relay also drops a stale offer from its book on its next state refresh — there is no separate "cancel on news" step; resolution *is* the invalidation.

**Payout is capped at the pot, on-chain.** `novate` also enforces `payout <= takerCollateral + makerCollateral` as a defense-in-depth sanity floor — a payout above the pot would be a guaranteed loss for the MM (fat-finger, pricing bug, or negative edge), so the escrow refuses to let it through regardless of what was signed.

***

## Market Maker flow

Connect to the maker channel `/ws/rfq` and `auth` exactly as for quoting (see the [Market Maker Guide](/mute.sh/integration-guides/maker-guide.md)). Any authenticated MM may post an offer on any live position — there is no requirement to be the parlay's original maker. You must hold a standing USDC allowance to the escrow — the escrow pulls `payout` from your own wallet at novation (it is never deducted from the original maker's escrowed collateral).

1. **Read** the live position (`parlays(parlayId)` + `parlayVoidedCount(parlayId)`): confirm `!settled && !failed` and note `taker`, `resolvedWonCount`, `voidedCount`, and the pot (`takerCollateral + makerCollateral`).
2. **Price** the residual: a fair cashout ≈ `P(remaining legs all win) × pot`, minus your edge. Use live HIP-4 outcome prices for the unresolved legs.
3. **Sign** a `CashoutOffer` (taker = the on-chain taker; counts = current) and send:

   ```json
   { "type": "CASHOUT_OFFER", "parlay_id": "...", "payout_e6": "...",
     "resolvedWonCount": 1, "voidedCount": 0, "nonce": "...", "deadline": "...",
     "signature": "0x…", "valid_until": 1699999999000 }
   ```

   The relay replies `{ "type": "ACK_CASHOUT_OFFER", "parlay_id", "offer_id" }`, or `{ "type": "CASHOUT_ERROR", "code", "error" }`.
4. **One live offer per maker per position.** A fresh signed offer **supersedes** your previous one (there is no PATCH — re-submit to modify). Re-quote whenever a leg resolves (your old offer is already invalid).
5. **Withdraw** an offer with `{ "type": "CASHOUT_CANCEL", "parlay_id", "offer_id" }`. This is a *soft* cancel: it removes the offer from the relay's book (so no taker can accept it through the relay), but the EIP-712 signature itself remains valid until its `deadline`. Because `novate` is permissionless, a party holding both signatures could still submit it on-chain — the relay never discloses your offer signature, but if you need a **trustless hard cancel**, burn the offer's nonce on the escrow: `cancelNonce(nonce)` (or `cancelNonces(uint256[])` for bulk, `isNonceUsed(owner, nonce)` to check). Once the nonce is consumed, `novate` reverts for that offer regardless of who submits it. In practice, short `deadline`s plus the relay soft cancel are sufficient; reserve the on-chain cancel for long-dated offers or a compromised channel. The relay respects hard cancels: an offer whose nonce is already used on-chain is rejected at ingest, and if you burn the nonce after posting, the relay detects it at accept time, drops the offer, and fails the acceptance without submitting.

When a taker accepts, the relay submits `novate` and pushes you `cashout:executed`. If the parlay then loses, you (as the new taker) get nothing; if it wins, you collect the whole pot at settlement — see the worked examples below for the exact accounting, including the case where you were the original maker.

***

## Taker flow

The relay pushes the **best live offer** to your `/ws/taker` socket as `{ "type": "cashout:offer", "parlay_id", "offer": { offer_id, maker, payout_e6, resolvedWonCount, voidedCount, nonce, deadline, valid_until } | null }`.

**Cashout pushes require a verified subscribe.** The base `hello` used for the ordinary RFQ push path is unauthenticated (a taker only ever gets pushes for RFQs they themselves created, so no proof of address control is needed there). Cashout is different — it concerns a *pre-existing* on-chain position you didn't just create — so before the relay will deliver `cashout:offer`/`cashout:executed` for an address, your socket must additionally send a **signed** `hello` proving control of that address (a fixed, domain-separated message binding the address and a fresh timestamp, checked against a short freshness window). An unsigned `hello` still works for the RFQ path but will not receive cashout pushes for that address. The SDK's cashout helpers handle this signing for you automatically.

To accept an offer:

1. `POST /v1/positions/:parlayId/cashout/prepare` with `{ "offer_id" }` → returns the `CashoutAcceptance` to sign (`{ taker, offerHash, nonce, deadline }`), plus the EIP-712 `domain` + `types`.
2. Sign it (EIP-712). The SDK exposes `signCashoutAcceptance(signer, prepared)` and the high-level `acceptCashout(signer, parlayId, offerId)`.
3. `POST /v1/positions/:parlayId/cashout/accept` with `{ "offer_id", "signature" }`. The relay submits `novate` and **waits for it to be mined** before responding; on success it returns `{ tx_hash, new_owner, payout_e6 }` and pushes `cashout:executed` to both the taker and the MM.

**Acceptance windows — two clocks.** The prepared acceptance carries an on-chain EIP-712 `deadline` of **1 hour**; separately, the relay's prepared ticket expires after **10 minutes** by default (`V2_CASHOUT_ACCEPT_TTL_MS`) — past that, `accept` returns `EXPIRED_RFQ` and you simply re-`prepare` and re-sign. Both are independent of the MM's own offer `deadline`/`valid_until`, which the MM chooses.

Your `payout` is credited to the escrow's pull-payment ledger, not sent to your wallet directly — claim it exactly like a settlement payout: sign an EIP-712 `WithdrawRequest` (the relay's `claim:prepare` → `claim` WS flow, surfaced as the Claim button on parlay.mute.sh; the relayer submits `withdrawWithSig` and pays gas). Direct callers with gas can still use `withdraw()` / `withdrawTo(addr)`. `GET /v1/positions/:parlayId/cashout` returns the current best offer + position state at any time (a plain REST poll, no signed subscribe required).

***

## Worked examples

Both examples share the same setup: **Alice** is the taker, **Bob** is the original maker. The parlay has a $300 pot (`takerCollateral + makerCollateral`), one leg has already resolved in Alice's favor, and the remaining legs are still live.

### Example 1: third-party novation

**Charlie**, a competing MM with no prior stake in this parlay, thinks the remaining legs are likely to hit. He signs a `CashoutOffer` paying Alice $220 to buy out her taker position.

1. Alice accepts. `novate` pulls **$220 from Charlie's own wallet** and credits it to Alice's withdrawable balance. Bob's escrowed maker collateral is untouched throughout.
2. Alice claims (signs a `WithdrawRequest`; the relayer submits `withdrawWithSig`) and receives her $220.
3. `parlay.taker` is now Charlie. The $300 pot still sits in the escrow, unchanged.
4. If the remaining legs all win: settlement credits Charlie (not Alice) the $300 pot. Charlie's net P\&L on the trade: `-$220 (paid Alice) + $300 (won pot) = +$80`.
5. If a remaining leg loses: Bob (the maker) collects the $300 pot as normal — Charlie gets nothing, and his $220 payout to Alice was the price of taking on that risk.

### Example 2: the original maker buys back its own book

This time **Bob**, the original maker, decides the remaining legs are more likely to hit than his own quote assumed, and wants to buy out Alice's position himself. There is no rule against this — the only counterparty ever blocked from offering is the current taker (no self-novation by the seller).

1. Bob signs a `CashoutOffer` with `maker = Bob`, paying Alice $220. Alice accepts.
2. `novate` pulls $220 from Bob's wallet (a fresh transfer, separate from his original escrowed maker collateral) and credits Alice. `parlay.taker` is reassigned to Bob.
3. **Bob is now both the maker and the taker of the same parlay** (`parlay.taker == parlay.maker == Bob`). This isn't a bug — settlement resolves the winner as `takerWins ? parlay.taker : parlay.maker`, and both branches now point at Bob, so he collects the $300 pot regardless of which way the remaining legs go.
4. Economically, Bob has converted a two-sided position into a guaranteed $300 receipt, at a cost of the $220 he paid Alice plus the $150 (say) he originally posted as maker collateral. He nets `$300 - $220 - $150 = -$70` against his own collateral if he'd otherwise won as maker anyway — but the real use case is the reverse: a maker who's now worried the remaining legs will hit uses this to cap their downside by buying out the taker's upside for a known price, locking in the outcome instead of riding it out.

***

## On-chain: `novate`

```solidity
function novate(
    ParlayEIP712Types.CashoutOffer calldata offer,
    ParlayEIP712Types.CashoutAcceptance calldata acceptance,
    bytes calldata offerSignature,
    bytes calldata acceptanceSignature
) external; // also novateWithPermit(..., Permit makerPermit) for gasless MM funding
```

Permissionless to relay; both signatures required. It verifies the position is live, the offer's pinned counts match the current chain state (plus the live published-result check above), both deadlines, the payout-vs-pot cap, and that the offering maker isn't the current taker — then consumes both nonces (the standard `nonceBitmap`), pulls `payout` from the MM's own wallet into the pull-payment ledger for the taker, and flips the single immutable field `parlay.taker` to the MM. Settlement math, fees, spread, and resolution are entirely unchanged by novation — the original pot simply changes hands to whoever holds `parlay.taker` when it finally settles. Emits `PositionNovated(parlayId, oldTaker, newTaker, payout, resolvedWonCount, voidedCount)`.

**v1 scope:** whole-position, taker-side cashout only. Partial cashout (selling a *fraction* of a position) needs fractional pot accounting and is out of scope.

***

## Run the end-to-end demo

A full cashout cycle can be walked end-to-end against a throwaway local chain: create a multi-leg parlay, resolve one leg, have an MM post a signed offer, have the taker accept it, novate on-chain, then resolve the rest and confirm settlement pays the new owner. Ask in the developer channel for the current runner script if you want to reproduce this locally — it isn't part of the public API surface.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://mute-sh.gitbook.io/mute.sh/integration-guides/cashout.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
