基于 PolyEdge SDK 构建 Polymarket 跟单机器人
本指南介绍如何使用 PolyEdge TypeScript SDK(polyedge)捕获出块前内存池Polymarket交易,并配合 Polymarket 官方 SDK(@polymarket/client)实现毫秒级自动化跟单。
[PolyEdge 内存池推流] ──(tx pre-block)──> [匹配目标交易员] ──> [Polymarket 下单接口]
1. 项目初始化与依赖
创建项目并安装依赖:
mkdir poly-copy-bot && cd poly-copy-bot
npm init -y
npm install @polymarket/client polyedge dotenv viem
npm install -D typescript tsx @types/node
npx tsc --init
在项目根目录下配置 .env 文件:
# PolyEdge API Key
POLYEDGE_API_KEY=pe_live_xxxxxxxxxxxxxxxx
# Polymarket 账户配置
POLYMARKET_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
POLYMARKET_DEPOSIT_WALLET=0xYourPolymarketDepositWalletAddress
# 监控目标交易员地址(小写,逗号分隔)
TARGET_ADDRESSES=0x34dd4a4b70eaf79a17878f7938263c801d4dfd83,0x4bf459194025b2ca11a2db2deb9ef2a08c467021
# 仓位模式:FIXED (固定金额) 或 PROPORTIONAL (按比例)
COPY_MODE=FIXED
# FIXED 模式:单笔跟单金额 (USD)
FIXED_AMOUNT_USD=50
# PROPORTIONAL 模式:比例系数 (0.1 = 复制 10%) 与上限 (USD)
COPY_RATIO=0.1
MAX_SPEND_USD=200
# 订单类型:FAK (立即成交剩余撤销) 或 FOK (全额成交或完全撤销)
ORDER_TYPE=FAK
# 滑点上限 (0.03 = 3%)
SLIPPAGE_TOLERANCE_PCT=0.03
2. 订单类型与仓位模式
订单执行类型 (OrderType)
OrderType.FAK(Fill-And-Kill / 推荐):市价单立即与盘口撮合,无法立即成交的部分自动撤销,不挂单入册。OrderType.FOK(Fill-Or-Kill):整笔订单必须全额立即成交,否则全部撤销。
仓位计算模式
- 固定金额 (
FIXED):每笔信号均按固定美元金额(如$50)下单。 - 等比复制 (
PROPORTIONAL):按目标交易员实际成交额的固定比例(如10%)下单,并通过MAX_SPEND_USD设限。
3. 数据模型定义 (types.ts)
LiveTransaction 等底层协议类型由 polyedge 导出,此处仅需定义跟单信号与本地配置接口:
// types.ts
import type { OrderType } from "@polymarket/client";
export interface CopySignal {
txHash: string;
targetAddress: string;
role: "taker" | "maker";
assetId: string;
side: "BUY" | "SELL";
price: number;
shares: number;
notionalUsd: number;
marketQuestion: string;
}
export interface BotConfig {
polymarketPrivateKey: `0x${string}`;
polymarketDepositWallet: `0x${string}`;
copyMode: "FIXED" | "PROPORTIONAL";
fixedAmountUsd: number;
copyRatio: number;
maxSpendUsd: number;
orderType: OrderType.FAK | OrderType.FOK;
slippageTolerancePct: number;
}
4. 初始化与连接推流 (stream.ts)
使用 polyedge SDK 创建监控特定地址的专属推流并建立连接。SDK 内置 45 秒看门狗与断线自动恢复机制:
// stream.ts
import { PolyEdgeClient, type PolyEdgeStream } from "polyedge";
export async function createAndConnectStream(
apiKey: string,
targetAddresses: string[]
): Promise<PolyEdgeStream> {
const client = new PolyEdgeClient({ apiKey });
// 1. 创建过滤目标地址的交易流配置
const streamConfig = await client.streams.create({
name: `CopyBot-${Date.now()}`,
addresses: targetAddresses,
});
if (!streamConfig.id) {
throw new Error("创建交易流失败:未返回流 ID");
}
console.log(`[PolyEdge] 交易流创建成功: ${streamConfig.id}`);
// 2. 连接 SSE 实时推流
return client.streams.connect(streamConfig.id, {
heartbeatTimeoutMs: 45_000,
});
}
5. 交易归因与信号提取 (matcher.ts)
Polymarket 预测市场采用二元代币结算架构,每个市场包含两个 Outcome Token。token_ids_index 严格取 0 或 1,分别对应两个代币的 Asset ID。
逐一检查该笔交易的主动吃单方(taker)与被动挂单方(makers),确认目标交易员角色并计算成交价:
// matcher.ts
import type { LiveTransaction } from "polyedge";
import type { CopySignal } from "./types";
export function extractCopySignals(
tx: LiveTransaction,
targetAddresses: Set<string>
): CopySignal[] {
const signals: CopySignal[] = [];
const market = tx.market;
if (!market || !market.token_ids || market.token_ids.length < 2) {
return signals;
}
const resolveAssetId = (index?: number): string | null => {
return (index === 0 || index === 1) && market.token_ids ? market.token_ids[index] : null;
};
// 1. 检查 Taker (主动吃单)
if (tx.taker?.user?.address) {
const takerAddr = tx.taker.user.address.toLowerCase();
if (targetAddresses.has(takerAddr)) {
const assetId = resolveAssetId(tx.taker.token_ids_index);
const shares = Number(tx.taker.shares) / 1e6;
const notionalUsd = Number(tx.taker.usdc) / 1e6;
const price = shares > 0 ? notionalUsd / shares : 0;
if (assetId && shares > 0 && price > 0 && tx.taker.side && tx.tx_hash) {
signals.push({
txHash: tx.tx_hash,
targetAddress: takerAddr,
role: "taker",
assetId,
side: tx.taker.side,
price,
shares,
notionalUsd,
marketQuestion: market.question || "",
});
}
}
}
// 2. 检查 Makers (被动挂单撮合成交)
if (Array.isArray(tx.makers)) {
for (const maker of tx.makers) {
if (!maker.user?.address) continue;
const makerAddr = maker.user.address.toLowerCase();
if (targetAddresses.has(makerAddr)) {
const assetId = resolveAssetId(maker.token_ids_index);
const shares = Number(maker.shares) / 1e6;
const notionalUsd = Number(maker.usdc) / 1e6;
const price = shares > 0 ? notionalUsd / shares : 0;
if (assetId && shares > 0 && price > 0 && maker.side && tx.tx_hash) {
signals.push({
txHash: tx.tx_hash,
targetAddress: makerAddr,
role: "maker",
assetId,
side: maker.side,
price,
shares,
notionalUsd,
marketQuestion: market.question || "",
});
}
}
}
}
return signals;
}
6. 下单执行器 (executor.ts)
使用 Polymarket 官方 SDK(@polymarket/client)计算仓位与滑点保护边界,提交市价单:
// executor.ts
import { Client, Side as OrderSide, OrderType } from "@polymarket/client";
import { privateKeyToAccount } from "viem/accounts";
import type { BotConfig, CopySignal } from "./types";
const CLOB_API_URL = "https://clob.polymarket.com";
const POLYGON_CHAIN_ID = 137;
export async function createExecutor(config: BotConfig) {
const account = privateKeyToAccount(config.polymarketPrivateKey);
const client = new Client(
CLOB_API_URL,
POLYGON_CHAIN_ID,
account,
config.polymarketDepositWallet
);
async function executeOrder(signal: CopySignal) {
const orderUsd = config.copyMode === "FIXED"
? config.fixedAmountUsd
: Math.min(signal.notionalUsd * config.copyRatio, config.maxSpendUsd);
if (orderUsd <= 0 || signal.price <= 0) return null;
// 滑点约束:买单设最高限价,卖单设最低限价
const maxPrice = signal.side === "BUY"
? Math.min(0.99, Number((signal.price * (1 + config.slippageTolerancePct)).toFixed(4)))
: undefined;
const minPrice = signal.side === "SELL"
? Math.max(0.01, Number((signal.price * (1 - config.slippageTolerancePct)).toFixed(4)))
: undefined;
const shares = Number((orderUsd / signal.price).toFixed(2));
if (shares <= 0) return null;
console.log(
`[执行跟单] 目标: ${signal.targetAddress} (${signal.role}) | ` +
`方向: ${signal.side} | 金额: $${orderUsd.toFixed(2)} | ` +
`${signal.side === "BUY" ? `价格上限: ${maxPrice}` : `价格下限: ${minPrice}`}`
);
return signal.side === "BUY"
? client.placeMarketOrder({
assetId: signal.assetId,
side: OrderSide.BUY,
amount: orderUsd,
orderType: config.orderType,
...(maxPrice ? { maxPrice } : {}),
})
: client.placeMarketOrder({
assetId: signal.assetId,
side: OrderSide.SELL,
shares,
orderType: config.orderType,
...(minPrice ? { minPrice } : {}),
});
}
return { client, executeOrder };
}
7. 入口主程序 (index.ts)
通过异步迭代器(for await)消费实时推流,提取跟单信号并执行:
// index.ts
import "dotenv/config";
import { OrderType } from "@polymarket/client";
import { createAndConnectStream } from "./stream";
import { extractCopySignals } from "./matcher";
import { createExecutor } from "./executor";
async function main() {
const polyedgeApiKey = process.env.POLYEDGE_API_KEY;
const privateKey = process.env.POLYMARKET_PRIVATE_KEY as `0x${string}`;
const depositWallet = process.env.POLYMARKET_DEPOSIT_WALLET as `0x${string}`;
const rawTargetAddresses = process.env.TARGET_ADDRESSES || "";
if (!polyedgeApiKey || !privateKey || !depositWallet || !rawTargetAddresses) {
throw new Error("缺少环境变量,请检查 .env 配置");
}
const targetAddresses = rawTargetAddresses
.split(",")
.map((addr) => addr.trim().toLowerCase())
.filter(Boolean);
const targetSet = new Set(targetAddresses);
// 1. 初始化 Polymarket 下单客户端
const executor = await createExecutor({
polymarketPrivateKey: privateKey,
polymarketDepositWallet: depositWallet,
copyMode: process.env.COPY_MODE?.toUpperCase() === "PROPORTIONAL" ? "PROPORTIONAL" : "FIXED",
fixedAmountUsd: Number(process.env.FIXED_AMOUNT_USD || "50"),
copyRatio: Number(process.env.COPY_RATIO || "0.1"),
maxSpendUsd: Number(process.env.MAX_SPEND_USD || "200"),
orderType: process.env.ORDER_TYPE?.toUpperCase() === "FOK" ? OrderType.FOK : OrderType.FAK,
slippageTolerancePct: Number(process.env.SLIPPAGE_TOLERANCE_PCT || "0.03"),
});
// 2. 建立 PolyEdge 交易流连接
const stream = await createAndConnectStream(polyedgeApiKey, targetAddresses);
const processedTxHashes = new Set<string>();
process.on("SIGINT", () => {
console.log("\n[退出] 正在关闭连接...");
stream.destroy();
process.exit(0);
});
console.log(`[监听中] 正在捕获出块前内存池Polymarket交易...`);
// 3. 消费交易流事件
for await (const tx of stream) {
if (!tx.tx_hash) continue;
if (processedTxHashes.has(tx.tx_hash)) continue;
processedTxHashes.add(tx.tx_hash);
// 维持最近 5,000 笔哈希滑动窗口
if (processedTxHashes.size > 5000) {
const oldest = processedTxHashes.values().next().value;
if (oldest) processedTxHashes.delete(oldest);
}
const signals = extractCopySignals(tx, targetSet);
for (const signal of signals) {
try {
const result = await executor.executeOrder(signal);
console.log(`[跟单结果]`, result);
} catch (err) {
console.error(`[下单失败] 目标交易: ${signal.txHash}`, err);
}
}
}
}
main().catch((err) => {
console.error("运行失败:", err);
process.exit(1);
});
8. 启动与验证
使用 tsx 启动:
npx tsx index.ts
当目标交易员触发成交时,终端输出跟单日志:
[PolyEdge] 交易流创建成功: 9bae9005-9ef1-4f17-8658-36ea54536836
[监听中] 正在捕获出块前内存池Polymarket交易...
[执行跟单] 目标: 0x4bf459194025b2ca11a2db2deb9ef2a08c467021 (taker) | 方向: BUY | 金额: $50.00 | 价格上限: 0.5047
[跟单结果] { orderId: '0x...', status: 'MATCHED' }