> ## Documentation Index
> Fetch the complete documentation index at: https://docs.polynode.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Trading

> Place orders on Polymarket through the polynode SDK with local credential custody and gasless onboarding.

<Warning>
  **Polymarket V2 cutover: April 28, 2026 at \~11am UTC.** Set `exchangeVersion: "v2"` (TypeScript) / `ExchangeVersion::V2` (Rust) / `ExchangeVersion.V2` (Python) in your `TraderConfig` before cutover. V1 orders submitted after April 28 will be rejected with `order_version_mismatch`. Pass your V2 builder code (mint at [polymarket.com/settings?tab=builder](https://polymarket.com/settings?tab=builder)) via the `builder` field on each order. See the [V2 Migration Guide](/guides/v2-migration) for the full rundown.
</Warning>

<Info>
  **Deposit wallets supported.** Polymarket is rolling out deposit wallets as a new account type for new users. The SDK handles both Safe and deposit wallet users automatically — no code changes needed. See the [Deposit Wallets guide](/guides/deposit-wallets) for details.
</Info>

## Overview

The trading module lets you place and manage orders on Polymarket. One function call handles wallet setup, credential creation, approvals, and order placement. You don't need to understand Polymarket's wallet types, contract addresses, or signing protocols. The SDK auto-detects whether a user has a Safe proxy, deposit wallet, or no wallet at all, and handles each case transparently.

```bash theme={null}
npm install polynode-sdk viem better-sqlite3 @polymarket/clob-client @polymarket/builder-relayer-client @polymarket/builder-signing-sdk
```

V2 order placement requires `polynode-sdk >= 0.9.0`. Deposit wallet support (address derivation, wallet detection) requires `>= 0.10.0`. Deposit wallet order signing (POLY\_1271) requires `>= 0.10.5`.

<Note>
  Your project must use ESM imports (`import` syntax). Add `"type": "module"` to your `package.json`, or use `.mjs` file extensions.
</Note>

## Quick Start

Pick the path that matches your situation.

### I'm building a backend service (private keys on your server)

Generate wallets, manage them yourself. This is the simplest path.

```typescript theme={null}
import { PolyNodeTrader } from 'polynode-sdk';

const trader = new PolyNodeTrader({ polynodeKey: 'pn_live_...' });

// 1. Generate a wallet (or use an existing private key)
const { privateKey, address } = await PolyNodeTrader.generateWallet();
// SAVE THIS KEY. It is not stored anywhere. If you lose it, you lose access.

// 2. One call sets up everything: deploys a Safe, sets approvals, creates credentials.
//    Completely gasless. No MATIC needed. Takes ~10 seconds for a new wallet.
const status = await trader.ensureReady(privateKey);

// 3. Send USDC.e (Polygon) to the funder address. This is your trading wallet.
console.log('Send USDC.e here:', status.funderAddress);

// 4. Place an order
const result = await trader.order({
  tokenId: '...',  // Get token IDs from /v1/events/search or /v1/crypto/active
  side: 'BUY',
  price: 0.55,
  size: 100,
});

// 5. Cancel it
if (result.orderId) {
  await trader.cancelOrder(result.orderId);
}
```

`ensureReady()` is idempotent. Call it every time your service starts. If the wallet is already set up, it returns instantly from the local database.

### I'm building a platform with Privy (server wallets for each user)

Use `createPrivySigner` to trade with Privy-managed wallets. Each user gets their own wallet without touching private keys.

```typescript theme={null}
import { PolyNodeTrader, createPrivySigner, createPrivyClient } from 'polynode-sdk';

// Set up Privy (one client for your whole app)
// createPrivyClient handles the wallet API config for you.
const privy = createPrivyClient({
  appId: process.env.PRIVY_APP_ID!,
  appSecret: process.env.PRIVY_APP_SECRET!,
  authorizationKey: process.env.PRIVY_AUTHORIZATION_KEY!,
});

// Create a signer for a specific user's wallet
const signer = createPrivySigner(privy, user.privyWalletId, user.walletAddress);

const trader = new PolyNodeTrader({ polynodeKey: 'pn_live_...' });

// Set up the wallet — gasless, ~10 seconds for new users
const status = await trader.ensureReady(signer);

// Send USDC.e to status.funderAddress, then trade
const result = await trader.order({ tokenId: '...', side: 'BUY', price: 0.55, size: 100 });
```

**Privy setup requirements:**

1. Create a Privy app at [dashboard.privy.io](https://dashboard.privy.io)
2. Enable **Server wallets** under Wallet infrastructure
3. Generate an **Authorization keypair** (same section) — this is `PRIVY_AUTHORIZATION_KEY`
4. Install: `npm install @privy-io/server-auth` (the SDK imports it automatically via `createPrivyClient`)

If you enable Privy gas sponsorship in your dashboard, all on-chain operations (approvals, deploys) are free for your users.

### I already have a Polymarket account (exported key or existing credentials)

If you export your private key from Polymarket, the SDK auto-detects your wallet type.

```typescript theme={null}
const trader = new PolyNodeTrader({ polynodeKey: 'pn_live_...' });
const status = await trader.ensureReady('0xYOUR_EXPORTED_KEY');

// Your USDC.e is already in the right place. Start trading.
const balance = await trader.checkBalance();
console.log(`USDC.e: $${balance.usdc}`);
```

If you already have CLOB credentials (from Polymarket or Dome), import them directly:

```typescript theme={null}
trader.linkCredentials({
  wallet: '0xYOUR_EOA',
  apiKey: 'your-clob-api-key',
  apiSecret: 'your-clob-api-secret',
  apiPassphrase: 'your-clob-passphrase',
  signatureType: 2,
  funderAddress: '0xYOUR_SAFE',
});
await trader.linkWallet('0xYOUR_PRIVATE_KEY');
```

## Where to Send USDC.e

<Warning>
  Polymarket uses **USDC.e** (bridged USDC) on Polygon, NOT native USDC. These are different tokens.

  * **USDC.e (correct):** `0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174`
  * **Native USDC (wrong):** `0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359`

  If you accidentally send native USDC, the tokens are still in your wallet (recoverable), but they won't work as a trading balance on Polymarket.
</Warning>

After `ensureReady()`, send USDC.e to `status.funderAddress`. This is the address that holds your trading balance.

| Wallet type    | `funderAddress` is...                                                 |
| -------------- | --------------------------------------------------------------------- |
| Safe (default) | The Safe contract address (different from your private key's address) |
| EOA            | The wallet address itself                                             |

Do not send USDC.e to the EOA address if you're using a Safe wallet. It will sit there unused.

## Finding Markets

You need a `tokenId` to place orders. Here's how to find one:

```typescript theme={null}
import { PolyNode } from 'polynode-sdk';
const pn = new PolyNode({ apiKey: 'pn_live_...' });

// Search for markets
const results = await pn.searchEvents('Bitcoin', { limit: 5 });
for (const event of results.events) {
  for (const market of event.markets) {
    console.log(market.question, '→', market.tokenId);
  }
}
```

Or use the REST API directly: `GET /v1/events/search?q=bitcoin&limit=5` or `GET /v1/crypto/active` for live crypto markets.

## Configuration

```typescript theme={null}
const trader = new PolyNodeTrader({
  polynodeKey: 'pn_live_...',         // Your polynode API key
  dbPath: './my-trading.db',          // Local SQLite path (default: ./polynode-trading.db)
  cosignerUrl: 'https://trade.polynode.dev', // Relay URL (default)
  fallbackDirect: true,               // Fall back to direct CLOB if relay is down (default: true)
  rpcUrl: 'https://polygon-bor-rpc.publicnode.com', // RPC for on-chain reads (default)
  builderCredentials: {               // Optional: your own Polymarket builder credentials
    key: process.env.POLY_BUILDER_API_KEY!,
    secret: process.env.POLY_BUILDER_SECRET!,
    passphrase: process.env.POLY_BUILDER_PASSPHRASE!,
  },
  feeConfig: {                        // Optional: charge fees on orders
    feeBps: 50,                       // 0.5% fee (0 = no fee, default)
    affiliate: '0xPartner...',        // Partner wallet for revenue share
    affiliateShareBps: 3000,          // 30% to partner, 70% to treasury
  },
});
```

### Builder Attribution (Your Own Credentials)

By default, orders placed through polynode are attributed to polynode's builder profile on Polymarket. If you're running a platform and want volume credited to **your own** builder account, pass your credentials in `builderCredentials`.

1. Go to [polymarket.com/settings?tab=builder](https://polymarket.com/settings?tab=builder) and create a builder profile
2. Generate API credentials (key, secret, passphrase)
3. Pass them in `builderCredentials` when constructing the trader

```typescript theme={null}
const trader = new PolyNodeTrader({
  polynodeKey: 'pn_live_...',
  builderCredentials: {
    key: process.env.POLY_BUILDER_API_KEY!,
    secret: process.env.POLY_BUILDER_SECRET!,
    passphrase: process.env.POLY_BUILDER_PASSPHRASE!,
  },
});
```

When `builderCredentials` is set:

* All orders are attributed to your builder profile on the [Builder Leaderboard](https://builders.polymarket.com)
* Gasless Safe deployments and approvals use your builder account
* polynode never stores your builder credentials. They're sent per-request and used only for HMAC signing.

When `builderCredentials` is omitted, polynode's default builder credentials are used. Your orders still go through, you just don't get the builder attribution on your own profile.

## Polymarket V2 Exchange

The Polymarket V2 exchange uses PolyUSD as collateral instead of USDC.e directly. One config change enables V2 support. Always call `ensureReady` / `ensure_ready` before any V2 method — it deploys the Safe (if needed), sets V2 approvals, creates CLOB credentials, and refreshes the V2 CLOB's balance-allowance cache:

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { PolyNodeTrader } from 'polynode-sdk';

  const trader = new PolyNodeTrader({
    polynodeKey: 'pn_live_...',
    exchangeVersion: 'v2',
  });

  // Required before any wrap/order/cancel call:
  await trader.ensureReady('0xYOUR_EOA_PRIVATE_KEY');
  ```

  ```python Python theme={null}
  import asyncio
  from polynode.trading import PolyNodeTrader, TraderConfig, ExchangeVersion

  trader = PolyNodeTrader(TraderConfig(
      polynode_key="pn_live_...",
      exchange_version=ExchangeVersion.V2,
  ))

  # Required before any wrap/order/cancel call:
  await trader.ensure_ready("0xYOUR_EOA_PRIVATE_KEY")
  ```

  ```rust Rust theme={null}
  use polynode::trading::{PolyNodeTrader, TraderConfig, ExchangeVersion, PrivateKeySigner};

  let mut trader = PolyNodeTrader::new(TraderConfig {
      polynode_key: "pn_live_...".into(),
      exchange_version: ExchangeVersion::V2,
      ..Default::default()
  })?;

  // Required before any wrap/order/cancel call:
  let signer = PrivateKeySigner::from_hex("0xYOUR_EOA_PRIVATE_KEY")?;
  trader.ensure_ready(Box::new(signer), None).await?;
  ```
</CodeGroup>

Before placing orders on V2 markets, wrap USDC.e into PolyUSD. Amounts are in raw 6-decimal integers (`1_000_000` = \$1; TS uses `bigint`):

<CodeGroup>
  ```typescript TypeScript theme={null}
  // Wrap 100 USDC.e → PolyUSD
  await trader.wrapToPolyUsd(100_000_000n);

  const balanceRaw = await trader.getPolyUsdBalance();
  console.log(`PolyUSD: $${Number(balanceRaw) / 1e6}`);
  ```

  ```python Python theme={null}
  # Wrap 100 USDC.e → PolyUSD
  tx_hash = await trader.wrap_to_polyusd(100_000_000)

  polyusd_raw = trader.get_polyusd_balance()  # sync, returns int
  print(f"PolyUSD: ${polyusd_raw / 1e6:.6f}")
  ```

  ```rust Rust theme={null}
  // Wrap 100 USDC.e → PolyUSD
  let tx_hash = trader.wrap_to_polyusd(100_000_000).await?;

  let balance_raw = trader.get_polyusd_balance().await?;
  println!("PolyUSD: ${:.6}", balance_raw as f64 / 1e6);
  ```
</CodeGroup>

Pass your builder code via the `builder` field on each V2 order. Mint one at [polymarket.com/settings?tab=builder](https://polymarket.com/settings?tab=builder):

<CodeGroup>
  ```typescript TypeScript theme={null}
  await trader.order({
    tokenId: "...",
    side: "BUY",
    price: 0.42,
    size: 5,
    type: "GTC",
    builder: "0x5472fdd7...",  // bytes32 builder code
  });
  ```

  ```python Python theme={null}
  from polynode.trading import OrderParams

  await trader.order(OrderParams(
      token_id="...",
      side="BUY",
      price=0.42,
      size=5,
      type="GTC",
      builder="0x5472fdd7...",  # bytes32 builder code
  ))
  ```

  ```rust Rust theme={null}
  use polynode::trading::{OrderParams, OrderSide, OrderType};

  trader.order(OrderParams {
      token_id: "...".into(),
      side: OrderSide::Buy,
      price: 0.42,
      size: 5.0,
      order_type: OrderType::GTC,
      builder: Some("0x5472fdd7...".into()),
      ..Default::default()
  }).await?;
  ```
</CodeGroup>

On V2, `ensureReady()` / `ensure_ready()` also refreshes the CLOB's cached balance-allowance view after setting approvals. If you change approvals outside that flow, call `trader.refreshBalanceAllowance()` / `trader.refresh_balance_allowance()` before placing orders — otherwise the CLOB can reject with `"not enough balance / allowance"` until its cache catches up.

All other trading methods (`order()`, `cancelOrder()`, `split()`, `merge()`, etc.) work the same on V2 markets. The `order_type` accepts `"GTC"` (default — rest on book), `"GTD"` (with `expiration` unix seconds), `"FOK"` (fill-or-kill, fully match or cancel), or `"FAK"` (fill-and-kill, match what you can, cancel the rest). `size` is in shares of the outcome token, `price` is the per-share USD price between 0 and 1.

<Tip>
  See the [V2 Migration Guide](/guides/v2-migration) for full details on the Polymarket V2 exchange upgrade, and the [PolyUSD Guide](/guides/polyusd) for wrapping and unwrapping mechanics.
</Tip>

## Fee Escrow

Charge your platform's per-order fee via on-chain escrow. Fees are pulled before the order, distributed on fill, and refunded on cancel. This is **your** fee — independent of Polymarket's protocol fee and V2 builder rev share (which are charged separately by the CLOB). See the [Fee Escrow Guide](/guides/fee-escrow) for the full architecture and the three-fee breakdown.

```typescript theme={null}
const trader = new PolyNodeTrader({
  polynodeKey: 'pn_live_...',
  feeConfig: { feeBps: 50 },  // 0.5% platform fee on every order (yours; not Polymarket's)
});

const result = await trader.order({
  tokenId: '...',
  side: 'BUY',
  price: 0.55,
  size: 100,
});

console.log(result.feeEscrowTxHash); // on-chain pullFee TX
console.log(result.feeAmount);       // "0.0275" USDC

// Cancel → fee is automatically refunded
await trader.cancelOrder(result.orderId);
```

Set `feeBps: 0` or omit `feeConfig` to skip your platform fee entirely. Zero overhead, zero behavior change. Note: this only turns off **your** fee — Polymarket's protocol fee and V2 builder rev share are controlled independently by the CLOB.

## API Reference

### `PolyNodeTrader.generateWallet()`

Static async method. Generates a fresh wallet. **You must `await` this call.**

```typescript theme={null}
const { privateKey, address } = await PolyNodeTrader.generateWallet();
// IMPORTANT: Save privateKey securely. It is not stored anywhere by the SDK.
```

### `ensureReady(signer, opts?)`

One-call onboarding. Detects wallet type (Safe, proxy, or deposit wallet), deploys contracts if needed, sets all approvals, creates CLOB credentials.

**Signer types accepted:**

* `string` — hex private key (most common)
* `createPrivySigner(...)` — Privy server wallet
* ethers v5/v6 Signer
* viem WalletClient

**Returns:** `ReadyStatus` with `funderAddress` (where to send collateral), `signatureType`, `approvalsSet`, `credentials`, and `actions` (what it did).

**Wallet types detected automatically:**

| `signatureType`    | Value | Description                                           |
| ------------------ | ----- | ----------------------------------------------------- |
| `POLY_GNOSIS_SAFE` | 2     | Gnosis Safe proxy (most existing Polymarket accounts) |
| `POLY_PROXY`       | 1     | Legacy proxy wallet                                   |
| `POLY_1271`        | 3     | Deposit wallet (newer Polymarket accounts, V2 only)   |
| `EOA`              | 0     | Direct EOA signing                                    |

Calling it again on a ready wallet is instant (loads from local DB).

For deposit wallet users (`POLY_1271`), order signing uses the ERC-7739 TypedDataSign wrapper automatically. No code changes needed on your side — `order()` detects the wallet type from stored credentials and signs accordingly.

### `order(params)`

Place an order on Polymarket.

```typescript theme={null}
const result = await trader.order({
  tokenId: '...',  // Market token ID (from /v1/events/search)
  side: 'BUY',     // 'BUY' or 'SELL'
  price: 0.55,     // Limit price (0 to 1)
  size: 100,       // Number of shares
  type: 'GTC',     // 'GTC' | 'GTD' | 'FOK' | 'FAK' (default: GTC)
});

if (result.success) {
  console.log('Order placed:', result.orderId);
  // When feeConfig is set:
  console.log('Fee TX:', result.feeEscrowTxHash);  // on-chain escrow TX
  console.log('Fee:', result.feeAmount);            // USDC charged
} else {
  console.log('Failed:', result.error);
}
```

### `cancelOrder(orderId)` / `cancelAll(market?)`

Cancel a specific order or all orders.

### `split(params)`

Split USDC into YES + NO outcome tokens. Gasless for Safe wallets. Auto-detects neg-risk vs standard markets.

```typescript theme={null}
const result = await trader.split({
  conditionId: '0x895e01db...', // market condition ID
  amount: 100,                   // $100 USDC
});
// result: { success: true, txHash: '0x...' }
```

### `merge(params)`

Merge YES + NO outcome tokens back into USDC. Gasless for Safe wallets.

```typescript theme={null}
const result = await trader.merge({
  conditionId: '0x895e01db...',
  amount: 50,
});
```

### `convert(params)`

Convert NO positions on selected outcomes into USDC + YES on complementary outcomes. Only works on neg-risk multi-outcome markets. Gasless for Safe wallets.

```typescript theme={null}
const result = await trader.convert({
  marketId: '0xc7d902c4...', // negRiskMarketID
  outcomeIndices: [0, 1],     // which outcomes to convert
  amount: 100,                // $100 per outcome
});
```

See the [Position Management guide](/guides/position-management) for a full explanation of how conversions work and when to use each operation.

### `checkBalance(wallet?)`

Returns USDC.e and MATIC balance for the funder address.

### `checkApprovals(wallet?)`

Check if all required token approvals are set on-chain (6 Polymarket approvals + 1 fee escrow approval).

### `getOpenOrders(params?)`

Fetch open orders from the CLOB. Filters: `market`, `assetId`.

### `getOrderHistory(params?)`

Query local order history from SQLite. Filters: `limit`, `offset`, `tokenId`, `side`.

### `linkCredentials(opts)` / `linkWallet(signer)`

Import existing credentials or link a wallet manually.

### `exportWallet(wallet?)` / `exportAll()` / `importWallet(exported)`

Export and import wallet state for backup. Private keys are never included.

### `unlinkWallet(address?)` / `getLinkedWallets()`

Remove or list stored wallets.

### `close()`

Close the SQLite connection. Call this when shutting down.

## How It Works

1. SDK signs the order locally (EIP-712)
2. SDK sends to polynode's relay at `trade.polynode.dev`
3. Relay adds builder attribution and forwards to Polymarket's CLOB
4. Response returned to SDK, logged in local SQLite

If the relay is down and `fallbackDirect` is true, orders go directly to Polymarket. Your trading never stops.

## Wallet Model & Signing

Polymarket uses a **two-address model** for each trader:

* **EOA (signer)** — a plain private key. Signs every order and every on-chain intent. Holds no trading funds.
* **Safe (funder)** — a Gnosis Safe smart wallet whose address is deterministically derived from the EOA (CREATE2 via the Polymarket Safe Factory). This is where USDC.e / pUSD / CTF positions live, and it's the `maker` on every order.

`ensureReady(privateKey)` on a fresh EOA does all four things for you, **gasless** via the Polymarket relayer:

1. Derives the Safe address from the EOA via CREATE2
2. Deploys the Safe if it doesn't exist yet on-chain
3. Sets all required approvals (USDC.e/pUSD to exchanges + collateral adapters, CTF to exchanges)
4. Creates CLOB API credentials (L2 HMAC) by signing an EIP-712 `ClobAuth` message from the EOA

After this, the SDK stores the CLOB API key, secret, and passphrase in its local SQLite DB keyed by the EOA address. You never send them to polynode.

### How orders are signed

Every V2 order struct includes `maker` (the Safe address) and `signer` (the EOA address). The EOA signs the EIP-712 hash; the CLOB verifies the signature against `signer` and trusts that the `signer` is a 1-of-1 owner of the `maker` Safe (it is, because the Safe's sole owner is the EOA by construction). At settlement time, the V2 exchange calls the Safe's `execTransaction` with your signed payload as authorization.

### How wrap/unwrap, approvals, and split/merge are signed

For any on-chain action that needs to move funds out of the Safe (`wrapToPolyUsd`, `unwrapFromPolyUsd`, `setApprovals`, `split`, `merge`, `convert`), the SDK:

1. Builds the raw calldata for the target contract call
2. Wraps it in a Safe `execTransaction` EIP-712 payload with the current Safe nonce
3. Has your EOA `personal_sign` that payload (via eth\_account / alloy / ethers)
4. POSTs the signed payload to `https://relayer-v2.polymarket.com/submit` with your **builder HMAC headers**
5. Polls the relayer until the tx is mined on Polygon

The relayer pays the gas. You never need MATIC. This is why `wrapToPolyUsd()` / `unwrapFromPolyUsd()` require `builderCredentials` in `TraderConfig` for Safe wallets — the HMAC authenticates your submit to the relayer.

### Linking credentials vs ensureReady

Three ways to get a wallet ready to trade, depending on what you already have:

| Situation                                                                       | Call                                                                                                                                                           | What it does                                                                                                                                                                                                                             |
| ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Fresh EOA (no Safe yet)                                                         | `trader.ensureReady(privateKey)`                                                                                                                               | Derives Safe, deploys, approves, creates CLOB creds, stores in local DB                                                                                                                                                                  |
| Existing EOA + Safe already set up (you re-start your service)                  | `trader.ensureReady(privateKey)`                                                                                                                               | No-op if the local DB already has creds; otherwise re-derives and re-fetches                                                                                                                                                             |
| You already have CLOB creds from somewhere else (DB export, Polymarket account) | `trader.linkCredentials({ wallet, apiKey, apiSecret, apiPassphrase, signatureType, funderAddress })` then `trader.linkWallet(privateKey)` to attach the signer | Imports the creds into the local DB and attaches the signer for order signing. Skips approvals + deploys entirely.                                                                                                                       |
| Multi-user platform (one SDK instance, many users)                              | `trader.linkCredentials(userA)` → `trader.linkWallet(userA.pk)` → trade; then switch with `trader.linkWallet(userB.pk)`                                        | Each `linkWallet` call swaps the active signer. `linkCredentials` writes into the local DB so you can return to a user later by calling `linkWallet` again with their key. Consider using one `PolyNodeTrader` per user for concurrency. |

### Multi-user / server wallet patterns

For a backend that trades on behalf of many users:

* **Privy / server-managed wallets:** use `createPrivySigner(privy, walletId, address)` instead of passing a raw private key to `ensureReady`. Same onboarding flow, but Privy holds the key and signs remotely.
* **Your own KMS:** implement the `RouterSigner` interface (or `TradingSigner` in Rust / a signer callable in Python). The SDK only needs two operations from you: `getAddress()` and `signTypedData(payload)`. Everything else is local.
* **Per-user DB file:** pass a different `dbPath` per user to keep their credentials isolated on disk. Or use a shared DB and call `linkWallet(userPk)` to swap active signers.

## Credential Custody

Credentials are stored in a local SQLite database. The SDK never sends your private key or CLOB credentials to polynode's servers. Back up with `exportWallet()`.

## Migrating from Dome

The polynode trading module is a drop-in replacement for Dome. See the [Dome Migration guide](/dome-migration#order-placement) for a line-by-line comparison.
