otto-intel-mcp 0.1.0
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/LICENSE +21 -0
- package/README.md +102 -0
- package/dist/adapter/cdp-signer.d.ts +133 -0
- package/dist/adapter/cdp-signer.js +356 -0
- package/dist/adapter/cdp-signer.js.map +1 -0
- package/dist/adapter/chain.d.ts +36 -0
- package/dist/adapter/chain.js +65 -0
- package/dist/adapter/chain.js.map +1 -0
- package/dist/adapter/erc20.d.ts +39 -0
- package/dist/adapter/erc20.js +17 -0
- package/dist/adapter/erc20.js.map +1 -0
- package/dist/adapter/lifi-decode.d.ts +52 -0
- package/dist/adapter/lifi-decode.js +149 -0
- package/dist/adapter/lifi-decode.js.map +1 -0
- package/dist/adapter/refusal.d.ts +21 -0
- package/dist/adapter/refusal.js +55 -0
- package/dist/adapter/refusal.js.map +1 -0
- package/dist/adapter/sent-step.d.ts +15 -0
- package/dist/adapter/sent-step.js +6 -0
- package/dist/adapter/sent-step.js.map +1 -0
- package/dist/adapter/verify.d.ts +149 -0
- package/dist/adapter/verify.js +432 -0
- package/dist/adapter/verify.js.map +1 -0
- package/dist/adapter-cdp-index.d.ts +6 -0
- package/dist/adapter-cdp-index.js +7 -0
- package/dist/adapter-index.d.ts +15 -0
- package/dist/adapter-index.js +15 -0
- package/dist/artifact-id.d.ts +16 -0
- package/dist/artifact-id.js +60 -0
- package/dist/boot-redaction.d.ts +16 -0
- package/dist/boot-redaction.js +54 -0
- package/dist/data-source.d.ts +7 -0
- package/dist/data-source.js +2 -0
- package/dist/errors.d.ts +70 -0
- package/dist/errors.js +221 -0
- package/dist/execution-config.d.ts +232 -0
- package/dist/execution-config.js +443 -0
- package/dist/execution-delegated-definition.d.ts +165 -0
- package/dist/execution-delegated-definition.js +116 -0
- package/dist/execution-delegation-admin-definition.d.ts +208 -0
- package/dist/execution-delegation-admin-definition.js +170 -0
- package/dist/execution-delegation-admin.d.ts +74 -0
- package/dist/execution-delegation-admin.js +290 -0
- package/dist/execution-delegation-policy.d.ts +257 -0
- package/dist/execution-delegation-policy.js +279 -0
- package/dist/execution-delegation.d.ts +190 -0
- package/dist/execution-delegation.js +545 -0
- package/dist/execution-errors.d.ts +9 -0
- package/dist/execution-errors.js +128 -0
- package/dist/execution-index.d.ts +17 -0
- package/dist/execution-index.js +16 -0
- package/dist/execution-intent.d.ts +14 -0
- package/dist/execution-intent.js +36 -0
- package/dist/execution-registration.d.ts +113 -0
- package/dist/execution-registration.js +161 -0
- package/dist/execution-tool-definitions.d.ts +1103 -0
- package/dist/execution-tool-definitions.js +1051 -0
- package/dist/execution-tools.d.ts +85 -0
- package/dist/execution-tools.js +690 -0
- package/dist/execution-types.d.ts +274 -0
- package/dist/execution-types.js +157 -0
- package/dist/hyperliquid-info-client.d.ts +31 -0
- package/dist/hyperliquid-info-client.js +121 -0
- package/dist/hyperliquid-order-assertions.d.ts +89 -0
- package/dist/hyperliquid-order-assertions.js +325 -0
- package/dist/hyperliquid-order-builder.d.ts +47 -0
- package/dist/hyperliquid-order-builder.js +55 -0
- package/dist/lifi-bridge-assertions.d.ts +49 -0
- package/dist/lifi-bridge-assertions.js +303 -0
- package/dist/lifi-execution-client.d.ts +74 -0
- package/dist/lifi-execution-client.js +165 -0
- package/dist/lifi-fee-assertions.d.ts +32 -0
- package/dist/lifi-fee-assertions.js +103 -0
- package/dist/lifi-swap-assertions.d.ts +32 -0
- package/dist/lifi-swap-assertions.js +214 -0
- package/dist/local-config.d.ts +9 -0
- package/dist/local-config.js +45 -0
- package/dist/mcp-server.d.ts +4 -0
- package/dist/mcp-server.js +44 -0
- package/dist/polymarket-clob-client.d.ts +73 -0
- package/dist/polymarket-clob-client.js +186 -0
- package/dist/polymarket-order-assertions.d.ts +60 -0
- package/dist/polymarket-order-assertions.js +273 -0
- package/dist/polymarket-order-builder.d.ts +43 -0
- package/dist/polymarket-order-builder.js +74 -0
- package/dist/prepared-artifacts.d.ts +45 -0
- package/dist/prepared-artifacts.js +92 -0
- package/dist/stdio-main.d.ts +2 -0
- package/dist/stdio-main.js +20 -0
- package/dist/tool-definitions.d.ts +20 -0
- package/dist/tool-definitions.js +162 -0
- package/dist/x402-read-source.d.ts +11 -0
- package/dist/x402-read-source.js +77 -0
- package/package.json +162 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { isPolymarketTickSize, } from './execution-config.js';
|
|
3
|
+
import { isExecutionRefusal, refuse } from './execution-errors.js';
|
|
4
|
+
/**
|
|
5
|
+
* Public, unauthenticated CLOB reads only. This client holds no API credentials, derives none, and
|
|
6
|
+
* never posts an order: the L2-authenticated surface (`postOrder`, key derivation, the Dynamic vault
|
|
7
|
+
* lifecycle) is fenced out of Step 1 by spec §11.
|
|
8
|
+
*/
|
|
9
|
+
const POLYMARKET_TOKEN_SCHEMA = z
|
|
10
|
+
.object({
|
|
11
|
+
token_id: z.string().min(1),
|
|
12
|
+
outcome: z.string().optional(),
|
|
13
|
+
})
|
|
14
|
+
.passthrough();
|
|
15
|
+
/** The live `/markets/{condition_id}` body, as captured in `test/fixtures/polymarket-market-*.json`. */
|
|
16
|
+
export const POLYMARKET_MARKET_RESPONSE_SCHEMA = z
|
|
17
|
+
.object({
|
|
18
|
+
condition_id: z.string().min(1),
|
|
19
|
+
// The live feed sends this as a JSON number (0.001); the SDK stringifies it before use.
|
|
20
|
+
minimum_tick_size: z.union([z.number(), z.string()]),
|
|
21
|
+
minimum_order_size: z.union([z.number(), z.string()]),
|
|
22
|
+
neg_risk: z.boolean().optional(),
|
|
23
|
+
accepting_orders: z.boolean(),
|
|
24
|
+
active: z.boolean(),
|
|
25
|
+
closed: z.boolean(),
|
|
26
|
+
archived: z.boolean().optional(),
|
|
27
|
+
enable_order_book: z.boolean(),
|
|
28
|
+
tokens: z.array(POLYMARKET_TOKEN_SCHEMA).min(1),
|
|
29
|
+
})
|
|
30
|
+
.passthrough();
|
|
31
|
+
export const POLYMARKET_TOKEN_MARKET_RESPONSE_SCHEMA = z
|
|
32
|
+
.object({
|
|
33
|
+
condition_id: z.string().min(1),
|
|
34
|
+
})
|
|
35
|
+
.passthrough();
|
|
36
|
+
/** The live `/fees/builder-fees/{code}` body: the value source for the ruled builder rates (§8). */
|
|
37
|
+
export const POLYMARKET_BUILDER_FEES_RESPONSE_SCHEMA = z
|
|
38
|
+
.object({
|
|
39
|
+
code: z.string().min(1),
|
|
40
|
+
builder_maker_fee_rate_bps: z.number(),
|
|
41
|
+
builder_taker_fee_rate_bps: z.number(),
|
|
42
|
+
enabled: z.boolean(),
|
|
43
|
+
})
|
|
44
|
+
.passthrough();
|
|
45
|
+
function metadataRefuse(message) {
|
|
46
|
+
return refuse('POLYMARKET_METADATA_INVALID', message);
|
|
47
|
+
}
|
|
48
|
+
function decimalNumber(value, name) {
|
|
49
|
+
const parsed = typeof value === 'number' ? value : Number(value);
|
|
50
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
51
|
+
metadataRefuse(`Polymarket market metadata has a non-positive ${name}`);
|
|
52
|
+
}
|
|
53
|
+
return parsed;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Map a live market body onto the metadata the constructor needs, refusing every shape the
|
|
57
|
+
* constructor cannot act on. Shared by the REST client and the recorded-fixture loader, so the
|
|
58
|
+
* assertions in tests run over the same parse the network path uses.
|
|
59
|
+
*/
|
|
60
|
+
export function polymarketMarketMetadata(raw, tokenId) {
|
|
61
|
+
const parsed = POLYMARKET_MARKET_RESPONSE_SCHEMA.safeParse(raw);
|
|
62
|
+
if (!parsed.success) {
|
|
63
|
+
metadataRefuse('Polymarket market metadata is missing required fields');
|
|
64
|
+
}
|
|
65
|
+
const market = parsed.data;
|
|
66
|
+
if (!market.tokens.some((token) => token.token_id === tokenId)) {
|
|
67
|
+
// A token id that is not in the market it resolved to means the two reads disagree; building an
|
|
68
|
+
// order against either half would bind the user to a market they did not name.
|
|
69
|
+
metadataRefuse('Polymarket market metadata does not contain the requested token id');
|
|
70
|
+
}
|
|
71
|
+
const tickSize = String(decimalNumber(market.minimum_tick_size, 'minimum tick size'));
|
|
72
|
+
if (!isPolymarketTickSize(tickSize)) {
|
|
73
|
+
refuse('UNSUPPORTED_POLYMARKET_MARKET', `market tick size ${tickSize} is outside the supported set (0.1, 0.01, 0.001, 0.0001)`);
|
|
74
|
+
}
|
|
75
|
+
return Object.freeze({
|
|
76
|
+
tokenId,
|
|
77
|
+
conditionId: market.condition_id,
|
|
78
|
+
tickSize,
|
|
79
|
+
minimumOrderSize: decimalNumber(market.minimum_order_size, 'minimum order size'),
|
|
80
|
+
negRisk: market.neg_risk === true,
|
|
81
|
+
acceptingOrders: market.accepting_orders,
|
|
82
|
+
active: market.active,
|
|
83
|
+
closed: market.closed,
|
|
84
|
+
orderBookEnabled: market.enable_order_book,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The live builder-registry row for exactly the expected code. A row for a different code is
|
|
89
|
+
* refused rather than trusted: the whole point of the read is to bind Otto's own attribution.
|
|
90
|
+
*/
|
|
91
|
+
export function polymarketBuilderFeeRates(raw, expectedCode) {
|
|
92
|
+
const parsed = POLYMARKET_BUILDER_FEES_RESPONSE_SCHEMA.safeParse(raw);
|
|
93
|
+
if (!parsed.success) {
|
|
94
|
+
metadataRefuse('Polymarket builder-fee response is missing required fields');
|
|
95
|
+
}
|
|
96
|
+
const rates = parsed.data;
|
|
97
|
+
if (rates.code.toLowerCase() !== expectedCode.toLowerCase()) {
|
|
98
|
+
metadataRefuse('Polymarket builder-fee response describes a different builder code');
|
|
99
|
+
}
|
|
100
|
+
for (const [name, value] of [
|
|
101
|
+
['maker', rates.builder_maker_fee_rate_bps],
|
|
102
|
+
['taker', rates.builder_taker_fee_rate_bps],
|
|
103
|
+
]) {
|
|
104
|
+
if (!Number.isInteger(value) || value < 0 || value > 10_000) {
|
|
105
|
+
metadataRefuse(`Polymarket builder-fee response has an out-of-range ${name} rate`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return Object.freeze({
|
|
109
|
+
code: rates.code.toLowerCase(),
|
|
110
|
+
makerBps: rates.builder_maker_fee_rate_bps,
|
|
111
|
+
takerBps: rates.builder_taker_fee_rate_bps,
|
|
112
|
+
enabled: rates.enabled,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
export function buildPolymarketMarketByTokenUrl(tokenId, config) {
|
|
116
|
+
return new URL(`${config.polymarketClobBaseUrl}/markets-by-token/${encodeURIComponent(tokenId)}`);
|
|
117
|
+
}
|
|
118
|
+
export function buildPolymarketMarketUrl(conditionId, config) {
|
|
119
|
+
return new URL(`${config.polymarketClobBaseUrl}/markets/${encodeURIComponent(conditionId)}`);
|
|
120
|
+
}
|
|
121
|
+
export function buildPolymarketBuilderFeesUrl(builderCode, config) {
|
|
122
|
+
return new URL(`${config.polymarketClobBaseUrl}/fees/builder-fees/${encodeURIComponent(builderCode)}`);
|
|
123
|
+
}
|
|
124
|
+
function boundedSignal(callerSignal, timeoutMs) {
|
|
125
|
+
const controller = new AbortController();
|
|
126
|
+
const timeout = setTimeout(() => controller.abort(new Error('Polymarket CLOB read timeout')), timeoutMs);
|
|
127
|
+
const onAbort = () => controller.abort(callerSignal?.reason);
|
|
128
|
+
if (callerSignal?.aborted)
|
|
129
|
+
onAbort();
|
|
130
|
+
else
|
|
131
|
+
callerSignal?.addEventListener('abort', onAbort, { once: true });
|
|
132
|
+
return {
|
|
133
|
+
signal: controller.signal,
|
|
134
|
+
cleanup: () => {
|
|
135
|
+
clearTimeout(timeout);
|
|
136
|
+
callerSignal?.removeEventListener('abort', onAbort);
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
export class RestPolymarketClobClient {
|
|
141
|
+
config;
|
|
142
|
+
fetchImpl;
|
|
143
|
+
constructor(config, fetchImpl = fetch) {
|
|
144
|
+
this.config = config;
|
|
145
|
+
this.fetchImpl = fetchImpl;
|
|
146
|
+
}
|
|
147
|
+
async getMarketMetadata(tokenId, signal) {
|
|
148
|
+
const byToken = POLYMARKET_TOKEN_MARKET_RESPONSE_SCHEMA.safeParse(await this.getJson(buildPolymarketMarketByTokenUrl(tokenId, this.config), signal));
|
|
149
|
+
if (!byToken.success) {
|
|
150
|
+
metadataRefuse('Polymarket token lookup did not return a condition id');
|
|
151
|
+
}
|
|
152
|
+
return polymarketMarketMetadata(await this.getJson(buildPolymarketMarketUrl(byToken.data.condition_id, this.config), signal), tokenId);
|
|
153
|
+
}
|
|
154
|
+
async getBuilderFeeRates(builderCode, signal) {
|
|
155
|
+
return polymarketBuilderFeeRates(await this.getJson(buildPolymarketBuilderFeesUrl(builderCode, this.config), signal), builderCode);
|
|
156
|
+
}
|
|
157
|
+
async getJson(requestUrl, callerSignal) {
|
|
158
|
+
const abort = boundedSignal(callerSignal, this.config.polymarketTimeoutMs);
|
|
159
|
+
try {
|
|
160
|
+
// No credential header of any kind: every read here is on a public endpoint.
|
|
161
|
+
const response = await this.fetchImpl(requestUrl, {
|
|
162
|
+
method: 'GET',
|
|
163
|
+
headers: { Accept: 'application/json' },
|
|
164
|
+
signal: abort.signal,
|
|
165
|
+
});
|
|
166
|
+
if (!response.ok) {
|
|
167
|
+
refuse('POLYMARKET_METADATA_FAILED', `Polymarket CLOB read failed with HTTP ${response.status}`);
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
return await response.json();
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
refuse('POLYMARKET_METADATA_FAILED', 'Polymarket CLOB response was not valid JSON');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
if (isExecutionRefusal(error))
|
|
178
|
+
throw error;
|
|
179
|
+
refuse('POLYMARKET_METADATA_FAILED', 'Polymarket CLOB read failed');
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
abort.cleanup();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=polymarket-clob-client.js.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type Address, type Hex } from 'viem';
|
|
2
|
+
import { type ExecutionServiceConfig, type PolymarketTickSize } from './execution-config.js';
|
|
3
|
+
import type { PolymarketOrderArgs, PolymarketOrderTypedData } from './execution-tool-definitions.js';
|
|
4
|
+
import type { ExecutionAssertion } from './execution-types.js';
|
|
5
|
+
import type { PolymarketBuilderFeeRates } from './polymarket-clob-client.js';
|
|
6
|
+
import { type UnsignedPolymarketOrder } from './polymarket-order-builder.js';
|
|
7
|
+
/**
|
|
8
|
+
* Assert-what-you-built for the Polymarket rail (spec §8.2): every check below runs on the order
|
|
9
|
+
* struct and EIP-712 payload the SDK actually produced — the exact bytes the user's signer will
|
|
10
|
+
* hash — never on the values this constructor intended to build.
|
|
11
|
+
*/
|
|
12
|
+
/** Price is carried as an exact integer count of 1/10,000ths, the finest tick the venue supports. */
|
|
13
|
+
export declare const POLYMARKET_PRICE_SCALE = 10000n;
|
|
14
|
+
/** Exact decimal-string → atomic-unit conversion. No float ever touches an amount. */
|
|
15
|
+
export declare function polymarketAtomicUnits(decimal: string, decimals?: number): bigint;
|
|
16
|
+
/** Exact price in 1/10,000ths. The caller's string form is authoritative; no rounding happens. */
|
|
17
|
+
export declare function polymarketPriceUnits(price: string): bigint;
|
|
18
|
+
export interface PolymarketPriceCheck {
|
|
19
|
+
readonly priceUnits: bigint;
|
|
20
|
+
readonly tickUnits: bigint;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The venue only accepts on-tick prices (live-verified 2026-08-17: every level of every book on a
|
|
24
|
+
* 0.01-tick market is an exact multiple of 0.01). The SDK would silently `roundNormal` an off-tick
|
|
25
|
+
* price to a different one, so an off-tick request is refused by name instead of being filled at a
|
|
26
|
+
* price the caller never asked for.
|
|
27
|
+
*/
|
|
28
|
+
export declare function assertPolymarketPriceOnTick(price: string, tickSize: PolymarketTickSize): PolymarketPriceCheck;
|
|
29
|
+
/**
|
|
30
|
+
* The live builder registry is the value source for the ruled 0/0 bps rates (§8, Step-0 ruling).
|
|
31
|
+
* A registry row that no longer matches the ruled value means the rate moved without the ruling
|
|
32
|
+
* that governs it, so the constructor refuses instead of guessing which value is now in force — and
|
|
33
|
+
* a disabled row means the attribution would be inert, which is a missing fee, not a lower one.
|
|
34
|
+
*/
|
|
35
|
+
export declare function assertPolymarketBuilderAttributionRuled(rates: PolymarketBuilderFeeRates, config: ExecutionServiceConfig): ExecutionAssertion[];
|
|
36
|
+
export interface PolymarketOrderAssertionInput {
|
|
37
|
+
readonly built: UnsignedPolymarketOrder;
|
|
38
|
+
readonly maker: Address;
|
|
39
|
+
readonly tokenId: string;
|
|
40
|
+
readonly side: 'BUY' | 'SELL';
|
|
41
|
+
readonly priceUnits: bigint;
|
|
42
|
+
readonly sizeAtomic: bigint;
|
|
43
|
+
readonly tickSize: PolymarketTickSize;
|
|
44
|
+
readonly negRisk: boolean;
|
|
45
|
+
readonly minimumOrderSizeAtomic: bigint;
|
|
46
|
+
readonly config: ExecutionServiceConfig;
|
|
47
|
+
readonly nowMs: number;
|
|
48
|
+
}
|
|
49
|
+
export interface AssertedPolymarketOrder {
|
|
50
|
+
readonly order: PolymarketOrderArgs;
|
|
51
|
+
readonly typedData: PolymarketOrderTypedData;
|
|
52
|
+
readonly orderHash: Hex;
|
|
53
|
+
readonly exchange: Address;
|
|
54
|
+
readonly assertions: readonly ExecutionAssertion[];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Decode the SDK's output back into a strictly typed order and assert every fund-critical field,
|
|
58
|
+
* including the exact Otto builder bytes32 inside the hashed struct (spec §7.3).
|
|
59
|
+
*/
|
|
60
|
+
export declare function decodeAndAssertPolymarketOrder(input: PolymarketOrderAssertionInput): AssertedPolymarketOrder;
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { getAddress, hashTypedData } from 'viem';
|
|
2
|
+
import { POLYMARKET_AMOUNT_DECIMALS, POLYMARKET_CHAIN_ID, POLYMARKET_EIP712_DOMAIN_NAME, POLYMARKET_EIP712_DOMAIN_VERSION, POLYMARKET_ORDER_PRIMARY_TYPE, POLYMARKET_ORDER_V2_EIP712_FIELDS, PM_BYTES32_ZERO, } from './execution-config.js';
|
|
3
|
+
import { refuse } from './execution-errors.js';
|
|
4
|
+
import { POLYMARKET_UNSIGNED_MARKER, } from './polymarket-order-builder.js';
|
|
5
|
+
/**
|
|
6
|
+
* Assert-what-you-built for the Polymarket rail (spec §8.2): every check below runs on the order
|
|
7
|
+
* struct and EIP-712 payload the SDK actually produced — the exact bytes the user's signer will
|
|
8
|
+
* hash — never on the values this constructor intended to build.
|
|
9
|
+
*/
|
|
10
|
+
/** Price is carried as an exact integer count of 1/10,000ths, the finest tick the venue supports. */
|
|
11
|
+
export const POLYMARKET_PRICE_SCALE = 10000n;
|
|
12
|
+
const TICK_UNITS = Object.freeze({
|
|
13
|
+
'0.1': 1000n,
|
|
14
|
+
'0.01': 100n,
|
|
15
|
+
'0.001': 10n,
|
|
16
|
+
'0.0001': 1n,
|
|
17
|
+
});
|
|
18
|
+
function orderRefuse(message) {
|
|
19
|
+
return refuse('POLYMARKET_ORDER_INVALID', message);
|
|
20
|
+
}
|
|
21
|
+
/** Exact decimal-string → atomic-unit conversion. No float ever touches an amount. */
|
|
22
|
+
export function polymarketAtomicUnits(decimal, decimals = POLYMARKET_AMOUNT_DECIMALS) {
|
|
23
|
+
const [whole, fraction = ''] = decimal.split('.');
|
|
24
|
+
if (fraction.length > decimals) {
|
|
25
|
+
orderRefuse('decimal value carries more precision than the venue supports');
|
|
26
|
+
}
|
|
27
|
+
return BigInt(`${whole}${fraction.padEnd(decimals, '0')}`);
|
|
28
|
+
}
|
|
29
|
+
/** Exact price in 1/10,000ths. The caller's string form is authoritative; no rounding happens. */
|
|
30
|
+
export function polymarketPriceUnits(price) {
|
|
31
|
+
return polymarketAtomicUnits(price, 4);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The venue only accepts on-tick prices (live-verified 2026-08-17: every level of every book on a
|
|
35
|
+
* 0.01-tick market is an exact multiple of 0.01). The SDK would silently `roundNormal` an off-tick
|
|
36
|
+
* price to a different one, so an off-tick request is refused by name instead of being filled at a
|
|
37
|
+
* price the caller never asked for.
|
|
38
|
+
*/
|
|
39
|
+
export function assertPolymarketPriceOnTick(price, tickSize) {
|
|
40
|
+
const priceUnits = polymarketPriceUnits(price);
|
|
41
|
+
const tickUnits = TICK_UNITS[tickSize];
|
|
42
|
+
if (priceUnits % tickUnits !== 0n) {
|
|
43
|
+
refuse('UNSUPPORTED_POLYMARKET_MARKET', `price ${price} is not an exact multiple of the market tick size ${tickSize}`);
|
|
44
|
+
}
|
|
45
|
+
if (priceUnits < tickUnits || priceUnits > POLYMARKET_PRICE_SCALE - tickUnits) {
|
|
46
|
+
refuse('UNSUPPORTED_POLYMARKET_MARKET', `price ${price} is outside the tradable range [${tickSize}, ${1 - Number(tickSize)}] for this market`);
|
|
47
|
+
}
|
|
48
|
+
return { priceUnits, tickUnits };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The live builder registry is the value source for the ruled 0/0 bps rates (§8, Step-0 ruling).
|
|
52
|
+
* A registry row that no longer matches the ruled value means the rate moved without the ruling
|
|
53
|
+
* that governs it, so the constructor refuses instead of guessing which value is now in force — and
|
|
54
|
+
* a disabled row means the attribution would be inert, which is a missing fee, not a lower one.
|
|
55
|
+
*/
|
|
56
|
+
export function assertPolymarketBuilderAttributionRuled(rates, config) {
|
|
57
|
+
if (rates.code.toLowerCase() !== config.polymarketBuilderCode.toLowerCase()) {
|
|
58
|
+
refuse('ATTRIBUTION_MISSING', 'Polymarket builder registry row is for a different builder code');
|
|
59
|
+
}
|
|
60
|
+
if (!rates.enabled) {
|
|
61
|
+
refuse('ATTRIBUTION_MISSING', 'Otto builder code is registered but disabled: constructed orders would carry no attribution');
|
|
62
|
+
}
|
|
63
|
+
const ruled = config.polymarketRuledBuilderFeeBps;
|
|
64
|
+
if (rates.makerBps !== ruled.maker || rates.takerBps !== ruled.taker) {
|
|
65
|
+
refuse('POLYMARKET_BUILDER_FEE_UNRULED', `live builder rates ${rates.makerBps}/${rates.takerBps} bps do not match the ruled ${ruled.maker}/${ruled.taker} bps under config version ${config.polymarketBuilderFeeConfigVersion}; the rate value is a Founder ruling, not something this constructor may infer`);
|
|
66
|
+
}
|
|
67
|
+
return [
|
|
68
|
+
{ id: 'polymarket_builder_registry_enabled', result: 'passed' },
|
|
69
|
+
{ id: 'polymarket_builder_fee_matches_ruled', result: 'passed' },
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
function sameAddress(left, right) {
|
|
73
|
+
try {
|
|
74
|
+
return getAddress(left) === getAddress(right);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function requiredString(source, key) {
|
|
81
|
+
const value = source[key];
|
|
82
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
83
|
+
orderRefuse(`constructed Polymarket order is missing a string ${key}`);
|
|
84
|
+
}
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Decode the SDK's output back into a strictly typed order and assert every fund-critical field,
|
|
89
|
+
* including the exact Otto builder bytes32 inside the hashed struct (spec §7.3).
|
|
90
|
+
*/
|
|
91
|
+
export function decodeAndAssertPolymarketOrder(input) {
|
|
92
|
+
const raw = input.built.order;
|
|
93
|
+
const captured = input.built.typedData;
|
|
94
|
+
// Re-checked here as well as in the builder: a signature present on the way OUT of construction
|
|
95
|
+
// means key material entered a prepare-only path, and dropping the field on rebuild would hide
|
|
96
|
+
// that rather than report it.
|
|
97
|
+
if (raw.signature !== POLYMARKET_UNSIGNED_MARKER) {
|
|
98
|
+
orderRefuse('constructed Polymarket order carries a signature; this constructor never signs');
|
|
99
|
+
}
|
|
100
|
+
// ---- the signed struct itself -------------------------------------------------------------
|
|
101
|
+
const domain = captured.domain;
|
|
102
|
+
const types = captured.types?.Order;
|
|
103
|
+
if (!domain || !Array.isArray(types)) {
|
|
104
|
+
orderRefuse('constructed Polymarket order carries no EIP-712 domain or Order struct');
|
|
105
|
+
}
|
|
106
|
+
if (captured.primaryType !== POLYMARKET_ORDER_PRIMARY_TYPE) {
|
|
107
|
+
orderRefuse('constructed Polymarket EIP-712 payload has an unexpected primary type');
|
|
108
|
+
}
|
|
109
|
+
if (types.length !== POLYMARKET_ORDER_V2_EIP712_FIELDS.length ||
|
|
110
|
+
types.some((field, index) => {
|
|
111
|
+
const entry = field;
|
|
112
|
+
const expected = POLYMARKET_ORDER_V2_EIP712_FIELDS[index];
|
|
113
|
+
return entry.name !== expected?.name || entry.type !== expected?.type;
|
|
114
|
+
})) {
|
|
115
|
+
// The SDK's struct drifting from the pinned one would silently change what `builder` commits to.
|
|
116
|
+
orderRefuse('constructed Polymarket order struct does not match the pinned CTF Exchange V2 struct');
|
|
117
|
+
}
|
|
118
|
+
const expectedExchange = input.built.exchange;
|
|
119
|
+
if (domain.name !== POLYMARKET_EIP712_DOMAIN_NAME ||
|
|
120
|
+
domain.version !== POLYMARKET_EIP712_DOMAIN_VERSION ||
|
|
121
|
+
domain.chainId !== POLYMARKET_CHAIN_ID ||
|
|
122
|
+
typeof domain.verifyingContract !== 'string' ||
|
|
123
|
+
!sameAddress(domain.verifyingContract, expectedExchange)) {
|
|
124
|
+
orderRefuse(`constructed Polymarket EIP-712 domain is not the ${input.negRisk ? 'negRisk ' : ''}CTF Exchange V2 domain on Polygon`);
|
|
125
|
+
}
|
|
126
|
+
const message = captured.message;
|
|
127
|
+
if (!message)
|
|
128
|
+
orderRefuse('constructed Polymarket order carries no signable message');
|
|
129
|
+
// ---- attribution: the exact Otto builder code, inside the hashed struct ---------------------
|
|
130
|
+
const builder = requiredString(message, 'builder').toLowerCase();
|
|
131
|
+
if (builder === PM_BYTES32_ZERO) {
|
|
132
|
+
refuse('ATTRIBUTION_MISSING', 'constructed Polymarket order carries the zero builder code');
|
|
133
|
+
}
|
|
134
|
+
if (builder !== input.config.polymarketBuilderCode.toLowerCase()) {
|
|
135
|
+
refuse('ATTRIBUTION_MISSING', 'constructed Polymarket order does not carry the configured Otto builder code');
|
|
136
|
+
}
|
|
137
|
+
if (requiredString(raw, 'builder').toLowerCase() !== builder) {
|
|
138
|
+
refuse('ATTRIBUTION_MISSING', 'Polymarket order args and signed struct disagree on the builder code');
|
|
139
|
+
}
|
|
140
|
+
if (requiredString(message, 'metadata').toLowerCase() !== PM_BYTES32_ZERO) {
|
|
141
|
+
orderRefuse('constructed Polymarket order carries unexpected metadata');
|
|
142
|
+
}
|
|
143
|
+
// ---- account-profile roles, which live INSIDE the signed order (spec §4) --------------------
|
|
144
|
+
const maker = requiredString(message, 'maker');
|
|
145
|
+
const signer = requiredString(message, 'signer');
|
|
146
|
+
if (!sameAddress(maker, input.maker) || !sameAddress(signer, input.maker)) {
|
|
147
|
+
refuse('ACCOUNT_BINDING_MISMATCH', 'constructed Polymarket order binds a maker or signer other than the declared account');
|
|
148
|
+
}
|
|
149
|
+
if (message.signatureType !== 0) {
|
|
150
|
+
refuse('UNSUPPORTED_ACCOUNT_PROFILE', 'constructed Polymarket order does not use the EOA signature type');
|
|
151
|
+
}
|
|
152
|
+
// ---- semantic identity: market, side, amounts ------------------------------------------------
|
|
153
|
+
if (requiredString(message, 'tokenId') !== input.tokenId) {
|
|
154
|
+
orderRefuse('constructed Polymarket order names a different outcome token');
|
|
155
|
+
}
|
|
156
|
+
const expectedSide = input.side === 'BUY' ? 0 : 1;
|
|
157
|
+
if (message.side !== expectedSide || raw.side !== input.side) {
|
|
158
|
+
orderRefuse('constructed Polymarket order is on the wrong side');
|
|
159
|
+
}
|
|
160
|
+
const makerAmount = BigInt(requiredString(message, 'makerAmount'));
|
|
161
|
+
const takerAmount = BigInt(requiredString(message, 'takerAmount'));
|
|
162
|
+
if (makerAmount <= 0n || takerAmount <= 0n) {
|
|
163
|
+
orderRefuse('constructed Polymarket order carries a non-positive leg');
|
|
164
|
+
}
|
|
165
|
+
// Both inputs are exact by schema (price ≤ 4 decimals and on-tick, size ≤ 2 decimals), and the
|
|
166
|
+
// venue's rounding precision for every tick size is at least the sum of those, so the collateral
|
|
167
|
+
// leg is exactly representable and the check is equality rather than a tolerance. Anything else is
|
|
168
|
+
// the caller buying dearer or selling cheaper than they asked.
|
|
169
|
+
const collateral = (input.sizeAtomic * input.priceUnits) / POLYMARKET_PRICE_SCALE;
|
|
170
|
+
if ((input.sizeAtomic * input.priceUnits) % POLYMARKET_PRICE_SCALE !== 0n) {
|
|
171
|
+
orderRefuse('requested price and size do not produce an exact collateral amount');
|
|
172
|
+
}
|
|
173
|
+
const shares = input.side === 'BUY' ? takerAmount : makerAmount;
|
|
174
|
+
const collateralLeg = input.side === 'BUY' ? makerAmount : takerAmount;
|
|
175
|
+
if (shares !== input.sizeAtomic) {
|
|
176
|
+
orderRefuse(`constructed Polymarket order is for ${shares} share units, not the requested ${input.sizeAtomic}`);
|
|
177
|
+
}
|
|
178
|
+
if (collateralLeg !== collateral) {
|
|
179
|
+
orderRefuse(`constructed Polymarket order prices ${shares} shares at ${collateralLeg} collateral units, not the requested ${collateral}`);
|
|
180
|
+
}
|
|
181
|
+
if (shares < input.minimumOrderSizeAtomic) {
|
|
182
|
+
refuse('UNSUPPORTED_POLYMARKET_MARKET', `order size is below the market minimum of ${input.minimumOrderSizeAtomic} share units`);
|
|
183
|
+
}
|
|
184
|
+
// ---- freshness of the signed timestamp -------------------------------------------------------
|
|
185
|
+
const timestampMs = Number(requiredString(message, 'timestamp'));
|
|
186
|
+
if (!Number.isSafeInteger(timestampMs) || Math.abs(timestampMs - input.nowMs) > 600_000) {
|
|
187
|
+
// The timestamp is inside the signed struct, so a clock skew between it and the artifact's own
|
|
188
|
+
// construction time would make the envelope's freshness bound describe a different order.
|
|
189
|
+
orderRefuse('constructed Polymarket order timestamp is not within the artifact freshness ceiling');
|
|
190
|
+
}
|
|
191
|
+
if (requiredString(raw, 'expiration') !== '0') {
|
|
192
|
+
// v1 constructs GTC orders only. `expiration` is not covered by the V2 signed struct, so a
|
|
193
|
+
// non-zero value would be an unsigned promise the exchange is free to ignore.
|
|
194
|
+
orderRefuse('constructed Polymarket GTC order carries a non-zero expiration');
|
|
195
|
+
}
|
|
196
|
+
// ---- rebuild the payload from the asserted values and re-derive the digest -------------------
|
|
197
|
+
const order = {
|
|
198
|
+
salt: requiredString(raw, 'salt'),
|
|
199
|
+
maker: getAddress(maker),
|
|
200
|
+
signer: getAddress(signer),
|
|
201
|
+
tokenId: input.tokenId,
|
|
202
|
+
makerAmount: makerAmount.toString(),
|
|
203
|
+
takerAmount: takerAmount.toString(),
|
|
204
|
+
side: input.side,
|
|
205
|
+
signatureType: 0,
|
|
206
|
+
metadata: PM_BYTES32_ZERO,
|
|
207
|
+
builder: builder,
|
|
208
|
+
timestamp: requiredString(message, 'timestamp'),
|
|
209
|
+
expiration: '0',
|
|
210
|
+
};
|
|
211
|
+
if (requiredString(raw, 'salt') !== requiredString(message, 'salt')) {
|
|
212
|
+
orderRefuse('Polymarket order args and signed struct disagree on the salt');
|
|
213
|
+
}
|
|
214
|
+
const typedData = {
|
|
215
|
+
primary_type: POLYMARKET_ORDER_PRIMARY_TYPE,
|
|
216
|
+
domain: {
|
|
217
|
+
name: POLYMARKET_EIP712_DOMAIN_NAME,
|
|
218
|
+
version: POLYMARKET_EIP712_DOMAIN_VERSION,
|
|
219
|
+
chainId: POLYMARKET_CHAIN_ID,
|
|
220
|
+
verifyingContract: getAddress(expectedExchange),
|
|
221
|
+
},
|
|
222
|
+
types: { Order: POLYMARKET_ORDER_V2_EIP712_FIELDS.map((field) => ({ ...field })) },
|
|
223
|
+
message: {
|
|
224
|
+
salt: order.salt,
|
|
225
|
+
maker: order.maker,
|
|
226
|
+
signer: order.signer,
|
|
227
|
+
tokenId: order.tokenId,
|
|
228
|
+
makerAmount: order.makerAmount,
|
|
229
|
+
takerAmount: order.takerAmount,
|
|
230
|
+
side: expectedSide,
|
|
231
|
+
signatureType: 0,
|
|
232
|
+
timestamp: order.timestamp,
|
|
233
|
+
metadata: order.metadata,
|
|
234
|
+
builder: order.builder,
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
const orderHash = hashTypedData({
|
|
238
|
+
domain: { ...typedData.domain, verifyingContract: typedData.domain.verifyingContract },
|
|
239
|
+
types: { Order: [...typedData.types.Order] },
|
|
240
|
+
primaryType: typedData.primary_type,
|
|
241
|
+
message: typedData.message,
|
|
242
|
+
});
|
|
243
|
+
// The rebuilt payload must hash to exactly what the SDK's own payload hashes to. If any field
|
|
244
|
+
// above was normalized into something different from what was built, the digests diverge here.
|
|
245
|
+
const capturedHash = hashTypedData({
|
|
246
|
+
domain: domain,
|
|
247
|
+
types: { Order: types },
|
|
248
|
+
primaryType: POLYMARKET_ORDER_PRIMARY_TYPE,
|
|
249
|
+
message: message,
|
|
250
|
+
});
|
|
251
|
+
if (orderHash !== capturedHash) {
|
|
252
|
+
orderRefuse('re-derived Polymarket order digest does not match the digest the SDK produced');
|
|
253
|
+
}
|
|
254
|
+
return Object.freeze({
|
|
255
|
+
order,
|
|
256
|
+
typedData,
|
|
257
|
+
orderHash,
|
|
258
|
+
exchange: getAddress(expectedExchange),
|
|
259
|
+
assertions: Object.freeze([
|
|
260
|
+
{ id: 'polymarket_builder_code_exact', result: 'passed' },
|
|
261
|
+
{ id: 'polymarket_eip712_struct_pinned', result: 'passed' },
|
|
262
|
+
{ id: 'polymarket_eip712_domain_exact', result: 'passed' },
|
|
263
|
+
{ id: 'polymarket_order_roles_bound', result: 'passed' },
|
|
264
|
+
{ id: 'polymarket_price_on_tick', result: 'passed' },
|
|
265
|
+
{ id: 'polymarket_amounts_exact_at_price', result: 'passed' },
|
|
266
|
+
{ id: 'polymarket_size_meets_venue_minimum', result: 'passed' },
|
|
267
|
+
{ id: 'polymarket_order_unsigned', result: 'passed' },
|
|
268
|
+
{ id: 'polymarket_order_timestamp_bounded', result: 'passed' },
|
|
269
|
+
{ id: 'polymarket_order_hash_recomputed', result: 'passed' },
|
|
270
|
+
]),
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
//# sourceMappingURL=polymarket-order-assertions.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type Address, type Hex } from 'viem';
|
|
2
|
+
import { type PolymarketTickSize } from './execution-config.js';
|
|
3
|
+
/**
|
|
4
|
+
* Drives `@polymarket/clob-client-v2`'s OWN order-construction half (spec §7.3: reuse the SDK's
|
|
5
|
+
* `createOrder`/`postOrder` split) and stops before signing.
|
|
6
|
+
*
|
|
7
|
+
* The split is enforced structurally rather than by discipline: the signer handed to the SDK holds
|
|
8
|
+
* no key and cannot produce a signature. It exposes only the caller's declared address, records the
|
|
9
|
+
* exact EIP-712 payload the SDK asked it to sign, and returns a marker string that is not hex and
|
|
10
|
+
* can never be mistaken for a signature. `postOrder` is never reached — this module does not import
|
|
11
|
+
* `ClobClient`, so no credentialed surface is in scope at all.
|
|
12
|
+
*/
|
|
13
|
+
/** Returned in place of a signature. Deliberately not hex: no consumer can mistake it for one. */
|
|
14
|
+
export declare const POLYMARKET_UNSIGNED_MARKER: "otto-prepare-only-unsigned";
|
|
15
|
+
export interface PolymarketCapturedTypedData {
|
|
16
|
+
readonly domain: unknown;
|
|
17
|
+
readonly types: unknown;
|
|
18
|
+
readonly primaryType: unknown;
|
|
19
|
+
readonly message: unknown;
|
|
20
|
+
}
|
|
21
|
+
export interface UnsignedPolymarketOrderRequest {
|
|
22
|
+
readonly maker: Address;
|
|
23
|
+
readonly tokenId: string;
|
|
24
|
+
readonly side: 'BUY' | 'SELL';
|
|
25
|
+
/** Decimal probability price, already validated on-tick by the caller. */
|
|
26
|
+
readonly price: number;
|
|
27
|
+
/** Order size in shares, already validated against the venue minimum by the caller. */
|
|
28
|
+
readonly size: number;
|
|
29
|
+
readonly tickSize: PolymarketTickSize;
|
|
30
|
+
readonly negRisk: boolean;
|
|
31
|
+
readonly builderCode: Hex;
|
|
32
|
+
}
|
|
33
|
+
export interface UnsignedPolymarketOrder {
|
|
34
|
+
/** The SDK's own order object, with the marker where a signature would be. */
|
|
35
|
+
readonly order: Record<string, unknown>;
|
|
36
|
+
/** The exact EIP-712 payload the SDK handed to the signer. */
|
|
37
|
+
readonly typedData: PolymarketCapturedTypedData;
|
|
38
|
+
/** The exchange the SDK selected for this market; asserted against the domain afterwards. */
|
|
39
|
+
readonly exchange: Address;
|
|
40
|
+
}
|
|
41
|
+
/** The V2 exchange the SDK routes this market to; the EIP-712 `verifyingContract`. */
|
|
42
|
+
export declare function polymarketExchangeAddress(negRisk: boolean): Address;
|
|
43
|
+
export declare function buildUnsignedPolymarketOrder(request: UnsignedPolymarketOrderRequest): Promise<UnsignedPolymarketOrder>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { OrderBuilder, SignatureTypeV2, getContractConfig } from '@polymarket/clob-client-v2';
|
|
2
|
+
import { getAddress } from 'viem';
|
|
3
|
+
import { POLYMARKET_CHAIN_ID } from './execution-config.js';
|
|
4
|
+
import { refuse } from './execution-errors.js';
|
|
5
|
+
/**
|
|
6
|
+
* Drives `@polymarket/clob-client-v2`'s OWN order-construction half (spec §7.3: reuse the SDK's
|
|
7
|
+
* `createOrder`/`postOrder` split) and stops before signing.
|
|
8
|
+
*
|
|
9
|
+
* The split is enforced structurally rather than by discipline: the signer handed to the SDK holds
|
|
10
|
+
* no key and cannot produce a signature. It exposes only the caller's declared address, records the
|
|
11
|
+
* exact EIP-712 payload the SDK asked it to sign, and returns a marker string that is not hex and
|
|
12
|
+
* can never be mistaken for a signature. `postOrder` is never reached — this module does not import
|
|
13
|
+
* `ClobClient`, so no credentialed surface is in scope at all.
|
|
14
|
+
*/
|
|
15
|
+
/** Returned in place of a signature. Deliberately not hex: no consumer can mistake it for one. */
|
|
16
|
+
export const POLYMARKET_UNSIGNED_MARKER = 'otto-prepare-only-unsigned';
|
|
17
|
+
/**
|
|
18
|
+
* A signer in shape only. `getSignerAddress` in the SDK accepts any object exposing
|
|
19
|
+
* `signTypedData` plus an account address, which is exactly — and only — what this provides.
|
|
20
|
+
*/
|
|
21
|
+
class PolymarketPrepareOnlySigner {
|
|
22
|
+
account;
|
|
23
|
+
captured = [];
|
|
24
|
+
constructor(address) {
|
|
25
|
+
this.account = Object.freeze({ address });
|
|
26
|
+
}
|
|
27
|
+
async signTypedData(args) {
|
|
28
|
+
const { account: _account, ...payload } = args;
|
|
29
|
+
this.captured.push(payload);
|
|
30
|
+
return POLYMARKET_UNSIGNED_MARKER;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** The V2 exchange the SDK routes this market to; the EIP-712 `verifyingContract`. */
|
|
34
|
+
export function polymarketExchangeAddress(negRisk) {
|
|
35
|
+
const contracts = getContractConfig(POLYMARKET_CHAIN_ID);
|
|
36
|
+
return getAddress(negRisk ? contracts.negRiskExchangeV2 : contracts.exchangeV2);
|
|
37
|
+
}
|
|
38
|
+
export async function buildUnsignedPolymarketOrder(request) {
|
|
39
|
+
const signer = new PolymarketPrepareOnlySigner(request.maker);
|
|
40
|
+
// funderAddress === the maker: for the v1 EOA profile the account that signs is the account that
|
|
41
|
+
// holds the collateral, so the SDK's maker/signer split collapses onto one declared address.
|
|
42
|
+
const builder = new OrderBuilder(signer, POLYMARKET_CHAIN_ID, SignatureTypeV2.EOA, request.maker);
|
|
43
|
+
let order;
|
|
44
|
+
try {
|
|
45
|
+
order = (await builder.buildOrder({
|
|
46
|
+
tokenID: request.tokenId,
|
|
47
|
+
price: request.price,
|
|
48
|
+
size: request.size,
|
|
49
|
+
side: request.side,
|
|
50
|
+
// Otto attribution enters here and nowhere else; the caller has no seam to set it (§5) and
|
|
51
|
+
// the constructed struct is re-asserted against the configured value afterwards (§8).
|
|
52
|
+
builderCode: request.builderCode,
|
|
53
|
+
}, { tickSize: request.tickSize, negRisk: request.negRisk }, 2));
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
return refuse('POLYMARKET_ORDER_INVALID', `Polymarket order construction failed: ${error instanceof Error ? error.message : 'unknown error'}`);
|
|
57
|
+
}
|
|
58
|
+
const typedData = signer.captured[0];
|
|
59
|
+
if (signer.captured.length !== 1 || !typedData) {
|
|
60
|
+
// Either the SDK never reached the signing leg (no typed data to assert) or it signed more than
|
|
61
|
+
// once. Both mean the artifact would describe something other than what was built.
|
|
62
|
+
return refuse('POLYMARKET_ORDER_INVALID', 'Polymarket order construction did not produce exactly one EIP-712 payload');
|
|
63
|
+
}
|
|
64
|
+
if (order.signature !== POLYMARKET_UNSIGNED_MARKER) {
|
|
65
|
+
// A real signature here would mean key material entered a prepare-only path.
|
|
66
|
+
return refuse('POLYMARKET_ORDER_INVALID', 'Polymarket order was signed inside the constructor');
|
|
67
|
+
}
|
|
68
|
+
return Object.freeze({
|
|
69
|
+
order,
|
|
70
|
+
typedData,
|
|
71
|
+
exchange: polymarketExchangeAddress(request.negRisk),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=polymarket-order-builder.js.map
|