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

# Rust — Trading

## Trading Module

<Warning>
  **Polymarket V2 is live.** Set `exchange_version: ExchangeVersion::V2` in your `TraderConfig`. V1 orders submitted after April 28, 2026 are rejected with `order_version_mismatch`. The Rust SDK defaults V2 order attribution to PolyNode's builder code; pass your own code via `TraderConfig.builder_code` or `OrderParams.builder`.
</Warning>

<Note>Requires the `trading` feature flag: `polynode = { version = "0.13.11", features = ["trading"] }`</Note>

<Info>
  **Deposit wallets supported.** The SDK auto-detects Safe proxy and deposit wallet users. No code changes needed for existing integrations. See the [Deposit Wallets guide](/guides/deposit-wallets).
</Info>

Place orders on Polymarket with local credential custody and builder attribution. All signing happens locally. Private keys never leave your machine. Supports both the current exchange and the [Polymarket V2 exchange](/guides/v2-migration). See also: [PolyUSD Guide](/guides/polyusd) for V2 collateral wrapping.

### Credential Model

The SDK separates three credentials so users can choose the easiest path without mixing ownership boundaries:

| Credential        | Used for                                                            | Default                                                      |
| ----------------- | ------------------------------------------------------------------- | ------------------------------------------------------------ |
| User CLOB API key | Authenticates `/order`, cancel, open-order, balance-allowance calls | Created or loaded by `ensure_ready()`                        |
| V2 builder code   | Public bytes32 attribution signed into V2 orders                    | PolyNode builder code via `TraderConfig.builder_code`        |
| Relayer auth      | Gasless Safe/proxy/deposit-wallet `/submit` calls                   | PolyNode managed relayer via `polynode_key` + `cosigner_url` |

Orders are always submitted with the user's CLOB API credentials. PolyNode's builder code and relayer path do not make PolyNode the owner of the order.

### Generate a Wallet

```rust,no_run theme={null}
use polynode::trading::PolyNodeTrader;

# fn example() {
let (private_key, address) = PolyNodeTrader::generate_wallet();
println!("address: {}", address);
println!("key: {}", private_key);
// Fund this address with USDC and POL on Polygon before trading
# }
```

### Onboarding

`ensure_ready()` handles the full onboarding flow in one call: derives addresses, checks Safe deployment and approvals, creates or loads CLOB credentials, and stores everything in a local SQLite database.

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

#[tokio::main]
async fn main() -> polynode::Result<()> {
    let mut trader = PolyNodeTrader::new(TraderConfig {
        polynode_key: "pn_live_YOUR_KEY".into(),
        db_path: "./my-trading.db".into(),
        exchange_version: ExchangeVersion::V2,
        ..Default::default()
    })?;

    let signer = PrivateKeySigner::from_hex("0xdeadbeef...")?;
    let status = trader.ensure_ready(Box::new(signer), None).await?;

    println!("wallet: {}", status.wallet);
    println!("funder: {}", status.funder_address);
    println!("safe deployed: {}", status.safe_deployed);
    println!("approvals set: {}", status.approvals_set);
    println!("actions taken: {:?}", status.actions);

    trader.close();
    Ok(())
}
```

`ReadyStatus.actions` is the easiest way to inspect what happened. Common values:

| Action                                       | Meaning                                                                                     |
| -------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `credentials_created` / `credentials_loaded` | User CLOB credentials were created or reused locally                                        |
| `relayer_key_provisioned`                    | The SDK signed a SIWE message and cached a per-user relayer key through PolyNode's cosigner |
| `relayer_key_skipped: ...`                   | Non-fatal; future relayer calls fall back to builder auth                                   |
| `safe_approvals_submitted: <tx>`             | Safe/proxy approvals were submitted through the configured relayer path                     |
| `deposit_wallet_create_submitted: <id>`      | Deposit wallet creation was submitted through the configured relayer path                   |
| `deposit_wallet_approval_submitted: <id>`    | Deposit wallet approvals were submitted through the configured relayer path                 |

### Place an Order

```rust,no_run theme={null}
use polynode::trading::{PolyNodeTrader, TraderConfig, PrivateKeySigner, OrderParams, OrderSide, OrderType};

# async fn example() -> polynode::Result<()> {
let mut trader = PolyNodeTrader::new(TraderConfig {
    polynode_key: "pn_live_...".into(),
    ..Default::default()
})?;

