Skip to content
Open
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
36 changes: 18 additions & 18 deletions core/api-doc-config.generated.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"_generated": "Auto-generated by extract-jsdoc.js on 2026-06-02T00:34:44.916Z. Do not edit manually.",
"_generated": "Auto-generated by extract-jsdoc.js on 2026-06-05T14:19:30.044Z. Do not edit manually.",
"methods": {
"has": {
"summary": "HTTP verb for the endpoint (e.g. GET, POST). */",
Expand Down Expand Up @@ -566,7 +566,7 @@
"type": "UnifiedEvent[]",
"description": "Filtered array of events"
},
"source": "BaseExchange.ts:1317"
"source": "BaseExchange.ts:1318"
},
"watchOrderBook": {
"summary": "Watch order book updates in real-time via WebSocket.",
Expand Down Expand Up @@ -595,7 +595,7 @@
"type": "OrderBook",
"description": "Promise that resolves with the current orderbook state"
},
"source": "BaseExchange.ts:1413"
"source": "BaseExchange.ts:1414"
},
"watchOrderBooks": {
"summary": "Watch multiple order books simultaneously via WebSocket.",
Expand Down Expand Up @@ -624,7 +624,7 @@
"type": "Record<string, OrderBook>",
"description": "Promise that resolves with order books keyed by ID"
},
"source": "BaseExchange.ts:1426"
"source": "BaseExchange.ts:1427"
},
"unwatchOrderBook": {
"summary": "Unsubscribe from a previously watched order book stream.",
Expand All @@ -641,7 +641,7 @@
"type": "void",
"description": "Result"
},
"source": "BaseExchange.ts:1454"
"source": "BaseExchange.ts:1455"
},
"watchTrades": {
"summary": "Watch trade executions in real-time via WebSocket.",
Expand Down Expand Up @@ -676,7 +676,7 @@
"type": "Trade[]",
"description": "Promise that resolves with recent trades"
},
"source": "BaseExchange.ts:1467"
"source": "BaseExchange.ts:1468"
},
"watchAddress": {
"summary": "Stream activity for a public wallet address",
Expand All @@ -699,7 +699,7 @@
"type": "SubscribedAddressSnapshot",
"description": "Promise that resolves with the latest SubscribedAddressSnapshot snapshot"
},
"source": "BaseExchange.ts:1481"
"source": "BaseExchange.ts:1482"
},
"unwatchAddress": {
"summary": "Stop watching a previously registered wallet address and release its resource updates.",
Expand All @@ -716,7 +716,7 @@
"type": "void",
"description": "Result"
},
"source": "BaseExchange.ts:1494"
"source": "BaseExchange.ts:1495"
},
"close": {
"summary": "Close all WebSocket connections and clean up resources.",
Expand All @@ -726,7 +726,7 @@
"type": "void",
"description": "Result"
},
"source": "BaseExchange.ts:1503"
"source": "BaseExchange.ts:1504"
},
"fetchMarketMatches": {
"summary": "Find the same or related market on other venues. Two modes:",
Expand All @@ -743,7 +743,7 @@
"type": "MatchResult[]",
"description": "Array of matched markets with relation and confidence"
},
"source": "BaseExchange.ts:1517"
"source": "BaseExchange.ts:1518"
},
"fetchMatches": {
"summary": "fetchMatches",
Expand All @@ -760,7 +760,7 @@
"type": "MatchResult[]",
"description": "Result"
},
"source": "BaseExchange.ts:1533"
"source": "BaseExchange.ts:1534"
},
"fetchEventMatches": {
"summary": "Find the same or related event on other venues. Two modes:",
Expand All @@ -777,7 +777,7 @@
"type": "EventMatchResult[]",
"description": "Array of matched events with market-level match details"
},
"source": "BaseExchange.ts:1541"
"source": "BaseExchange.ts:1542"
},
"compareMarketPrices": {
"summary": "Compare live prices for the same market across venues. Finds identity matches and returns side-by-side best bid/ask prices so you can spot price differences at a glance.",
Expand All @@ -794,7 +794,7 @@
"type": "PriceComparison[]",
"description": "Array of price comparisons across venues"
},
"source": "BaseExchange.ts:1557"
"source": "BaseExchange.ts:1558"
},
"fetchRelatedMarkets": {
"summary": "Find related markets across venues. Discovers subset/superset market relationships",
Expand All @@ -811,7 +811,7 @@
"type": "PriceComparison[]",
"description": "Array of subset/superset matches with live prices"
},
"source": "BaseExchange.ts:1567"
"source": "BaseExchange.ts:1568"
},
"fetchMatchedMarkets": {
"summary": "fetchMatchedMarkets",
Expand All @@ -828,7 +828,7 @@
"type": "MatchedMarketPair[]",
"description": "Result"
},
"source": "BaseExchange.ts:1578"
"source": "BaseExchange.ts:1579"
},
"fetchMatchedPrices": {
"summary": "fetchMatchedPrices",
Expand All @@ -845,7 +845,7 @@
"type": "MatchedPricePair[]",
"description": "Array of matched market pairs with prices from each venue"
},
"source": "BaseExchange.ts:1586"
"source": "BaseExchange.ts:1587"
},
"fetchHedges": {
"summary": "fetchHedges",
Expand All @@ -862,7 +862,7 @@
"type": "PriceComparison[]",
"description": "Array of subset/superset matches with live prices"
},
"source": "BaseExchange.ts:1597"
"source": "BaseExchange.ts:1598"
},
"fetchArbitrage": {
"summary": "fetchArbitrage",
Expand All @@ -879,7 +879,7 @@
"type": "ArbitrageOpportunity[]",
"description": "Array of arbitrage opportunities sorted by spread"
},
"source": "BaseExchange.ts:1607"
"source": "BaseExchange.ts:1608"
},
"watchPrices": {
"summary": "Watch AMM price updates for a market address (Limitless only).",
Expand Down
2 changes: 1 addition & 1 deletion sdks/python/API_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1456,7 +1456,7 @@ title: str # The market title (e.g., "Will BTC close above $100k on Dec 31?").
description: str # Long-form market description or resolution criteria.
slug: str # URL-friendly slug for the market.
outcomes: List[MarketOutcome] # The possible outcomes for this market.
resolution_date: str # When the market is scheduled to resolve.
resolution_date: str # When the market is scheduled to resolve. Optional because some venues do not publish a cutoff for every market (e.g. Opinion categorical children) — emit `undefined` rather than coercing to epoch.
volume24h: float # Trading volume over the past 24 hours (USD).
volume: float # Total / Lifetime volume
liquidity: float # Current market liquidity (USD).
Expand Down
15 changes: 10 additions & 5 deletions sdks/python/pmxt/feed_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ def __init__(
if api_key:
self._headers["Authorization"] = f"Bearer {api_key}"

def list_feeds(self) -> List[str]:
return self._request(f"{self._base_url}/api/feeds/")

def load_markets(self) -> Dict[str, Market]:
data = self._get("loadMarkets", {})
return {
Expand Down Expand Up @@ -172,13 +175,15 @@ def fetch_historical_prices(
return [self._to_ticker(r) for r in data]

def _get(self, method: str, params: Dict[str, Any]) -> Any:
filtered = {k: v for k, v in params.items() if v is not None}
qs = urllib.parse.urlencode(filtered) if filtered else ""
url = f"{self._base_url}/api/feeds/{self._feed_name}/{method}"
if qs:
url += f"?{qs}"
return self._request(url, params)

def _request(self, url: str, params: Optional[Dict[str, Any]] = None) -> Any:
filtered = {k: v for k, v in (params or {}).items() if v is not None}
qs = urllib.parse.urlencode(filtered) if filtered else ""
request_url = f"{url}?{qs}" if qs else url

req = urllib.request.Request(url, headers=self._headers)
req = urllib.request.Request(request_url, headers=self._headers)
try:
with urllib.request.urlopen(req, timeout=15) as resp:
body = json.loads(resp.read())
Expand Down
37 changes: 37 additions & 0 deletions sdks/python/tests/test_feed_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import json
import urllib.request

from pmxt.feed_client import FeedClient


class _FakeResponse:
def __init__(self, payload):
self._payload = payload

def __enter__(self):
return self

def __exit__(self, exc_type, exc, tb):
return False

def read(self):
return json.dumps(self._payload).encode("utf-8")


def test_list_feeds_hits_the_root_endpoint(monkeypatch):
captured = {}

def fake_urlopen(req, timeout=15):
captured["url"] = req.full_url
captured["headers"] = dict(req.header_items())
captured["timeout"] = timeout
return _FakeResponse({"success": True, "data": ["binance", "chainlink"]})

monkeypatch.setattr(urllib.request, "urlopen", fake_urlopen)

client = FeedClient("binance", base_url="http://localhost:3847")

assert client.list_feeds() == ["binance", "chainlink"]
assert captured["url"] == "http://localhost:3847/api/feeds/"
assert captured["timeout"] == 15
assert captured["headers"] == {}
2 changes: 1 addition & 1 deletion sdks/typescript/API_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1456,7 +1456,7 @@ title: string; // The market title (e.g., "Will BTC close above $100k on Dec 31?
description: string; // Long-form market description or resolution criteria.
slug: string; // URL-friendly slug for the market.
outcomes: MarketOutcome[]; // The possible outcomes for this market.
resolutionDate: string; // When the market is scheduled to resolve.
resolutionDate: string; // When the market is scheduled to resolve. Optional because some venues do not publish a cutoff for every market (e.g. Opinion categorical children) — emit `undefined` rather than coercing to epoch.
volume24h: number; // Trading volume over the past 24 hours (USD).
volume: number; // Total / Lifetime volume
liquidity: number; // Current market liquidity (USD).
Expand Down
16 changes: 14 additions & 2 deletions sdks/typescript/pmxt/feed-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ export class FeedClient {
};
}

async listFeeds(): Promise<string[]> {
return this.getRoot<string[]>();
}

async loadMarkets(): Promise<Record<string, Market>> {
return this.get<Record<string, Market>>('loadMarkets', {});
}
Expand Down Expand Up @@ -118,14 +122,22 @@ export class FeedClient {
}

private async get<T>(method: string, params: Record<string, unknown>): Promise<T> {
return this.request<T>(`${this.baseUrl}/api/feeds/${this.feedName}/${method}`, params);
}

private async getRoot<T>(params: Record<string, unknown> = {}): Promise<T> {
return this.request<T>(`${this.baseUrl}/api/feeds/`, params);
}

private async request<T>(url: string, params: Record<string, unknown>): Promise<T> {
const qs = Object.entries(params)
.filter(([, v]) => v !== undefined && v !== null)
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)
.join('&');

const url = `${this.baseUrl}/api/feeds/${this.feedName}/${method}${qs ? '?' + qs : ''}`;
const requestUrl = `${url}${qs ? '?' + qs : ''}`;

const response = await fetch(url, {
const response = await fetch(requestUrl, {
headers: this.headers,
signal: AbortSignal.timeout(30_000),
});
Expand Down
28 changes: 28 additions & 0 deletions sdks/typescript/tests/feed-client.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { FeedClient } from '../pmxt/feed-client';

describe('FeedClient.listFeeds', () => {
afterEach(() => {
jest.restoreAllMocks();
});

it('requests the feeds index and returns the available feed names', async () => {
const fetchMock = jest.spyOn(globalThis, 'fetch').mockResolvedValue({
ok: true,
json: jest.fn().mockResolvedValue({
success: true,
data: ['binance', 'chainlink'],
}),
} as any);

const client = new FeedClient('binance', { baseUrl: 'http://localhost:3847' });
await expect(client.listFeeds()).resolves.toEqual(['binance', 'chainlink']);
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(fetchMock).toHaveBeenCalledWith(
'http://localhost:3847/api/feeds/',
expect.objectContaining({
headers: {},
}),
);
fetchMock.mockRestore();
});
});
Loading