Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { readFileSync } from "node:fs";
import { FastMCP } from "fastmcp";
import * as tools from "./tools/index.js";
import { log } from "./util/log.js";

const packageJson = JSON.parse(
readFileSync(new URL("../package.json", import.meta.url), "utf8"),
Expand Down Expand Up @@ -42,12 +43,10 @@ async function main() {
server.addTool(tools.redeemPositionsTool);
server.addTool(tools.getPositionsTool);

process.stderr.write(
"Trading features enabled (POLYMARKET_PRIVATE_KEY is configured)\n",
);
log("Trading features enabled (POLYMARKET_PRIVATE_KEY is configured)");
} else {
process.stderr.write(
"Read-only mode: Set POLYMARKET_PRIVATE_KEY environment variable to enable trading features\n",
log(
"Read-only mode: Set POLYMARKET_PRIVATE_KEY environment variable to enable trading features",
);
}

Expand Down
3 changes: 2 additions & 1 deletion src/services/approvals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
utils,
Wallet,
} from "ethers";
import { log } from "../util/log.js";
import { getConfig, POLYGON_ADDRESSES } from "./config.js";

export type ApprovalCheck = {
Expand Down Expand Up @@ -226,7 +227,7 @@ export class PolymarketApprovals {
if (current.missing.includes(key)) {
const hash = await this.sendTx(fn, nonce++, waitConfs);
txHashes.push(hash);
process.stderr.write(`Approved ${label}: ${hash}\n`);
log(`Approved ${label}: ${hash}`);
}
}

Expand Down
19 changes: 9 additions & 10 deletions src/services/redemption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

import { type BigNumber, Contract, providers, Wallet } from "ethers";
import { log } from "../util/log.js";
import { getConfig, POLYGON_ADDRESSES } from "./config.js";

// Parent collection ID for Polymarket (constant)
Expand Down Expand Up @@ -174,7 +175,7 @@ export class PolymarketRedemption {
"No CTF tokens to redeem. Balance is 0 - position may have already been redeemed.",
};
}
process.stderr.write(`Token balance: ${tokenBalance.toString()}\n`);
log(`Token balance: ${tokenBalance.toString()}`);
}

// Check if market is resolved
Expand Down Expand Up @@ -218,9 +219,9 @@ export class PolymarketRedemption {
const amounts: [bigint, bigint] =
outcomeIndex === 0 ? [tokenBalance, 0n] : [0n, tokenBalance];

process.stderr.write(`Redeeming negRisk position:` + "\n");
process.stderr.write(` Condition ID: ${conditionIdBytes32}\n`);
process.stderr.write(` Amounts: [${amounts[0]}, ${amounts[1]}]\n`);
log(`Redeeming negRisk position:`);
log(` Condition ID: ${conditionIdBytes32}`);
log(` Amounts: [${amounts[0]}, ${amounts[1]}]`);

const negRiskAdapter = this.getNegRiskAdapterContract();
tx = await negRiskAdapter.redeemPositions(conditionIdBytes32, amounts, {
Expand All @@ -238,11 +239,9 @@ export class PolymarketRedemption {
};
}

process.stderr.write(`Redeeming CTF position:` + "\n");
process.stderr.write(` Condition ID: ${conditionIdBytes32}\n`);
process.stderr.write(
` Winning index sets: [${winningIndexSets.join(", ")}]\n`,
);
log(`Redeeming CTF position:`);
log(` Condition ID: ${conditionIdBytes32}`);
log(` Winning index sets: [${winningIndexSets.join(", ")}]`);

const ctf = this.getCtfContract();
tx = await ctf.redeemPositions(
Expand All @@ -256,7 +255,7 @@ export class PolymarketRedemption {
);
}

process.stderr.write(`Transaction submitted: ${tx.hash}\n`);
log(`Transaction submitted: ${tx.hash}`);

// Wait for confirmation
const receipt = await tx.wait(1);
Expand Down
31 changes: 16 additions & 15 deletions src/services/trading.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
} from "@polymarket/clob-client";
import { ClobClient, OrderType, Side } from "@polymarket/clob-client";
import { providers, Wallet } from "ethers";
import { log } from "../util/log.js";
import { PolymarketApprovals } from "./approvals.js";
import { getConfig } from "./config.js";

Expand Down Expand Up @@ -112,11 +113,11 @@ export class PolymarketTrading {
cfg.funderAddress,
);

process.stderr.write("Polymarket trading client initialized\n");
process.stderr.write(` - Signer: ${await ethersSigner.getAddress()}\n`);
process.stderr.write(` - Signature Type: ${cfg.signatureType}\n`);
log("Polymarket trading client initialized");
log(` - Signer: ${await ethersSigner.getAddress()}`);
log(` - Signature Type: ${cfg.signatureType}`);
if (cfg.funderAddress) {
process.stderr.write(` - Funder/Proxy: ${cfg.funderAddress}\n`);
log(` - Funder/Proxy: ${cfg.funderAddress}`);
}
}

