PMWallets
中文

SDKs and the copy-trading bot

Official open-source (MIT) clients for Node.js and Python, and a ready-to-run Polymarket copy-trading bot built on them.

The SDKs wrap every endpoint in the API documentation and handle the WebSocket for you: reconnect and keep-alive, missed frames detected by session/seq and replayed from the last fill delivered, de-duplication by eventId, and a persisted cursor so a restart resumes where it stopped. Delivery is at least once: the cursor is saved after your handler returns, so a crash in between replays that fill on restart — make your handler idempotent on eventId. You need an API key — create one on the keys page.

Where to deploy: Ireland (AWS eu-west-1). Our servers are in the United Kingdom, so the fills are pushed from there, and Dublin is next door. For placing orders, the server’s region matters too: Polymarket’s API only closes positions from the UK, the US and several EU countries, and accepts orders from Ireland — the copy-trading bot has traded live from there. This is advice on where to put a server, not a way around where you are: whether you may trade is decided by your own location and Polymarket’s terms, which forbid using a VPN or similar to get around its geographic restrictions. Check its current rules before you go live.

Node.js SDK

pmwallets-node on GitHub, pmwallets on npm. Node.js ≥ 20, TypeScript types included.

shell
npm install pmwallets
typescript
import { PmwClient, FillStream, FileStateStore, verifyWebhook } from 'pmwallets';

const pmw = new PmwClient({ apiKey: process.env.PMW_API_KEY! });

// who is making money on Polymarket, and how
const board = await pmw.leaderboard({ minWinLo: 0.55, minEligible: 30, style: 'taker', status: 'active', limit: 50 });

// follow one (billed per entity per hour; 402 = balance too low, 409 = dormant)
await pmw.subscribe({ entityId: board.rows[0].entityId, channels: ['ws'] });

// every fill of every entity you follow
const stream = new FillStream({
  client: pmw,
  store: new FileStateStore('./stream.json'),    // a restart resumes from the last saved cursor
  onFill: async (fill, { source }) => console.log(source, fill.side, fill.price, fill.tokenId),
  onEvent: (e) => e.type === 'replaced' && console.warn('another connection took the stream'),
});
await stream.start();
What it does
PmwClientleaderboard, entities, address unlocks, subscriptions, fills replay (fills, fillsSince), trade-history exports
FillStreamWebSocket with reconnect and keep-alive; gap detection and replay; de-duplication; persisted cursor
verifyWebhook(rawBody, signature, secret)checks x-pmw-signature (HMAC-SHA256 of the raw body)

Python SDK

pmwallets-python on GitHub, pmwallets on PyPI. Python ≥ 3.10, sync and asyncio clients.

shell
pip install pmwallets
python
import asyncio
from pmwallets import AsyncClient, Client, FillStream, FileStateStore

# synchronous REST
with Client(api_key="pmw_...") as pmw:
    board = pmw.leaderboard(minWinLo=0.55, minEligible=30, style="taker", status="active", limit=50)
    pmw.subscribe(board["rows"][0]["entityId"], channels=["ws"])   # billed per entity per hour

# every fill of every entity you follow
async def main():
    async with AsyncClient(api_key="pmw_...") as pmw:
        stream = FillStream(
            client=pmw,
            store=FileStateStore("stream.json"),     # a restart resumes from the last saved cursor
            on_fill=lambda fill, meta: print(meta.source, fill["side"], fill["price"], fill["tokenId"]),
        )
        await stream.run()

asyncio.run(main())
What it does
Client / AsyncClientleaderboard, entities, address unlocks, subscriptions, fills replay (fills, fills_since), trade-history exports
FillStreamasyncio WebSocket with reconnect and keep-alive; gap detection and replay; de-duplication; persisted cursor
verify_webhook(raw_body, signature, secret)checks x-pmw-signature (HMAC-SHA256 of the raw body)

Polymarket copy-trading bot

Built on the SDKs. You pick the traders on the board and subscribe; the bot receives each of their fills over the WebSocket and mirrors it on the Polymarket CLOB with your own account, inside the limits you set. Your keys never leave your machine — PMWallets never sees them and never places orders. It starts in dry-run: every decision is logged, nothing is traded.

Node.js

polymarket-copy-trading-bot · pmwallets-copytrade on npm

shell
# Node.js ≥ 20
npm install -g pmwallets-copytrade
pmwallets-copytrade init            # writes config.yaml
export PMW_API_KEY=pmw_...          # https://pmwallets.com/keys
pmwallets-copytrade run             # dry-run: logs every decision, trades nothing

Python

polymarket-copy-trading-bot-python · pmwallets-copytrade on PyPI

shell
# Python ≥ 3.10
pip install pmwallets-copytrade
pmwallets-copytrade init            # writes config.yaml
export PMW_API_KEY=pmw_...          # https://pmwallets.com/keys
pmwallets-copytrade run             # dry-run: logs every decision, trades nothing

Going live

When the dry-run log looks right, switch mode in config.yaml; the repository README covers setting up the Polymarket account.

yaml
mode: live
polymarket:
  privateKey: ${POLY_PRIVATE_KEY}        # signs your orders
  signatureType: 3
  funderAddress: ${POLY_FUNDER_ADDRESS}  # your Polymarket profile address (holds the USDC)

Which traders are worth following and how the bot decides: the copy-trading guide.

Where to run it

One stream per account: an API-key connection takes priority over the pmwallets.com feed page, and between two API connections the newest wins — so run one consumer per account. PMWallets’ servers are in the United Kingdom; for a trader who is eligible to use Polymarket’s API, a consumer that also places orders should run from Ireland (AWS eu-west-1), because Polymarket’s API does not accept orders from the UK, the US and several EU countries. The server’s region is not a way around your own eligibility.

API documentation · Measured push latency · Questions we get asked