opentool 0.19.0 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opentool",
3
- "version": "0.19.0",
3
+ "version": "0.19.1",
4
4
  "description": "OpenTool framework for building serverless MCP tools",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -10,7 +10,7 @@
10
10
  "validate": "opentool validate"
11
11
  },
12
12
  "dependencies": {
13
- "opentool": "^0.19.0",
13
+ "opentool": "^0.19.1",
14
14
  "zod": "^4.3.6"
15
15
  },
16
16
  "devDependencies": {
@@ -1,6 +1,6 @@
1
1
  # Polymarket Simple Trade
2
2
 
3
- Minimal OpenTool starter for placing one Polymarket order with an operating wallet signer and recording the submission in store.
3
+ Minimal OpenTool starter for placing one Polymarket mainnet order with an operating wallet signer, a canonical Polymarket funder address, and `signatureType = 2`.
4
4
 
5
5
  ## Quickstart
6
6
 
@@ -18,7 +18,11 @@ npx opentool dev
18
18
  "side": "BUY",
19
19
  "price": "0.52",
20
20
  "size": "10",
21
- "orderType": "GTC",
22
- "environment": "mainnet"
21
+ "orderType": "GTC"
23
22
  }
24
23
  ```
24
+
25
+ ## Required Runtime
26
+
27
+ - `POLYMARKET_FUNDER_ADDRESS`: the user's Polymarket proxy/safe trading wallet
28
+ - Turnkey signer envs injected by the platform runtime
@@ -3,7 +3,7 @@ export const metadata = {
3
3
  name: "polymarket-simple-trade",
4
4
  displayName: "Polymarket Simple Trade",
5
5
  version: "1.0.0",
6
- description: "Minimal OpenTool starter for placing one Polymarket trade.",
6
+ description: "Minimal OpenTool starter for placing one Polymarket trade via the canonical funder account path.",
7
7
  category: "internal",
8
8
  discovery: {
9
9
  keywords: ["polymarket", "prediction-market", "trading", "opentool"],
@@ -10,7 +10,7 @@
10
10
  "validate": "opentool validate"
11
11
  },
12
12
  "dependencies": {
13
- "opentool": "^0.19.0",
13
+ "opentool": "^0.19.1",
14
14
  "zod": "^4.3.6"
15
15
  },
16
16
  "devDependencies": {
@@ -2,12 +2,18 @@ import {
2
2
  createOrDerivePolymarketApiKey,
3
3
  placePolymarketOrder,
4
4
  type PolymarketApiCredentials,
5
- type PolymarketEnvironment,
6
5
  } from "opentool/adapters/polymarket";
7
6
  import { store } from "opentool/store";
8
- import { wallet, type WalletContext, type WalletFullContext } from "opentool/wallet";
7
+ import {
8
+ wallet,
9
+ type WalletContext,
10
+ type WalletFullContext,
11
+ } from "opentool/wallet";
9
12
  import { z } from "zod";
10
13
 
14
+ const POLYMARKET_ENVIRONMENT = "mainnet" as const;
15
+ const POLYMARKET_SIGNATURE_TYPE = 2 as const;
16
+
11
17
  const tradeRequestSchema = z
12
18
  .object({
13
19
  conditionId: z.string().min(1),
@@ -16,7 +22,6 @@ const tradeRequestSchema = z
16
22
  price: z.coerce.string().min(1),
17
23
  size: z.coerce.string().min(1),
18
24
  orderType: z.enum(["GTC", "FOK", "FAK", "GTD"]).default("GTC"),
19
- environment: z.enum(["mainnet", "testnet"]).optional(),
20
25
  expiration: z.coerce.number().int().nonnegative().optional(),
21
26
  nonce: z.coerce.number().int().nonnegative().optional(),
22
27
  feeRateBps: z.coerce.number().int().nonnegative().optional(),
@@ -25,13 +30,23 @@ const tradeRequestSchema = z
25
30
  .strict();
26
31
 
27
32
  export const profile = {
28
- description: "Place one Polymarket order with the operating wallet signer",
33
+ description:
34
+ "Place one Polymarket order with the operating wallet signer and canonical funder account",
29
35
  category: "trade",
30
36
  };
31
37
 
32
- function assertSignerContext(ctx: WalletContext): asserts ctx is WalletFullContext {
33
- if (!("walletClient" in ctx) || !ctx.walletClient || !("account" in ctx) || !ctx.account) {
34
- throw new Error("Configure a signer (PRIVATE_KEY or Turnkey env vars) before trading.");
38
+ function assertSignerContext(
39
+ ctx: WalletContext,
40
+ ): asserts ctx is WalletFullContext {
41
+ if (
42
+ !("walletClient" in ctx) ||
43
+ !ctx.walletClient ||
44
+ !("account" in ctx) ||
45
+ !ctx.account
46
+ ) {
47
+ throw new Error(
48
+ "Configure a signer (PRIVATE_KEY or Turnkey env vars) before trading.",
49
+ );
35
50
  }
36
51
  }
37
52
 
@@ -49,26 +64,49 @@ function readCredentialsFromEnv(): PolymarketApiCredentials | undefined {
49
64
  };
50
65
  }
51
66
 
52
- function resolveEnvironment(): PolymarketEnvironment {
53
- return process.env.POLYMARKET_ENVIRONMENT === "testnet" ? "testnet" : "mainnet";
67
+ function readFunderAddress(): `0x${string}` {
68
+ const funderAddress = process.env.POLYMARKET_FUNDER_ADDRESS?.trim();
69
+ if (!funderAddress || !/^0x[a-fA-F0-9]{40}$/.test(funderAddress)) {
70
+ throw new Error(
71
+ "POLYMARKET_FUNDER_ADDRESS must be set to the user's Polymarket funder wallet.",
72
+ );
73
+ }
74
+ return funderAddress as `0x${string}`;
75
+ }
76
+
77
+ function readApiKeyNonce(): number {
78
+ const rawNonce = process.env.POLYMARKET_API_NONCE?.trim();
79
+ if (!rawNonce) {
80
+ throw new Error("POLYMARKET_API_NONCE must be set for Polymarket trading.");
81
+ }
82
+ if (!/^\d+$/.test(rawNonce)) {
83
+ throw new Error("POLYMARKET_API_NONCE must be a non-negative integer.");
84
+ }
85
+ const nonce = Number(rawNonce);
86
+ if (!Number.isSafeInteger(nonce) || nonce < 0) {
87
+ throw new Error("POLYMARKET_API_NONCE must be a non-negative integer.");
88
+ }
89
+ return nonce;
54
90
  }
55
91
 
56
92
  export async function POST(req: Request) {
57
93
  const payload = tradeRequestSchema.parse(await req.json());
58
- const environment: PolymarketEnvironment = payload.environment ?? resolveEnvironment();
94
+ const environment = POLYMARKET_ENVIRONMENT;
59
95
  const ctx = await wallet({
60
96
  chain: process.env.OPENTOOL_SIGNER_CHAIN ?? "base",
61
97
  apiKey: process.env.ALCHEMY_API_KEY,
62
98
  rpcUrl: process.env.RPC_URL,
63
99
  });
64
100
  assertSignerContext(ctx);
101
+ const funderAddress = readFunderAddress();
102
+ const apiKeyNonce = readApiKeyNonce();
65
103
 
66
- // Polymarket only needs EIP-712 signing here, so the signer can come from the shared operating wallet.
67
104
  const credentials =
68
105
  readCredentialsFromEnv() ??
69
106
  (await createOrDerivePolymarketApiKey({
70
107
  wallet: ctx,
71
108
  environment,
109
+ nonce: apiKeyNonce,
72
110
  }));
73
111
 
74
112
  const result = await placePolymarketOrder({
@@ -81,19 +119,27 @@ export async function POST(req: Request) {
81
119
  side: payload.side,
82
120
  price: payload.price,
83
121
  size: payload.size,
84
- ...(payload.expiration !== undefined ? { expiration: payload.expiration } : {}),
122
+ maker: funderAddress,
123
+ signer: ctx.address as `0x${string}`,
124
+ signatureType: POLYMARKET_SIGNATURE_TYPE,
125
+ ...(payload.expiration !== undefined
126
+ ? { expiration: payload.expiration }
127
+ : {}),
85
128
  ...(payload.nonce !== undefined ? { nonce: payload.nonce } : {}),
86
- ...(payload.feeRateBps !== undefined ? { feeRateBps: payload.feeRateBps } : {}),
129
+ ...(payload.feeRateBps !== undefined
130
+ ? { feeRateBps: payload.feeRateBps }
131
+ : {}),
87
132
  ...(payload.tickSize ? { tickSize: payload.tickSize } : {}),
88
133
  },
89
134
  });
90
135
 
91
- const ref = result.orderId ?? `${payload.conditionId}:${payload.tokenId}:${Date.now()}`;
136
+ const ref =
137
+ result.orderId ?? `${payload.conditionId}:${payload.tokenId}:${Date.now()}`;
92
138
  await store({
93
139
  source: "polymarket",
94
140
  ref,
95
141
  status: "submitted",
96
- walletAddress: ctx.address,
142
+ walletAddress: funderAddress,
97
143
  action: "trade",
98
144
  notional: payload.size,
99
145
  market: {
@@ -111,12 +157,16 @@ export async function POST(req: Request) {
111
157
  price: payload.price,
112
158
  size: payload.size,
113
159
  orderType: payload.orderType,
160
+ signerAddress: ctx.address,
161
+ funderAddress,
162
+ signatureType: POLYMARKET_SIGNATURE_TYPE,
114
163
  },
115
164
  });
116
165
 
117
166
  return Response.json({
118
167
  ok: true,
119
168
  environment,
169
+ funderAddress,
120
170
  order: result,
121
171
  });
122
172
  }