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
1 change: 1 addition & 0 deletions core/specs/polymarket/PolymarketClobAPI.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -954,6 +954,7 @@ paths:
# ----------------------------------------------------------------------------
/geoblock:
get:
operationId: getGeoblock
summary: Check Geoblock Status
description: Check the geographic eligibility of the requesting IP address.
tags: [System]
Expand Down
15 changes: 12 additions & 3 deletions core/src/BaseExchange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ export interface ApiEndpoint {
isPrivate?: boolean;
/** Identifier used to generate the implicit API method name. */
operationId?: string;
/**
* When set, requests use this base URL instead of the descriptor default
* (OpenAPI path- or operation-level `servers` override).
*/
baseUrl?: string;
}

export interface ApiDescriptor {
Expand Down Expand Up @@ -1229,7 +1234,11 @@ export abstract class PredictionMarketExchange {
if (name in this) {
continue;
}
(this as any)[name] = this.createImplicitMethod(name, endpoint, descriptor.baseUrl);
(this as any)[name] = this.createImplicitMethod(
name,
endpoint,
endpoint.baseUrl ?? descriptor.baseUrl
);
}
}

Expand All @@ -1255,7 +1264,7 @@ export abstract class PredictionMarketExchange {
private createImplicitMethod(
name: string,
endpoint: ApiEndpoint,
baseUrl: string
resolvedBaseUrl: string
): (params?: Record<string, any>) => Promise<any> {
return async (params?: Record<string, any>): Promise<any> => {
const allParams = { ...(params || {}) };
Expand All @@ -1279,7 +1288,7 @@ export abstract class PredictionMarketExchange {
headers = this.sign(endpoint.method, resolvedPath, allParams);
}

const url = `${baseUrl}${resolvedPath}`;
const url = `${resolvedBaseUrl}${resolvedPath}`;
const method = endpoint.method.toUpperCase();

try {
Expand Down
1 change: 1 addition & 0 deletions core/src/exchanges/polymarket/api-clob.ts
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,7 @@ export const polymarketClobSpec = {
},
"/geoblock": {
"get": {
"operationId": "getGeoblock",
"summary": "Check Geoblock Status",
"tags": [
"System"
Expand Down
22 changes: 19 additions & 3 deletions core/src/utils/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,27 @@ export function parseOpenApiSpec(spec: any, baseUrl?: string): ApiDescriptor {
const topLevelSecurity = !!(spec.security && spec.security.length > 0);

const paths = spec.paths || {};
for (const [path, methods] of Object.entries<any>(paths)) {
for (const [httpMethod, operation] of Object.entries<any>(methods)) {
// Skip non-HTTP-method keys like "parameters"
for (const [path, pathItem] of Object.entries<any>(paths)) {
if (!pathItem || typeof pathItem !== 'object') {
continue;
}
const pathServerUrl = Array.isArray(pathItem.servers) && pathItem.servers[0]?.url
? String(pathItem.servers[0].url).replace(/\/$/, '')
: undefined;

for (const [httpMethod, operation] of Object.entries<any>(pathItem)) {
// Skip non-HTTP-method keys like "parameters", "servers"
if (!['get', 'post', 'put', 'patch', 'delete'].includes(httpMethod.toLowerCase())) {
continue;
}
if (!operation || typeof operation !== 'object') {
continue;
}

const opServerUrl = Array.isArray(operation.servers) && operation.servers[0]?.url
? String(operation.servers[0].url).replace(/\/$/, '')
: undefined;
const endpointBaseUrl = opServerUrl || pathServerUrl;

const name = operation.operationId || generateMethodName(httpMethod, path);
const isPrivate = operation.security !== undefined
Expand All @@ -59,6 +74,7 @@ export function parseOpenApiSpec(spec: any, baseUrl?: string): ApiDescriptor {
path,
isPrivate,
operationId: operation.operationId,
...(endpointBaseUrl ? { baseUrl: endpointBaseUrl } : {}),
};
}
}
Expand Down
45 changes: 45 additions & 0 deletions core/test/unit/openapi.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { polymarketClobSpec } from '../../src/exchanges/polymarket/api-clob';
import { parseOpenApiSpec } from '../../src/utils/openapi';

describe('parseOpenApiSpec', () => {
it('Polymarket getGeoblock uses website API host (not CLOB)', () => {
const d = parseOpenApiSpec(polymarketClobSpec);
expect(d.endpoints.getGeoblock.baseUrl).toBe('https://polymarket.com/api');
expect(d.endpoints.getGeoblock.path).toBe('/geoblock');
});

it('uses operation-level servers as endpoint baseUrl override', () => {
const spec = {
openapi: '3.0.3',
servers: [{ url: 'https://primary.example.com' }],
paths: {
'/x': {
get: {
operationId: 'getX',
servers: [{ url: 'https://override.example.com/' }],
},
},
},
};

const d = parseOpenApiSpec(spec);
expect(d.baseUrl).toBe('https://primary.example.com');
expect(d.endpoints.getX.baseUrl).toBe('https://override.example.com');
});

it('inherits path-level servers when the operation omits servers', () => {
const spec = {
openapi: '3.0.3',
servers: [{ url: 'https://primary.example.com' }],
paths: {
'/y': {
servers: [{ url: 'https://path-level.example.com' }],
get: { operationId: 'getY' },
},
},
};

const d = parseOpenApiSpec(spec);
expect(d.endpoints.getY.baseUrl).toBe('https://path-level.example.com');
});
});
Loading