let signer = PrivateKeySigner::from_hex("0x...")?;
trader.ensure_ready(Box::new(signer), None).await?;

let result = trader.order(OrderParams {
    token_id: "51037625779056581606819614184446816710505006861008496087735536016411882582167".into(),
    side: OrderSide::Buy,
    price: 0.55,
    size: 100.0,
    order_type: OrderType::GTC,
    // Optional per-order override. If omitted, TraderConfig.builder_code is used.
    // builder: Some("0xYourBuilderCodeBytes32".into()),
    ..Default::default()
}).await?;

if result.success {
    println!("order placed: {:?}", result.order_id);
} else {
    println!("order failed: {:?}", result.error);
}

trader.close();
# Ok(())
# }
```

### Order Types

| Type  | Behavior                                                 |
| ----- | -------------------------------------------------------- |
| `GTC` | Good til canceled (default)                              |
| `GTD` | Good til date (set `expiration` to a Unix timestamp)     |
| `FOK` | Fill or kill, entire order fills or nothing              |
| `FAK` | Fill and kill, partial fills allowed, remainder canceled |

### Cancel Orders

```rust,no_run theme={null}
# async fn example(trader: &mut polynode::trading::PolyNodeTrader) -> polynode::Result<()> {
// Cancel a specific order
let result = trader.cancel_order("order_id_here").await?;
println!("canceled: {:?}", result.canceled);

// Cancel all orders
let result = trader.cancel_all(None).await?;

// Cancel all orders for a specific market
let result = trader.cancel_all(Some("condition_id")).await?;
# Ok(())
# }
```

### Query Open Orders

```rust,no_run theme={null}
# async fn example(trader: &mut polynode::trading::PolyNodeTrader) -> polynode::Result<()> {
// All open orders
let orders = trader.get_open_orders(None).await?;
for o in &orders {
    println!("{}: {} {} @ {} (matched: {}/{})",
        o.id, o.side, o.asset_id, o.price, o.size_matched, o.original_size);
}

// Open orders for a specific market
let orders = trader.get_open_orders(Some("condition_id")).await?;
# Ok(())
# }
```

### Pre-Trade Checks

```rust,no_run theme={null}
# async fn example(trader: &mut polynode::trading::PolyNodeTrader) -> polynode::Result<()> {
// Check token approvals
let approvals = trader.check_approvals(None).await?;
println!("all approved: {}", approvals.all_approved);

// Check USDC and POL balances
let balance = trader.check_balance(None).await?;
println!("USDC: {}, POL: {}", balance.usdc, balance.matic);
# Ok(())
# }
```

### Local Order History

All orders are logged locally in SQLite:

```rust,no_run theme={null}
use polynode::trading::HistoryParams;

# fn example(trader: &mut polynode::trading::PolyNodeTrader) -> polynode::Result<()> {
let history = trader.get_order_history(Some(HistoryParams {
    limit: Some(50),
    offset: None,
    token_id: None,
    side: None,
}))?;
for row in &history {
    println!("{}: {} {} @ {} — {}", row.token_id, row.side, row.size, row.price, row.status);
}
# Ok(())
# }
```

### Wallet Export and Import

Back up and restore wallet credentials:

```rust,no_run theme={null}
# fn example(trader: &mut polynode::trading::PolyNodeTrader) -> polynode::Result<()> {
// Export
let exported = trader.export_wallet(None)?;
if let Some(ref data) = exported {
    let json = serde_json::to_string_pretty(data).unwrap();
    std::fs::write("wallet-backup.json", json).unwrap();
}

// Import
let json = std::fs::read_to_string("wallet-backup.json").unwrap();
let data: polynode::trading::WalletExport = serde_json::from_str(&json).unwrap();
trader.import_wallet(data)?;
# Ok(())
# }
```

### Polymarket V2 Exchange

The SDK supports the Polymarket V2 exchange system. Set `exchange_version` in your config to target V2. Defaults to V1 — no existing code is affected.

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

# async fn example() -> polynode::Result<()> {
let mut trader = PolyNodeTrader::new(TraderConfig {
    polynode_key: "pn_live_...".into(),
    exchange_version: ExchangeVersion::V2,
    relayer_mode: RelayerMode::Auto, // default
    ..Default::default()
})?;
# Ok(())
# }
```

