> ## Documentation Index
> Fetch the complete documentation index at: https://pmxt-sync-hosted-docs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Escrow Lifecycle

> Deposit, trade, withdraw — how USDC moves through PMXT's non-custodial PreFundedEscrow.

Hosted trading on PMXT settles through a non-custodial `PreFundedEscrow` smart contract on **Polygon** (chainId 137). USDC sits in the escrow under your wallet's beneficial ownership; PMXT cannot move funds without an EIP-712 signature from your wallet. This page walks through the full lifecycle: approve, deposit, trade, withdraw.

<Info>
  **You fund Polygon only.** USDC always sits in the Polygon `PreFundedEscrow`
  for the currently hosted trading venues (Polymarket, Opinion, and
  Limitless). For hosted venues that don't settle on Polygon (Opinion on BSC,
  Limitless on Base), PMXT operates an internal cross-chain leg that holds
  outcome tokens on the venue's chain; you neither fund nor sign anything on
  that side. Pass any EVM-compatible private key; the same EVM wallet/key can
  trade the currently hosted venues.
</Info>

## Why escrow at all?

Polymarket's CLOB exchange expects the submitter to be the operator of the user's CLOB proxy wallet. That proxy is created by Polymarket's USDC.e adapter and is non-trivial to operate from a third-party context. PMXT's `PreFundedEscrow` solves this by acting as a pre-funded operator: the user deposits USDC once, PMXT routes orders against that balance, and the user can withdraw at any time. The user signs every order with EIP-712 against the Polygon escrow's domain; the escrow contract can only spend USDC against signed orders, never unilaterally.

For Opinion, the same Polygon escrow balance funds a cross-chain settlement leg into a BSC-side `VenueEscrow` that holds Opinion outcome tokens — you still only deposit on Polygon, and your EIP-712 signature still targets the Polygon escrow domain. The BSC hop is PMXT's plumbing.

## The `client.escrow` namespace

Hosted exchange clients (Polymarket, Opinion, Limitless) expose an `escrow` namespace. Every method **builds an unsigned transaction**. Your wallet — MetaMask, ethers `Wallet`, viem, web3.py, etc. — is responsible for signing and broadcasting it. PMXT never holds your private key.

| Method (Python / TypeScript)                                                        | Description                                                  |
| ----------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| `escrow.approve_tx(token, amount_wei=None)` / `escrow.approveTx(token, amountWei?)` | Build an unsigned ERC-20 approval for USDC or CTF.           |
| `escrow.deposit_tx(amount)` / `escrow.depositTx(amount)`                            | Build an unsigned USDC deposit into PreFundedEscrow.         |
| `escrow.withdraw_tx(action, amount=None)` / `escrow.withdrawTx(action, amount?)`    | Build an unsigned `request` / `claim` / `cancel` withdrawal. |
| `escrow.withdrawals(include="pending,events")` / `escrow.withdrawals({ include })`  | Read pending withdrawal state and historical events.         |