Expand Down Expand Up @@ -253,12 +254,12 @@ export class PolymarketTrading {

const client = this.getClient();

process.stderr.write(`Placing ${args.side} order:\n`);
process.stderr.write(` Token: ${args.tokenId}\n`);
process.stderr.write(` Price: ${args.price}\n`);
process.stderr.write(` Size: ${args.size}\n`);
process.stderr.write(
` Market: negRisk=${marketParams.negRisk}, tickSize=${marketParams.tickSize}\n`,
log(`Placing ${args.side} order:`);
log(` Token: ${args.tokenId}`);
log(` Price: ${args.price}`);
log(` Size: ${args.size}`);
log(
` Market: negRisk=${marketParams.negRisk}, tickSize=${marketParams.tickSize}`,
);

return client.createAndPostOrder(
Expand Down Expand Up @@ -304,11 +305,11 @@ export class PolymarketTrading {

const client = this.getClient();

process.stderr.write(`Placing ${args.side} market order:\n`);
process.stderr.write(` Token: ${args.tokenId}\n`);
process.stderr.write(` Amount: ${args.amount}\n`);
process.stderr.write(
` Market: negRisk=${marketParams.negRisk}, tickSize=${marketParams.tickSize}\n`,
log(`Placing ${args.side} market order:`);
log(` Token: ${args.tokenId}`);
log(` Amount: ${args.amount}`);
log(
` Market: negRisk=${marketParams.negRisk}, tickSize=${marketParams.tickSize}`,
);

return client.createAndPostMarketOrder(
Expand Down
15 changes: 3 additions & 12 deletions src/tools/cancel-all-orders.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
import { z } from "zod";
import { ApprovalRequiredError } from "../services/approvals.js";
import { tradeApi } from "../services/trading.js";
import { withApprovalGuard } from "../util/with-approval-guard.js";

const cancelAllOrdersSchema = z.object({});

export const cancelAllOrdersTool = {
name: "cancel_all_orders",
description: "Cancel all open orders for the authenticated account.",
parameters: cancelAllOrdersSchema,
execute: async (_args: z.infer<typeof cancelAllOrdersSchema>) => {
try {
const result = await tradeApi.cancelAllOrders();
return JSON.stringify(result, null, 2);
} catch (err) {
if (err instanceof ApprovalRequiredError) {
return JSON.stringify(err, null, 2);
}
throw err;
}
},
execute: async (_args: z.infer<typeof cancelAllOrdersSchema>) =>
withApprovalGuard(() => tradeApi.cancelAllOrders()),
};
15 changes: 3 additions & 12 deletions src/tools/cancel-order.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { z } from "zod";
import { ApprovalRequiredError } from "../services/approvals.js";
import { tradeApi } from "../services/trading.js";
import { withApprovalGuard } from "../util/with-approval-guard.js";

const cancelOrderSchema = z.object({
orderId: z.string().describe("The unique identifier of the order to cancel"),
Expand All @@ -10,15 +10,6 @@ export const cancelOrderTool = {
name: "cancel_order",
description: "Cancel a specific order by its ID.",
parameters: cancelOrderSchema,
execute: async (args: z.infer<typeof cancelOrderSchema>) => {
try {
const result = await tradeApi.cancelOrder(args.orderId);
return JSON.stringify(result, null, 2);
} catch (err) {
if (err instanceof ApprovalRequiredError) {
return JSON.stringify(err, null, 2);
}
throw err;
}
},
execute: async (args: z.infer<typeof cancelOrderSchema>) =>
withApprovalGuard(() => tradeApi.cancelOrder(args.orderId)),
};
13 changes: 2 additions & 11 deletions src/tools/get-balance-allowance.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AssetType } from "@polymarket/clob-client";
import { z } from "zod";
import { ApprovalRequiredError } from "../services/approvals.js";
import { tradeApi } from "../services/trading.js";
import { withApprovalGuard } from "../util/with-approval-guard.js";

const getBalanceAllowanceSchema = z.object({
assetType: z
Expand All @@ -23,15 +23,6 @@ export const getBalanceAllowanceTool = {
asset_type: AssetType[args.assetType],
};
if (args.tokenID) params.token_id = args.tokenID;

try {
const result = await tradeApi.getBalanceAllowance(params);
return JSON.stringify(result, null, 2);
} catch (err) {
if (err instanceof ApprovalRequiredError) {
return JSON.stringify(err, null, 2);
}
throw err;
}
return withApprovalGuard(() => tradeApi.getBalanceAllowance(params));
},
};
19 changes: 6 additions & 13 deletions src/tools/place-market-order.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { z } from "zod";
import { ApprovalRequiredError } from "../services/approvals.js";
import { tradeApi } from "../services/trading.js";
import { withApprovalGuard } from "../util/with-approval-guard.js";

const placeMarketOrderSchema = z.object({
tokenId: z.string().describe("The token ID of the market outcome to trade"),
Expand All @@ -24,20 +24,13 @@ export const placeMarketOrderTool = {
description:
"Place a market order that executes immediately at current market price. IMPORTANT: For BUY orders, amount is the dollar amount ($USD) you want to spend. For SELL orders, amount is the number of shares to sell. Example: amount=5, side=BUY means 'spend $5 to buy shares at market price'. Minimum $1 for BUY orders.",
parameters: placeMarketOrderSchema,
execute: async (args: z.infer<typeof placeMarketOrderSchema>) => {
try {
const result = await tradeApi.placeMarketOrder({
execute: async (args: z.infer<typeof placeMarketOrderSchema>) =>
withApprovalGuard(() =>
tradeApi.placeMarketOrder({
tokenId: args.tokenId,
amount: args.amount,
side: args.side,
...(args.orderType && { orderType: args.orderType }),
});
return JSON.stringify(result, null, 2);
} catch (err) {
if (err instanceof ApprovalRequiredError) {
return JSON.stringify(err, null, 2);
}
throw err;
}
},
}),
),
};
19 changes: 6 additions & 13 deletions src/tools/place-order.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { z } from "zod";
import { ApprovalRequiredError } from "../services/approvals.js";
import { tradeApi } from "../services/trading.js";
import { withApprovalGuard } from "../util/with-approval-guard.js";

const placeOrderSchema = z.object({
tokenId: z.string().describe("The token ID of the market outcome to trade"),
Expand Down Expand Up @@ -31,21 +31,14 @@ export const placeOrderTool = {
description:
"Place a limit order on Polymarket at a specific price. Specify the number of shares (size) and price (0-1). For both BUY and SELL, you specify the number of shares you want to trade. Example: size=10, price=0.6 means buy/sell 10 shares at $0.60 per share (total: $6).",
parameters: placeOrderSchema,
execute: async (args: z.infer<typeof placeOrderSchema>) => {
try {
const result = await tradeApi.placeOrder({
execute: async (args: z.infer<typeof placeOrderSchema>) =>
withApprovalGuard(() =>
tradeApi.placeOrder({
tokenId: args.tokenId,
price: args.price,
size: args.size,
side: args.side,
...(args.orderType && { orderType: args.orderType }),
});
return JSON.stringify(result, null, 2);
} catch (err) {
if (err instanceof ApprovalRequiredError) {
return JSON.stringify(err, null, 2);
}
throw err;
}
},
}),
),
};
21 changes: 7 additions & 14 deletions src/tools/update-balance-allowance.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AssetType } from "@polymarket/clob-client";
import { z } from "zod";
import { ApprovalRequiredError } from "../services/approvals.js";
import { tradeApi } from "../services/trading.js";
import { withApprovalGuard } from "../util/with-approval-guard.js";

const updateBalanceAllowanceSchema = z.object({
assetType: z
Expand All @@ -23,19 +23,12 @@ export const updateBalanceAllowanceTool = {
asset_type: AssetType[args.assetType],
};
if (args.tokenID) params.token_id = args.tokenID;

try {
return withApprovalGuard(async () => {
await tradeApi.updateBalanceAllowance(params);
return JSON.stringify(
{ success: true, message: "Balance allowance updated successfully" },
null,
2,
);
} catch (err) {
if (err instanceof ApprovalRequiredError) {
return JSON.stringify(err, null, 2);
}
throw err;
}
return {
success: true,
message: "Balance allowance updated successfully",
};
});
},
};
3 changes: 3 additions & 0 deletions src/util/log.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function log(message: string): void {
process.stderr.write(`${message}\n`);
}
15 changes: 15 additions & 0 deletions src/util/with-approval-guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ApprovalRequiredError } from "../services/approvals.js";

export async function withApprovalGuard(
fn: () => Promise<unknown>,
): Promise<string> {
try {
const result = await fn();
return JSON.stringify(result, null, 2);
} catch (err) {
if (err instanceof ApprovalRequiredError) {
return JSON.stringify(err, null, 2);
}
throw err;
}
}
Loading