### Builder Attribution and Relayer Modes

Default managed mode:

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

# fn example() -> polynode::Result<()> {
let trader = PolyNodeTrader::new(TraderConfig {
    polynode_key: "pn_live_...".into(),
    exchange_version: ExchangeVersion::V2,
    // builder_code defaults to PolyNode's public V2 builder code.
    // relayer_mode defaults to Auto: managed relayer when polynode_key is set.
    ..Default::default()
})?;
# Ok(())
# }
```

Bring your own builder code but still use PolyNode's managed relayer:

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

# fn example() -> polynode::Result<()> {
let trader = PolyNodeTrader::new(TraderConfig {
    polynode_key: "pn_live_...".into(),
    exchange_version: ExchangeVersion::V2,
    builder_code: Some("0xYourBuilderCodeBytes32".into()),
    ..Default::default()
})?;
# Ok(())
# }
```

Bring your own builder credentials for direct Polymarket relayer auth:

```rust,no_run theme={null}
use polynode::trading::{
    BuilderCredentials, ExchangeVersion, PolyNodeTrader, RelayerMode, TraderConfig,
};

# fn example() -> polynode::Result<()> {
let trader = PolyNodeTrader::new(TraderConfig {
    exchange_version: ExchangeVersion::V2,
    relayer_mode: RelayerMode::BuilderCredentials,
    builder_code: Some("0xYourBuilderCodeBytes32".into()),
    builder_credentials: Some(BuilderCredentials {
        key: "builder-api-key".into(),
        secret: "builder-secret-base64".into(),
        passphrase: "builder-passphrase".into(),
    }),
    ..Default::default()
})?;
# Ok(())
# }
```

`RelayerMode::Auto` chooses the least-friction path:

| Config                                               | Relayer submit behavior                                                                                                         |
| ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `polynode_key` + `cosigner_url`                      | Submit through PolyNode `/relay`; per-user relayer key is preferred                                                             |
| No `polynode_key`, but `builder_credentials` present | Submit directly to Polymarket relayer with those credentials                                                                    |
| `relayer_mode: RelayerMode::DirectRpc`               | Safe/proxy calls sign `execTransaction` and broadcast through `rpc_url`; deposit-wallet factory calls still need a relayer mode |
| Neither configured                                   | Smart-wallet deploy/approve/wrap calls return a clear configuration error                                                       |

Direct RPC mode for Safe/proxy calls:

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

# fn example() -> polynode::Result<()> {
let trader = PolyNodeTrader::new(TraderConfig {
    exchange_version: ExchangeVersion::V2,
    relayer_mode: RelayerMode::DirectRpc,
    rpc_url: "https://polygon-bor-rpc.publicnode.com".into(),
    ..Default::default()
})?;
# Ok(())
# }
```

`DirectRpc` pays gas from the signer EOA and requires a signer with `TradingSigner::sign_hash` support, such as `PrivateKeySigner`; the built-in `PrivySigner` does not currently sign raw transactions. It does not apply to deposit-wallet create/approval envelopes, which route through Polymarket's deposit-wallet factory relayer API.

V2 uses PolyUSD as collateral instead of USDC.e. The SDK provides helper methods for wrapping and balance checking:

```rust,no_run theme={null}
# async fn example(trader: &mut polynode::trading::PolyNodeTrader) -> polynode::Result<()> {
// Wrap USDC.e → PolyUSD (amount in raw units, 6 decimals)
let tx_hash = trader.wrap_to_polyusd(1_000_000).await?; // 1 USDC

// Unwrap PolyUSD → USDC.e
let tx_hash = trader.unwrap_from_polyusd(1_000_000).await?;

// Check balances
let polyusd = trader.get_polyusd_balance().await?;
let usdce = trader.get_usdce_balance().await?;
# Ok(())
# }
```

Order placement, cancellation, and all other trading methods work identically on V2. See the [V2 Migration Guide](/guides/v2-migration) and [PolyUSD Guide](/guides/polyusd) for full details.

### Custom Signers

Implement the `TradingSigner` trait for HSM, KMS, or other signing backends:

```rust,no_run theme={null}
use polynode::trading::{TradingSigner, Address, async_trait};

struct MyHsmSigner { /* ... */ }