See the source: [`sdks/python/pmxt/escrow.py`](https://github.com/pmxt-dev/pmxt/blob/main/sdks/python/pmxt/escrow.py) and [`sdks/typescript/pmxt/escrow.ts`](https://github.com/pmxt-dev/pmxt/blob/main/sdks/typescript/pmxt/escrow.ts).

## 1. Approve and deposit

Before the first deposit, the escrow contract needs permission to pull USDC from your wallet (a standard ERC-20 approval). Then you deposit. For most users, the dashboard handles both in a single connected-wallet flow.

### Recommended: use the dashboard

Open [pmxt.dev/dashboard](https://pmxt.dev/dashboard), connect the wallet whose address you passed to the SDK, and use the Deposit flow. Your wallet (MetaMask, Rabby, WalletConnect, etc.) prompts you to sign — PMXT never sees your private key.

1. Go to [pmxt.dev/dashboard](https://pmxt.dev/dashboard) and connect the matching wallet.
2. Click **Deposit** and enter the USDC amount.
3. Approve the USDC allowance in your wallet (one-time per token).
4. Confirm the deposit transaction in your wallet.

<Note>
  Approval is one-time per token + spender; the deposit itself is one-time setup before your first trade. If you ever rotate the escrow contract (rare, gated on a PMXT migration announcement), you'll need to re-approve.
</Note>

<Accordion title="Advanced: programmatic approve + deposit">
  If you're scripting deposits (CI, treasury automation, custom wallet integrations), `client.escrow` builds **unsigned** transactions you sign and broadcast with your own wallet library.

  **Approve**

  <CodeGroup>
    ```python Python theme={null}
    # Unlimited approval for USDC (most common)
    result = client.escrow.approve_tx("usdc")
    # result is a dict wrapping the unsigned tx, e.g.:
    #   { "tx": { "to": "0x...", "data": "0x...", "value": "0",
    #             "chainId": 137, "gas": "...", "maxFeePerGas": "...",
    #             "maxPriorityFeePerGas": "...", "nonce": "..." } }
    tx = result["tx"]
    # Sign and broadcast `tx` with your preferred wallet library.

    # Or scope the approval to a specific wei amount
    result = client.escrow.approve_tx("usdc", amount_wei=1_000_000_000)  # 1,000 USDC
    tx = result["tx"]

    # Approve the Polymarket CTF token (only needed for direct CTF transfers)
    result = client.escrow.approve_tx("ctf")
    tx = result["tx"]
    ```

    ```typescript TypeScript theme={null}
    // Unlimited approval for USDC (most common)
    const { tx } = await client.escrow.approveTx("usdc");
    // The returned object wraps the unsigned tx, e.g.:
    //   { tx: { to: "0x...", data: "0x...", value: "0", chainId: 137,
    //           gas: "...", maxFeePerGas: "...",
    //           maxPriorityFeePerGas: "...", nonce: "..." } }

    // Or scope the approval to a specific wei amount (bigint)
    const { tx: txScoped } = await client.escrow.approveTx("usdc", 1_000_000_000n);

    // Approve the CTF token
    const { tx: ctfTx } = await client.escrow.approveTx("ctf");
    ```
  </CodeGroup>

  **Deposit**

  Amounts are in **whole USDC** (6 decimals); the SDK validates precision and rejects values like `0.0000001`.

  <CodeGroup>
    ```python Python theme={null}
    # Deposit 10 USDC
    result = client.escrow.deposit_tx(amount=10.0)
    tx = result["tx"]
    # Sign and send `tx` with your wallet library.

    # Decimals up to 6 places are supported
    tx = client.escrow.deposit_tx(amount=10.5)["tx"]
    tx = client.escrow.deposit_tx(amount=10.123456)["tx"]
    ```

    ```typescript TypeScript theme={null}
    // Deposit 10 USDC — number, decimal string, or wei BigInt all accepted
    const { tx } = await client.escrow.depositTx(10);
    const { tx: tx2 } = await client.escrow.depositTx("10.5");
    const { tx: tx3 } = await client.escrow.depositTx(10_500_000n); // wei micro-USDC
    ```
  </CodeGroup>

  <Warning>
    USDC precision is 6 decimals. The SDK rejects `0.0000001` with `ValidationError`. Pre-round before passing the value in.
  </Warning>
</Accordion>

## 2. Confirm the deposit

After the deposit transaction confirms on-chain, the balance shows up in escrow. `fetch_balance` is the canonical check.

<CodeGroup>
  ```python Python theme={null}
  balances = client.fetch_balance()
  usdc = balances[0]
  print(f"Available: {usdc.available}, Locked: {usdc.locked}, Total: {usdc.total}")
  ```

  ```typescript TypeScript theme={null}
  const balances = await client.fetchBalance();
  const usdc = balances[0];
  console.log(`Available: ${usdc.available}, Locked: ${usdc.locked}, Total: ${usdc.total}`);
  ```
</CodeGroup>

<Info>
  On-chain confirmation usually takes 2–5 seconds on Polygon. If `fetch_balance` still reads zero 30 seconds after broadcast, check the tx on Polygonscan — a failed deposit (e.g. due to missing approval) will not update escrow state.
</Info>

## 3. Trade

With escrow funded, you can trade. `create_order` and `submit_order` debit the escrow balance; `cancel_order` releases the reservation. No additional escrow calls are needed during normal trading — the balance just gets spent.

```python theme={null}
order = client.create_order(
    market_id="2eeb03dc-404b-41d5-bc57-6aeb37927ae6",
    outcome_id="a114f052-1fd1-4bcd-b9cf-de019db81b67",
    side="buy",
    order_type="market",
    amount=5.0,
    denom="usdc",
    slippage_pct=30.0,
)
```

## 4. Withdraw

Withdrawals are a **two-step timelock**. You first `request` a withdrawal; after a contract-enforced delay (\~1 hour in production), you `claim` it. You can also `cancel` a pending request.

The timelock is a security feature — it gives you a window to detect and abort an unauthorized withdrawal even if the operator key were compromised. Because the escrow is non-custodial, every withdrawal step requires a signature from your wallet.

### Recommended: withdraw from the dashboard

The dashboard handles the full request → wait → claim journey as one user flow.

1. Open [pmxt.dev/dashboard](https://pmxt.dev/dashboard) and connect your wallet.
2. Click **Withdraw**, enter the USDC amount, and confirm the request transaction in your wallet.
3. Wait for the timelock to elapse (\~1 hour in production). The dashboard shows a countdown for each pending request.
4. Come back, click **Claim** on the matured request, and confirm the claim transaction in your wallet. Funds move from escrow to your wallet.

If you change your mind during the timelock window, the dashboard exposes a **Cancel** action on the pending request — the funds stay in escrow as free balance.

<Accordion title="Advanced: programmatic withdraw">
  For scripted withdrawals, `client.escrow.withdraw_tx` builds **unsigned** request / claim / cancel transactions you sign and broadcast with your own wallet library.

  **Request**

  <CodeGroup>
    ```python Python theme={null}
    tx = client.escrow.withdraw_tx("request", amount=10.0)["tx"]
    # Sign and send `tx`. After the timelock, the funds become claimable.
    ```

    ```typescript TypeScript theme={null}
    const { tx } = await client.escrow.withdrawTx("request", 10);
    ```
  </CodeGroup>

  **Inspect pending withdrawals**

  <CodeGroup>
    ```python Python theme={null}
    state = client.escrow.withdrawals(include="pending,events")
    # state.pending is a list of pending requests with their `claimable_at` timestamps.
    for req in state["pending"]:
        print(req["amount"], "claimable at", req["claimable_at"])
    ```

    ```typescript TypeScript theme={null}
    const state = await client.escrow.withdrawals({ include: "pending,events" });
    // state.pending lists requests with claimable_at timestamps
    for (const req of state.pending) {
      console.log(req.amount, "claimable at", req.claimable_at);
    }
    ```
  </CodeGroup>

  **Claim**

  Once `claimable_at` has passed, claim the funds — they move from escrow to your wallet.

  <CodeGroup>
    ```python Python theme={null}
    tx = client.escrow.withdraw_tx("claim")["tx"]
    ```

    ```typescript TypeScript theme={null}
    const { tx } = await client.escrow.withdrawTx("claim");
    ```
  </CodeGroup>

  <Note>
    `claim` does not take an `amount`. It claims all matured requests at once. The escrow tracks individual request maturities; only matured ones settle.
  </Note>

  **Cancel**

  If you change your mind during the timelock window, cancel a pending request and the funds remain in escrow as free balance.

  <CodeGroup>
    ```python Python theme={null}
    tx = client.escrow.withdraw_tx("cancel")["tx"]
    ```

    ```typescript TypeScript theme={null}
    const { tx } = await client.escrow.withdrawTx("cancel");
    ```
  </CodeGroup>
</Accordion>

## Errors you might hit

* `MissingWalletAddress` — `client.escrow.*` requires `wallet_address` on the exchange constructor. Pass it explicitly.
* `ValidationError: amount precision exceeds 6 decimals` — round before passing.
* `InsufficientEscrowBalance` (during a trade) — deposit more before retrying, or wait for matured withdrawals to clear pending positions.
* `HostedTradingError` (5xx) — transient server error; retry with backoff. See [Hosted errors](/guides/hosted-errors).

## Source references

* Python: [`sdks/python/pmxt/escrow.py`](https://github.com/pmxt-dev/pmxt/blob/main/sdks/python/pmxt/escrow.py)
* TypeScript: [`sdks/typescript/pmxt/escrow.ts`](https://github.com/pmxt-dev/pmxt/blob/main/sdks/typescript/pmxt/escrow.ts)
