> For the complete documentation index, see [llms.txt](https://docs.strikelabs.app/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.strikelabs.app/protocol/custody.md).

# Custody and the money lifecycle

How real money enters and leaves, which party signs each step, and what custody actually means here. Read the custody section before you fund an account. It is stated plainly rather than favourably, because the favourable version would be false.

### Custody, plainly

**Strike holds your funds.** Deposits sit in a single vault contract on Arbitrum. The operator key can move **every dollar in that vault to any address it chooses**, and nothing in the contract prevents it.

The mechanism is not a bug and is not hidden. Entitlements are credited by `commitBatch`, which checks solvency and batch identity — **not whether a payee is a real user**. So the operator can credit an address it controls and have that address call the ordinary `claim()`. The vault ends empty, `totalCommitted` returns to zero so the books look clean afterwards, and the only on-chain trace is a `BatchCommitted`/`Claimed` pair **indistinguishable from a legitimate payout**. No emergency event is emitted on that path.

The contract's immutable `failsafe` address bounds `emergencyWithdraw` and **nothing else**. It is not a bound on where operator-directed funds can go, and this vault must not be described as if it were.

**Once an entitlement is committed, claiming it needs nobody's permission** and no Strike server — that part is real, and it is why the design is worth having. But **getting an entitlement committed requires the venue to agree**. That is not a trustless exit, and it is not sold as one here.

This is inherent to enforcing margin off-chain: an operator that can commit arbitrary entitlements *is* the design. That key is the custody model. It is disclosed here, in the same terms as the vault contract's own header, so nobody can claim the blast radius was hidden.

## The shape of it

Four steps, and the party that signs each one is different. The venue itself never touches the chain: it holds no RPC connection and no key. Separate operator daemons watch for deposits and commit payouts, and they reach the venue's ledger through token-gated endpoints you never call.

| STEP                                                                 | WHO SIGNS                      | WHERE                                               | GAS        |
| -------------------------------------------------------------------- | ------------------------------ | --------------------------------------------------- | ---------- |
| <p><strong>1. Deposit</strong><br>USDC into the vault</p>            | **You**, with your wallet      | Arbitrum — a transaction against the vault contract | Yours      |
| <p><strong>2. Credit</strong><br>the venue books your balance</p>    | The operator's deposit watcher | Off-chain, after N confirmations                    | None       |
| <p><strong>3. Request</strong><br><code>POST /v1/withdraw</code></p> | **You**, with your session key | Off-chain. Debits your venue cash immediately       | None       |
| <p><strong>4. Commit</strong><br>the entitlement goes on chain</p>   | The operator's batcher         | Arbitrum — `commitBatch`                            | Operator's |
| <p><strong>5. Claim</strong><br>USDC reaches your wallet</p>         | **You**                        | Arbitrum — `claim()`                                | **Yours**  |

## 1. Depositing

Send USDC to the vault contract on Arbitrum from the wallet you sign in with. That is the whole user-facing step — there is no Strike API call, and `POST /v1/deposit` is **not** for you: it is the operator's watcher crediting your balance after the fact.

The address you deposit from is the identity your account is keyed to. Deposit from an address you have never signed in with and the money is still recorded as owed, but the account is **held** — reachable by nobody until an operator resolves it by hand. Sign in first; it costs one signature and avoids a support conversation.

USDC has 6 decimals, and so does the venue's internal representation, so nothing is lost in translation. The vault will not accept a fee-on-transfer token, because the credit event carries the requested amount rather than the amount that arrived.

## 2. Getting credited

The watcher waits for confirmations, then credits your venue balance. It is idempotent on `(txHash, logIndex)`, so one chain log credits exactly once no matter how many times the daemon retries or restarts.

Poll `GET /v1/me` and watch `cash`. In `deposits` mode accounts start at zero and no other mechanism creates cash, so a rising balance is a confirmed deposit.

## 3. Requesting a withdrawal

```
payout = client.withdraw(250.0)
# {"id": 41, "amountUsd": 250.0, "state": "requested",
#  "address": "0x…", "account": {...}}
```

Session-signed, through the same verification path as an order. The **amount is inside the signed envelope**, so a signature for $10 cannot be spent on $10,000.

### You do not choose the destination

There is no destination field in the request and none in the envelope. The venue pays only to your account's own linked wallet address, pinned at request time so that the destination of money already debited cannot change underneath the queue. A destination on the wire would imply one could be chosen; it cannot, and there is deliberately nothing there for a signature to authorise.

### The gate is buying power, never equity

`free = buyingPower − alreadyPendingWithdrawals`, where `buyingPower = cash − locked`.

Equity is deliberately *not* the gate. Equity includes unrealised marks, and a mark is the midpoint of a book someone can post into — gating on it would let a trader quote their own position up and withdraw against the print. `locked` already covers short margin and resting-order reserves, so **margin backing an open short is not withdrawable**. Cancel the orders or close the short first.

The check and the debit happen in one critical section, so two requests racing for the same balance cannot both pass. At most **16** payouts may be outstanding at once. A refusal never debits.

## 4. Commitment

The operator's batcher polls for `requested` payouts and commits them on chain in batches, then marks them `committed`. There are exactly two states and no way back: `requested → committed`.

`commitBatch` reverts unless the USDC is already in the contract, so a committed entitlement is never unfunded. There is no separate funding leg between commitment and your claim.

Batching is why this is not instant: your payout waits for the next batch. The cadence is an operational choice, not a protocol guarantee.

## 5. Claiming

Once committed, the entitlement is yours to take. Call `claim()` on the vault from your own wallet. It pays your full entitlement, zeroes it before transferring, and needs no cooperation from Strike — no server, no API, no permission. If Strike disappeared entirely after your entitlement was committed, you could still claim it.

{% hint style="warning" %}
**Claiming costs gas, and that is not optional**

`claim()` is an Arbitrum transaction you pay for. You need a small amount of ETH on Arbitrum in the claiming wallet — a wallet holding only USDC cannot claim. Plan for this *before* you need to exit, not during.

There is a `claimFor` path that lets a third party submit a claim you signed, and the operator may run a relayer that does so. Treat it strictly as a convenience: **`claim()` with your own gas always works**, and is the path that does not depend on anyone else being up. The relayer is deliberately non-load-bearing — its own health endpoint reports `loadBearing: false` — and no exit here should be described as gasless without that caveat. `claimFor` pays only the user named in the signature, so a relayer cannot redirect your funds.
{% endhint %}

## Checking reserves yourself

`GET /v1/reserves` is public and unauthenticated on purpose: the point of a reserve number is that anyone can read it without asking the operator. It exposes only aggregates — no account, no address, no position.

```
curl -s https://api.strikelabs.app/v1/reserves
```

```
{
  "cashTotalUsd": 412500.0,
  "pendingWithdrawalsUsd": 8250.0,
  "uncollectibleUsd": 0.0,
  "negativeAccounts": 0,
  "nonExemptAccounts": 37
}
```

The solvency invariant you can check against the chain yourself:

```
cashTotalUsd + pendingWithdrawalsUsd  <=  USDC.balanceOf(vault) − vault.totalCommitted()
```

{% hint style="info" %}
**Read `uncollectibleUsd`, not just the total**

`cashTotalUsd` is `Σ max(cash, 0)`, clamped **per account**. An overdrawn account does not net against anyone else's balance — a naive sum would make the published liability *smaller* exactly when the house had just absorbed a loss, so the alarm would read backwards under stress.

`uncollectibleUsd` is `Σ max(−cash, 0)`: money already paid out on someone's behalf that will probably never be collected. It arises from forced unwinds and from shorts settling in the money beyond their margin. It reduces the operator's buffer. It is **not** headroom and not room to withdraw against.
{% endhint %}

The market maker is excluded from these sums. It trades on fictional capital that no deposit backs, and its cash can never leave as USDC.

## What the operator can do, in one list

So that none of it is a surprise:

* **Move every dollar in the vault to any address**, by committing entitlements to addresses it controls and claiming them. Leaves the books looking clean. This is the one that matters.
* **Refuse to commit** a withdrawal you have requested. Your cash is already debited at that point; the entitlement is what has not yet been created.
* **Sweep to the failsafe address** via `emergencyWithdraw`. This one is bounded — it can pay only the single immutable failsafe address fixed at deployment — and it emits a distinct event. It is the *only* outflow the contract constrains.
* **Hand-settle a series the oracle refused**, within a bounded deviation from the last oracle print, with every attempt audit-logged. See [Market structure](/protocol/market-structure.md).

There is no pause, no upgrade path, no proxy and no owner transfer on the vault: exactly one privileged role plus the immutable failsafe destination. That limits what can change about the contract. It does not limit the operator key.

***

This page is intended to say the same thing as the vault contract's header. If it reads as softer than the contract, the page is wrong — report it.


---

# 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://docs.strikelabs.app/protocol/custody.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.
