Skip to main content

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.
npm install polynode-sdk@0.5.5 viem better-sqlite3 @polymarket/clob-client @polymarket/builder-relayer-client @polymarket/builder-signing-sdk
Your project must use ESM imports (import syntax). Add "type": "module" to your package.json, or use .mjs file extensions.

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

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.
After ensureReady(), send USDC.e to status.funderAddress. This is the address that holds your trading balance.
Wallet typefunderAddress is…
Safe (default)The Safe contract address (different from your private key’s address)
EOAThe 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:
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

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!,
  },
});

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 and create a builder profile
  2. Generate API credentials (key, secret, passphrase)
  3. Pass them in builderCredentials when constructing the trader
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
  • 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.

API Reference

PolyNodeTrader.generateWallet()

Static async method. Generates a fresh wallet. You must await this call.
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, 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 USDC.e), signatureType, approvalsSet, credentials, and actions (what it did). Calling it again on a ready wallet is instant (loads from local DB).

order(params)

Place an order on Polymarket.
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);
} 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.
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.
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.
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 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 6 required token approvals are set on-chain.

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.

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 for a line-by-line comparison.