#[async_trait]
impl TradingSigner for MyHsmSigner {
    fn address(&self) -> Address {
        // Return the EOA address
        todo!()
    }

    async fn sign_typed_data(
        &self,
        payload: &polynode::trading::Eip712Payload,
    ) -> polynode::Result<Vec<u8>> {
        // Sign EIP-712 typed data, return 65-byte signature
        todo!()
    }

    async fn sign_message(&self, message: &[u8]) -> polynode::Result<Vec<u8>> {
        // Sign raw message (personal_sign), return 65-byte signature
        todo!()
    }
}
```

### Fee Escrow

Charge per-order fees with on-chain escrow. Fees are pulled before the order, distributed on fill, and refunded on cancel. See the [Fee Escrow Guide](/guides/fee-escrow) for the full architecture and security model.

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

let mut trader = PolyNodeTrader::new(TraderConfig {
    polynode_key: "pn_live_...".into(),
    fee_config: Some(FeeConfig {
        fee_bps: 50,  // 0.5% fee on every order
        affiliate: Some("0xYourWallet...".into()),  // REQUIRED: your wallet receives the fee
        affiliate_share_bps: None,  // default: you keep 100%
    }),
    ..Default::default()
})?;

let result = trader.order(OrderParams {
    token_id: "...".into(),
    side: OrderSide::Buy,
    price: 0.55,
    size: 100.0,
    order_type: OrderType::GTC,
    // fee_config: None uses the trader's global fee_config (via Default)
    ..Default::default()
}).await?;

println!("Fee TX: {:?}", result.fee_escrow_tx_hash);
println!("Fee: {:?} USDC", result.fee_amount);

// Cancel → fee is automatically refunded
trader.cancel_order("order-id").await?;
```

Set `fee_bps: 0` or omit `fee_config` to skip the escrow entirely. Per-order overrides via `OrderParams.fee_config`.

### Privy Signer

<Note>Requires the `privy` feature flag: `polynode = { version = "0.13.11", features = ["trading", "privy"] }`</Note>

Sign orders with a Privy server-side wallet. No private key needed on your machine. All signing happens via Privy's HTTP API.

```rust,no_run theme={null}
use polynode::trading::{PolyNodeTrader, TraderConfig};
use polynode::trading::privy::{PrivyConfig, PrivySigner};

#[tokio::main]
async fn main() -> polynode::Result<()> {
    let config = PrivyConfig {
        app_id: "your-privy-app-id".into(),
        app_secret: "your-privy-app-secret".into(),
        authorization_key: "wallet-auth:your-authorization-key".into(),
    };

    // Or load from PRIVY_APP_ID, PRIVY_APP_SECRET, PRIVY_AUTHORIZATION_KEY env vars:
    // let config = PrivyConfig::from_env()?;

    let signer = PrivySigner::new(
        config,
        "privy-wallet-id".into(),
        "0xYourWalletAddress".parse().unwrap(),
    );

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

    let status = trader.ensure_ready(Box::new(signer), None).await?;
    println!("ready: {}", status.credentials_stored);

    trader.close();
    Ok(())
}
```

The `PrivySigner` implements `TradingSigner`, so it works with `ensure_ready()`, `order()`, and all other trading methods. Get your Privy credentials from the [Privy Dashboard](https://dashboard.privy.io).

### TraderConfig

```rust,no_run theme={null}
use polynode::trading::{TraderConfig, SignatureType, ExchangeVersion, RelayerMode};

# fn example() {
let config = TraderConfig {
    polynode_key: "pn_live_...".into(),
    db_path: "./my-trading.db".into(),          // local SQLite for credentials + history
    cosigner_url: "https://trade.polynode.dev".into(), // default
    fallback_direct: true,                       // submit directly if cosigner is down
    default_signature_type: SignatureType::PolyGnosisSafe, // default
    rpc_url: "https://polygon-bor-rpc.publicnode.com".into(),  // default; for on-chain reads
    builder_code: Some("0xYourBuilderCodeBytes32".into()), // V2 default order attribution
    relayer_mode: RelayerMode::Auto,              // managed when polynode_key is set
    exchange_version: ExchangeVersion::V1,       // default; set to V2 for the Polymarket V2 exchange
    builder_credentials: None,                    // optional direct relayer fallback/override
    fee_config: None,                             // optional fee escrow config
};
# }
```
