pmxt-core 2.39.0 → 2.40.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/dist/exchanges/baozi/fetcher.js +28 -8
- package/dist/exchanges/baozi/index.js +6 -4
- package/dist/exchanges/gemini-titan/auth.d.ts +34 -0
- package/dist/exchanges/gemini-titan/auth.js +80 -0
- package/dist/exchanges/gemini-titan/config.d.ts +15 -0
- package/dist/exchanges/gemini-titan/config.js +24 -0
- package/dist/exchanges/gemini-titan/errors.d.ts +20 -0
- package/dist/exchanges/gemini-titan/errors.js +75 -0
- package/dist/exchanges/gemini-titan/fetcher.d.ts +26 -0
- package/dist/exchanges/gemini-titan/fetcher.js +148 -0
- package/dist/exchanges/gemini-titan/index.d.ts +31 -0
- package/dist/exchanges/gemini-titan/index.js +188 -0
- package/dist/exchanges/gemini-titan/normalizer.d.ts +13 -0
- package/dist/exchanges/gemini-titan/normalizer.js +229 -0
- package/dist/exchanges/gemini-titan/types.d.ts +220 -0
- package/dist/exchanges/gemini-titan/types.js +6 -0
- package/dist/exchanges/gemini-titan/utils.d.ts +30 -0
- package/dist/exchanges/gemini-titan/utils.js +57 -0
- package/dist/exchanges/gemini-titan/websocket.d.ts +46 -0
- package/dist/exchanges/gemini-titan/websocket.js +295 -0
- package/dist/exchanges/kalshi/api.d.ts +1 -1
- package/dist/exchanges/kalshi/api.js +1 -1
- package/dist/exchanges/kalshi/fetcher.js +6 -2
- package/dist/exchanges/limitless/api.d.ts +1 -1
- package/dist/exchanges/limitless/api.js +1 -1
- package/dist/exchanges/limitless/index.js +3 -6
- package/dist/exchanges/metaculus/fetchEvents.js +7 -2
- package/dist/exchanges/mock/index.d.ts +55 -0
- package/dist/exchanges/mock/index.js +603 -0
- package/dist/exchanges/mock/seededRng.d.ts +10 -0
- package/dist/exchanges/mock/seededRng.js +48 -0
- package/dist/exchanges/myriad/api.d.ts +1 -1
- package/dist/exchanges/myriad/api.js +1 -1
- package/dist/exchanges/myriad/websocket.d.ts +4 -0
- package/dist/exchanges/myriad/websocket.js +51 -6
- package/dist/exchanges/opinion/api.d.ts +1 -1
- package/dist/exchanges/opinion/api.js +1 -1
- package/dist/exchanges/polymarket/api-clob.d.ts +1 -1
- package/dist/exchanges/polymarket/api-clob.js +1 -1
- package/dist/exchanges/polymarket/api-data.d.ts +1 -1
- package/dist/exchanges/polymarket/api-data.js +1 -1
- package/dist/exchanges/polymarket/api-gamma.d.ts +1 -1
- package/dist/exchanges/polymarket/api-gamma.js +1 -1
- package/dist/exchanges/polymarket/auth.js +5 -2
- package/dist/exchanges/polymarket/index.d.ts +2 -2
- package/dist/exchanges/polymarket/index.js +2 -1
- package/dist/exchanges/polymarket/websocket.d.ts +51 -0
- package/dist/exchanges/polymarket/websocket.js +125 -0
- package/dist/exchanges/polymarket_us/normalizer.js +5 -1
- package/dist/exchanges/probable/api.d.ts +1 -1
- package/dist/exchanges/probable/api.js +1 -1
- package/dist/exchanges/probable/index.js +9 -6
- package/dist/exchanges/smarkets/fetcher.js +6 -2
- package/dist/index.d.ts +8 -0
- package/dist/index.js +9 -1
- package/dist/router/Router.js +55 -21
- package/dist/server/exchange-factory.js +9 -0
- package/dist/server/openapi.yaml +22 -0
- package/dist/server/ws-handler.js +13 -3
- package/package.json +3 -3
- package/dist/exchanges/baozi/price.test.d.ts +0 -1
- package/dist/exchanges/baozi/price.test.js +0 -33
- package/dist/exchanges/kalshi/kalshi.test.d.ts +0 -1
- package/dist/exchanges/kalshi/kalshi.test.js +0 -641
- package/dist/exchanges/kalshi/price.test.d.ts +0 -1
- package/dist/exchanges/kalshi/price.test.js +0 -24
- package/dist/exchanges/myriad/price.test.d.ts +0 -1
- package/dist/exchanges/myriad/price.test.js +0 -17
- package/dist/exchanges/polymarket_us/errors.test.d.ts +0 -1
- package/dist/exchanges/polymarket_us/errors.test.js +0 -54
- package/dist/exchanges/polymarket_us/index.test.d.ts +0 -8
- package/dist/exchanges/polymarket_us/index.test.js +0 -237
- package/dist/exchanges/polymarket_us/normalizer.test.d.ts +0 -1
- package/dist/exchanges/polymarket_us/normalizer.test.js +0 -224
- package/dist/exchanges/polymarket_us/price.test.d.ts +0 -1
- package/dist/exchanges/polymarket_us/price.test.js +0 -131
- package/dist/exchanges/polymarket_us/websocket.test.d.ts +0 -8
- package/dist/exchanges/polymarket_us/websocket.test.js +0 -162
- package/dist/exchanges/smarkets/price.test.d.ts +0 -1
- package/dist/exchanges/smarkets/price.test.js +0 -50
- package/dist/router/Router.test.d.ts +0 -1
- package/dist/router/Router.test.js +0 -328
- package/dist/router/client.test.d.ts +0 -1
- package/dist/router/client.test.js +0 -177
|
@@ -36,24 +36,34 @@ class BaoziFetcher {
|
|
|
36
36
|
}),
|
|
37
37
|
]);
|
|
38
38
|
const markets = [];
|
|
39
|
+
let booleanSkipped = 0;
|
|
39
40
|
for (const account of booleanAccounts) {
|
|
40
41
|
try {
|
|
41
42
|
const parsed = (0, utils_1.parseMarket)(account.account.data);
|
|
42
43
|
markets.push({ pubkey: account.pubkey.toString(), parsed });
|
|
43
44
|
}
|
|
44
|
-
catch {
|
|
45
|
-
|
|
45
|
+
catch (parseError) {
|
|
46
|
+
booleanSkipped++;
|
|
47
|
+
console.warn(`[Baozi] fetchRawMarkets: failed to parse boolean market account pubkey=${account.pubkey.toString()}:`, parseError);
|
|
46
48
|
}
|
|
47
49
|
}
|
|
50
|
+
if (booleanSkipped > 0) {
|
|
51
|
+
console.warn(`[Baozi] fetchRawMarkets: skipped ${booleanSkipped} malformed boolean market account(s) out of ${booleanAccounts.length}`);
|
|
52
|
+
}
|
|
53
|
+
let raceSkipped = 0;
|
|
48
54
|
for (const account of raceAccounts) {
|
|
49
55
|
try {
|
|
50
56
|
const parsed = (0, utils_1.parseRaceMarket)(account.account.data);
|
|
51
57
|
markets.push({ pubkey: account.pubkey.toString(), parsed });
|
|
52
58
|
}
|
|
53
|
-
catch {
|
|
54
|
-
|
|
59
|
+
catch (parseError) {
|
|
60
|
+
raceSkipped++;
|
|
61
|
+
console.warn(`[Baozi] fetchRawMarkets: failed to parse race market account pubkey=${account.pubkey.toString()}:`, parseError);
|
|
55
62
|
}
|
|
56
63
|
}
|
|
64
|
+
if (raceSkipped > 0) {
|
|
65
|
+
console.warn(`[Baozi] fetchRawMarkets: skipped ${raceSkipped} malformed race market account(s) out of ${raceAccounts.length}`);
|
|
66
|
+
}
|
|
57
67
|
marketsCache.set(markets);
|
|
58
68
|
return markets;
|
|
59
69
|
}
|
|
@@ -114,25 +124,35 @@ class BaoziFetcher {
|
|
|
114
124
|
}),
|
|
115
125
|
]);
|
|
116
126
|
const booleanPositions = [];
|
|
127
|
+
let boolPosSkipped = 0;
|
|
117
128
|
for (const account of booleanAccounts) {
|
|
118
129
|
try {
|
|
119
130
|
const parsed = (0, utils_1.parseUserPosition)(account.account.data);
|
|
120
131
|
booleanPositions.push({ pubkey: account.pubkey.toString(), parsed });
|
|
121
132
|
}
|
|
122
|
-
catch {
|
|
123
|
-
|
|
133
|
+
catch (parseError) {
|
|
134
|
+
boolPosSkipped++;
|
|
135
|
+
console.warn(`[Baozi] fetchRawUserPositions: failed to parse boolean position account pubkey=${account.pubkey.toString()}:`, parseError);
|
|
124
136
|
}
|
|
125
137
|
}
|
|
138
|
+
if (boolPosSkipped > 0) {
|
|
139
|
+
console.warn(`[Baozi] fetchRawUserPositions: skipped ${boolPosSkipped} malformed boolean position account(s) out of ${booleanAccounts.length}`);
|
|
140
|
+
}
|
|
126
141
|
const racePositions = [];
|
|
142
|
+
let racePosSkipped = 0;
|
|
127
143
|
for (const account of raceAccounts) {
|
|
128
144
|
try {
|
|
129
145
|
const parsed = (0, utils_1.parseRacePosition)(account.account.data);
|
|
130
146
|
racePositions.push({ pubkey: account.pubkey.toString(), parsed });
|
|
131
147
|
}
|
|
132
|
-
catch {
|
|
133
|
-
|
|
148
|
+
catch (parseError) {
|
|
149
|
+
racePosSkipped++;
|
|
150
|
+
console.warn(`[Baozi] fetchRawUserPositions: failed to parse race position account pubkey=${account.pubkey.toString()}:`, parseError);
|
|
134
151
|
}
|
|
135
152
|
}
|
|
153
|
+
if (racePosSkipped > 0) {
|
|
154
|
+
console.warn(`[Baozi] fetchRawUserPositions: skipped ${racePosSkipped} malformed race position account(s) out of ${raceAccounts.length}`);
|
|
155
|
+
}
|
|
136
156
|
return { booleanPositions, racePositions };
|
|
137
157
|
}
|
|
138
158
|
catch (error) {
|
|
@@ -108,8 +108,9 @@ class BaoziExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
|
108
108
|
marketLookup.set(marketPdaStr, unified);
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
|
-
catch {
|
|
112
|
-
|
|
111
|
+
catch (error) {
|
|
112
|
+
console.warn(`[Baozi] fetchPositions: failed to fetch boolean market account for PDA=${marketPdaStr}:`, error);
|
|
113
|
+
throw error;
|
|
113
114
|
}
|
|
114
115
|
}
|
|
115
116
|
}
|
|
@@ -127,8 +128,9 @@ class BaoziExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
|
127
128
|
marketLookup.set(racePdaStr, unified);
|
|
128
129
|
}
|
|
129
130
|
}
|
|
130
|
-
catch {
|
|
131
|
-
|
|
131
|
+
catch (error) {
|
|
132
|
+
console.warn(`[Baozi] fetchPositions: failed to fetch race market account for PDA=${racePdaStr}:`, error);
|
|
133
|
+
throw error;
|
|
132
134
|
}
|
|
133
135
|
}
|
|
134
136
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ExchangeCredentials } from '../../BaseExchange';
|
|
2
|
+
/**
|
|
3
|
+
* Gemini HMAC-SHA384 authentication.
|
|
4
|
+
*
|
|
5
|
+
* Gemini's prediction market API uses header-based auth where the POST body
|
|
6
|
+
* is always empty — the actual payload is base64-encoded and placed in the
|
|
7
|
+
* X-GEMINI-PAYLOAD header, with an HMAC-SHA384 signature.
|
|
8
|
+
*/
|
|
9
|
+
export declare class GeminiAuth {
|
|
10
|
+
private readonly apiKey;
|
|
11
|
+
private readonly apiSecret;
|
|
12
|
+
private lastNonce;
|
|
13
|
+
constructor(credentials: ExchangeCredentials);
|
|
14
|
+
/**
|
|
15
|
+
* Generate a strictly monotonic nonce.
|
|
16
|
+
* Uses Date.now() but ensures it never repeats even if called
|
|
17
|
+
* within the same millisecond.
|
|
18
|
+
*/
|
|
19
|
+
nonce(): number;
|
|
20
|
+
/**
|
|
21
|
+
* Build the three authentication headers for a given payload object.
|
|
22
|
+
*
|
|
23
|
+
* The payload must include `request` (endpoint path) and `nonce`.
|
|
24
|
+
* Additional fields (symbol, side, etc.) are included for order requests.
|
|
25
|
+
*/
|
|
26
|
+
buildHeaders(payload: Record<string, unknown>): Record<string, string>;
|
|
27
|
+
/**
|
|
28
|
+
* Build WebSocket handshake authentication headers.
|
|
29
|
+
*
|
|
30
|
+
* WebSocket auth uses a time-based nonce (seconds since epoch)
|
|
31
|
+
* as the payload, not a JSON object.
|
|
32
|
+
*/
|
|
33
|
+
buildWsHeaders(): Record<string, string>;
|
|
34
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.GeminiAuth = void 0;
|
|
7
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
8
|
+
const errors_1 = require("../../errors");
|
|
9
|
+
/**
|
|
10
|
+
* Gemini HMAC-SHA384 authentication.
|
|
11
|
+
*
|
|
12
|
+
* Gemini's prediction market API uses header-based auth where the POST body
|
|
13
|
+
* is always empty — the actual payload is base64-encoded and placed in the
|
|
14
|
+
* X-GEMINI-PAYLOAD header, with an HMAC-SHA384 signature.
|
|
15
|
+
*/
|
|
16
|
+
class GeminiAuth {
|
|
17
|
+
apiKey;
|
|
18
|
+
apiSecret;
|
|
19
|
+
lastNonce = 0;
|
|
20
|
+
constructor(credentials) {
|
|
21
|
+
if (!credentials.apiKey || !credentials.apiSecret) {
|
|
22
|
+
throw new errors_1.AuthenticationError('Gemini Titan trading requires both apiKey and apiSecret.', 'GeminiTitan');
|
|
23
|
+
}
|
|
24
|
+
this.apiKey = credentials.apiKey;
|
|
25
|
+
this.apiSecret = credentials.apiSecret;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Generate a strictly monotonic nonce.
|
|
29
|
+
* Uses Date.now() but ensures it never repeats even if called
|
|
30
|
+
* within the same millisecond.
|
|
31
|
+
*/
|
|
32
|
+
nonce() {
|
|
33
|
+
const now = Date.now();
|
|
34
|
+
this.lastNonce = Math.max(now, this.lastNonce + 1);
|
|
35
|
+
return this.lastNonce;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Build the three authentication headers for a given payload object.
|
|
39
|
+
*
|
|
40
|
+
* The payload must include `request` (endpoint path) and `nonce`.
|
|
41
|
+
* Additional fields (symbol, side, etc.) are included for order requests.
|
|
42
|
+
*/
|
|
43
|
+
buildHeaders(payload) {
|
|
44
|
+
const jsonPayload = JSON.stringify(payload);
|
|
45
|
+
const b64Payload = Buffer.from(jsonPayload).toString('base64');
|
|
46
|
+
const signature = crypto_1.default
|
|
47
|
+
.createHmac('sha384', this.apiSecret)
|
|
48
|
+
.update(b64Payload)
|
|
49
|
+
.digest('hex');
|
|
50
|
+
return {
|
|
51
|
+
'Content-Type': 'text/plain',
|
|
52
|
+
'Content-Length': '0',
|
|
53
|
+
'X-GEMINI-APIKEY': this.apiKey,
|
|
54
|
+
'X-GEMINI-PAYLOAD': b64Payload,
|
|
55
|
+
'X-GEMINI-SIGNATURE': signature,
|
|
56
|
+
'Cache-Control': 'no-cache',
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Build WebSocket handshake authentication headers.
|
|
61
|
+
*
|
|
62
|
+
* WebSocket auth uses a time-based nonce (seconds since epoch)
|
|
63
|
+
* as the payload, not a JSON object.
|
|
64
|
+
*/
|
|
65
|
+
buildWsHeaders() {
|
|
66
|
+
const nonce = Math.floor(Date.now() / 1000).toString();
|
|
67
|
+
const b64Payload = Buffer.from(nonce).toString('base64');
|
|
68
|
+
const signature = crypto_1.default
|
|
69
|
+
.createHmac('sha384', this.apiSecret)
|
|
70
|
+
.update(b64Payload)
|
|
71
|
+
.digest('hex');
|
|
72
|
+
return {
|
|
73
|
+
'X-GEMINI-APIKEY': this.apiKey,
|
|
74
|
+
'X-GEMINI-NONCE': nonce,
|
|
75
|
+
'X-GEMINI-PAYLOAD': b64Payload,
|
|
76
|
+
'X-GEMINI-SIGNATURE': signature,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
exports.GeminiAuth = GeminiAuth;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const DEFAULT_GEMINI_BASE_URL = "https://api.gemini.com";
|
|
2
|
+
export declare const GEMINI_SANDBOX_BASE_URL = "https://api.sandbox.gemini.com";
|
|
3
|
+
export declare const GEMINI_WS_URL = "wss://ws.gemini.com";
|
|
4
|
+
export declare const GEMINI_SANDBOX_WS_URL = "wss://ws.sandbox.gemini.com";
|
|
5
|
+
export declare const INSTRUMENT_PREFIX = "GEMI-";
|
|
6
|
+
export declare const MIN_PRICE = 0.01;
|
|
7
|
+
export declare const MAX_PRICE = 0.99;
|
|
8
|
+
export declare const TICK_SIZE = 0.01;
|
|
9
|
+
export declare const PAYOUT_PER_CONTRACT = 1;
|
|
10
|
+
export interface GeminiApiConfig {
|
|
11
|
+
baseUrl: string;
|
|
12
|
+
wsUrl: string;
|
|
13
|
+
sandbox: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function getGeminiConfig(baseUrlOverride?: string, sandbox?: boolean): GeminiApiConfig;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PAYOUT_PER_CONTRACT = exports.TICK_SIZE = exports.MAX_PRICE = exports.MIN_PRICE = exports.INSTRUMENT_PREFIX = exports.GEMINI_SANDBOX_WS_URL = exports.GEMINI_WS_URL = exports.GEMINI_SANDBOX_BASE_URL = exports.DEFAULT_GEMINI_BASE_URL = void 0;
|
|
4
|
+
exports.getGeminiConfig = getGeminiConfig;
|
|
5
|
+
exports.DEFAULT_GEMINI_BASE_URL = 'https://api.gemini.com';
|
|
6
|
+
exports.GEMINI_SANDBOX_BASE_URL = 'https://api.sandbox.gemini.com';
|
|
7
|
+
exports.GEMINI_WS_URL = 'wss://ws.gemini.com';
|
|
8
|
+
exports.GEMINI_SANDBOX_WS_URL = 'wss://ws.sandbox.gemini.com';
|
|
9
|
+
// All Gemini prediction market instrument symbols use this prefix
|
|
10
|
+
exports.INSTRUMENT_PREFIX = 'GEMI-';
|
|
11
|
+
// Gemini prediction market prices are in the range [0.01, 0.99]
|
|
12
|
+
exports.MIN_PRICE = 0.01;
|
|
13
|
+
exports.MAX_PRICE = 0.99;
|
|
14
|
+
exports.TICK_SIZE = 0.01;
|
|
15
|
+
// Payout is always $1.00 per winning contract
|
|
16
|
+
exports.PAYOUT_PER_CONTRACT = 1.0;
|
|
17
|
+
function getGeminiConfig(baseUrlOverride, sandbox) {
|
|
18
|
+
const isSandbox = sandbox ?? false;
|
|
19
|
+
return {
|
|
20
|
+
baseUrl: baseUrlOverride ?? (isSandbox ? exports.GEMINI_SANDBOX_BASE_URL : exports.DEFAULT_GEMINI_BASE_URL),
|
|
21
|
+
wsUrl: isSandbox ? exports.GEMINI_SANDBOX_WS_URL : exports.GEMINI_WS_URL,
|
|
22
|
+
sandbox: isSandbox,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ErrorMapper } from '../../utils/error-mapper';
|
|
2
|
+
import { BadRequest } from '../../errors';
|
|
3
|
+
/**
|
|
4
|
+
* Maps Gemini Titan API errors to PMXT unified error classes.
|
|
5
|
+
*
|
|
6
|
+
* Gemini returns errors as JSON:
|
|
7
|
+
* { result: "error", reason: "InvalidSignature", message: "..." }
|
|
8
|
+
*
|
|
9
|
+
* Common reasons:
|
|
10
|
+
* - InvalidSignature -> AuthenticationError
|
|
11
|
+
* - InsufficientFunds -> InsufficientFunds
|
|
12
|
+
* - InvalidQuantity, InvalidPrice, MarketNotOpen -> InvalidOrder
|
|
13
|
+
*/
|
|
14
|
+
export declare class GeminiErrorMapper extends ErrorMapper {
|
|
15
|
+
constructor();
|
|
16
|
+
protected extractErrorMessage(error: any): string;
|
|
17
|
+
protected mapBadRequestError(message: string, data: any): BadRequest;
|
|
18
|
+
mapError(error: any): ReturnType<ErrorMapper['mapError']>;
|
|
19
|
+
}
|
|
20
|
+
export declare const geminiErrorMapper: GeminiErrorMapper;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.geminiErrorMapper = exports.GeminiErrorMapper = void 0;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const error_mapper_1 = require("../../utils/error-mapper");
|
|
9
|
+
const errors_1 = require("../../errors");
|
|
10
|
+
/**
|
|
11
|
+
* Maps Gemini Titan API errors to PMXT unified error classes.
|
|
12
|
+
*
|
|
13
|
+
* Gemini returns errors as JSON:
|
|
14
|
+
* { result: "error", reason: "InvalidSignature", message: "..." }
|
|
15
|
+
*
|
|
16
|
+
* Common reasons:
|
|
17
|
+
* - InvalidSignature -> AuthenticationError
|
|
18
|
+
* - InsufficientFunds -> InsufficientFunds
|
|
19
|
+
* - InvalidQuantity, InvalidPrice, MarketNotOpen -> InvalidOrder
|
|
20
|
+
*/
|
|
21
|
+
class GeminiErrorMapper extends error_mapper_1.ErrorMapper {
|
|
22
|
+
constructor() {
|
|
23
|
+
super('GeminiTitan');
|
|
24
|
+
}
|
|
25
|
+
extractErrorMessage(error) {
|
|
26
|
+
if (axios_1.default.isAxiosError(error) && error.response?.data) {
|
|
27
|
+
const data = error.response.data;
|
|
28
|
+
if (typeof data === 'string') {
|
|
29
|
+
return `[${error.response.status}] ${data}`;
|
|
30
|
+
}
|
|
31
|
+
if (data.message) {
|
|
32
|
+
return `[${error.response.status}] ${data.message}`;
|
|
33
|
+
}
|
|
34
|
+
if (data.reason) {
|
|
35
|
+
return `[${error.response.status}] ${data.reason}`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return super.extractErrorMessage(error);
|
|
39
|
+
}
|
|
40
|
+
mapBadRequestError(message, data) {
|
|
41
|
+
const reason = typeof data === 'object' && data?.reason
|
|
42
|
+
? String(data.reason)
|
|
43
|
+
: '';
|
|
44
|
+
const lowerReason = reason.toLowerCase();
|
|
45
|
+
const lowerMessage = message.toLowerCase();
|
|
46
|
+
if (lowerReason.includes('insufficientfunds') || lowerMessage.includes('insufficient')) {
|
|
47
|
+
return new errors_1.InsufficientFunds(message, this.exchangeName);
|
|
48
|
+
}
|
|
49
|
+
if (lowerReason.includes('invalidquantity') ||
|
|
50
|
+
lowerReason.includes('invalidprice') ||
|
|
51
|
+
lowerReason.includes('limitpriceofftick') ||
|
|
52
|
+
lowerReason.includes('invalidstopprice') ||
|
|
53
|
+
lowerReason.includes('marketnotopen') ||
|
|
54
|
+
lowerReason.includes('unknowninstrument') ||
|
|
55
|
+
lowerReason.includes('duplicateorder')) {
|
|
56
|
+
return new errors_1.InvalidOrder(message, this.exchangeName);
|
|
57
|
+
}
|
|
58
|
+
if (lowerReason.includes('invalidsignature') ||
|
|
59
|
+
lowerReason.includes('invalidapikey') ||
|
|
60
|
+
lowerMessage.includes('terms_not_accepted')) {
|
|
61
|
+
return new errors_1.AuthenticationError(message, this.exchangeName);
|
|
62
|
+
}
|
|
63
|
+
return super.mapBadRequestError(message, data);
|
|
64
|
+
}
|
|
65
|
+
mapError(error) {
|
|
66
|
+
if (axios_1.default.isAxiosError(error) && error.response?.status === 429) {
|
|
67
|
+
const retryAfter = error.response.headers?.['retry-after'];
|
|
68
|
+
const retryAfterSeconds = retryAfter ? parseInt(retryAfter, 10) : undefined;
|
|
69
|
+
return new errors_1.RateLimitExceeded(this.extractErrorMessage(error), retryAfterSeconds, this.exchangeName);
|
|
70
|
+
}
|
|
71
|
+
return super.mapError(error);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
exports.GeminiErrorMapper = GeminiErrorMapper;
|
|
75
|
+
exports.geminiErrorMapper = new GeminiErrorMapper();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { MarketFilterParams, EventFetchParams } from '../../BaseExchange';
|
|
2
|
+
import { IExchangeFetcher, FetcherContext } from '../interfaces';
|
|
3
|
+
import { GeminiAuth } from './auth';
|
|
4
|
+
import { GeminiRawEvent, GeminiRawOrder, GeminiRawPosition, GeminiRawOrderBook } from './types';
|
|
5
|
+
export declare class GeminiFetcher implements IExchangeFetcher<GeminiRawEvent, GeminiRawEvent> {
|
|
6
|
+
private readonly ctx;
|
|
7
|
+
private readonly baseUrl;
|
|
8
|
+
private readonly auth;
|
|
9
|
+
private symbolToEventTicker;
|
|
10
|
+
constructor(ctx: FetcherContext, baseUrl: string, auth?: GeminiAuth);
|
|
11
|
+
fetchRawMarkets(params?: MarketFilterParams): Promise<GeminiRawEvent[]>;
|
|
12
|
+
fetchRawEvents(params: EventFetchParams): Promise<GeminiRawEvent[]>;
|
|
13
|
+
fetchRawSingleEvent(eventTicker: string): Promise<GeminiRawEvent>;
|
|
14
|
+
fetchRawOrderBook(instrumentSymbol: string): Promise<GeminiRawOrderBook | undefined>;
|
|
15
|
+
getEventTickerForSymbol(instrumentSymbol: string): string | undefined;
|
|
16
|
+
submitRawOrder(payload: Record<string, unknown>): Promise<GeminiRawOrder>;
|
|
17
|
+
cancelRawOrder(orderId: number): Promise<{
|
|
18
|
+
result: string;
|
|
19
|
+
message: string;
|
|
20
|
+
}>;
|
|
21
|
+
fetchRawActiveOrders(symbol?: string): Promise<GeminiRawOrder[]>;
|
|
22
|
+
fetchRawOrderHistory(): Promise<GeminiRawOrder[]>;
|
|
23
|
+
fetchRawPositions(): Promise<GeminiRawPosition[]>;
|
|
24
|
+
private get;
|
|
25
|
+
private postAuthenticated;
|
|
26
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GeminiFetcher = void 0;
|
|
4
|
+
const errors_1 = require("./errors");
|
|
5
|
+
// ----------------------------------------------------------------------------
|
|
6
|
+
// Fetcher
|
|
7
|
+
// ----------------------------------------------------------------------------
|
|
8
|
+
class GeminiFetcher {
|
|
9
|
+
ctx;
|
|
10
|
+
baseUrl;
|
|
11
|
+
auth;
|
|
12
|
+
// Index mapping instrumentSymbol -> eventTicker, built during fetchRawEvents
|
|
13
|
+
symbolToEventTicker = new Map();
|
|
14
|
+
constructor(ctx, baseUrl, auth) {
|
|
15
|
+
this.ctx = ctx;
|
|
16
|
+
this.baseUrl = baseUrl;
|
|
17
|
+
this.auth = auth;
|
|
18
|
+
}
|
|
19
|
+
// -- Public data -----------------------------------------------------------
|
|
20
|
+
async fetchRawMarkets(params) {
|
|
21
|
+
return this.fetchRawEvents(params ?? {});
|
|
22
|
+
}
|
|
23
|
+
async fetchRawEvents(params) {
|
|
24
|
+
const allEvents = [];
|
|
25
|
+
const pageSize = 500;
|
|
26
|
+
let offset = params.offset ?? 0;
|
|
27
|
+
const maxResults = params.limit ?? 250000;
|
|
28
|
+
while (allEvents.length < maxResults) {
|
|
29
|
+
const queryParams = {
|
|
30
|
+
limit: String(Math.min(pageSize, maxResults - allEvents.length)),
|
|
31
|
+
offset: String(offset),
|
|
32
|
+
};
|
|
33
|
+
if (params.status && params.status !== 'all') {
|
|
34
|
+
queryParams.status = params.status === 'active' ? 'active' : params.status;
|
|
35
|
+
}
|
|
36
|
+
else if (!params.status) {
|
|
37
|
+
queryParams.status = 'active';
|
|
38
|
+
}
|
|
39
|
+
if (params.category) {
|
|
40
|
+
queryParams.category = params.category;
|
|
41
|
+
}
|
|
42
|
+
if (params.query) {
|
|
43
|
+
queryParams.search = params.query;
|
|
44
|
+
}
|
|
45
|
+
const response = await this.get('/v1/prediction-markets/events', queryParams);
|
|
46
|
+
const events = response.data;
|
|
47
|
+
if (events.length === 0)
|
|
48
|
+
break;
|
|
49
|
+
// Build the instrumentSymbol -> eventTicker index
|
|
50
|
+
for (const event of events) {
|
|
51
|
+
for (const contract of event.contracts) {
|
|
52
|
+
this.symbolToEventTicker.set(contract.instrumentSymbol, event.ticker);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
allEvents.push(...events);
|
|
56
|
+
// Check if we've fetched all available
|
|
57
|
+
if (allEvents.length >= response.pagination.total)
|
|
58
|
+
break;
|
|
59
|
+
offset += events.length;
|
|
60
|
+
}
|
|
61
|
+
return allEvents;
|
|
62
|
+
}
|
|
63
|
+
async fetchRawSingleEvent(eventTicker) {
|
|
64
|
+
return this.get(`/v1/prediction-markets/events/${encodeURIComponent(eventTicker)}`);
|
|
65
|
+
}
|
|
66
|
+
async fetchRawOrderBook(instrumentSymbol) {
|
|
67
|
+
const eventTicker = this.getEventTickerForSymbol(instrumentSymbol);
|
|
68
|
+
if (!eventTicker) {
|
|
69
|
+
throw new Error(`Cannot fetch order book: no event ticker found for ${instrumentSymbol}. ` +
|
|
70
|
+
'Call fetchMarkets first to build the symbol index.');
|
|
71
|
+
}
|
|
72
|
+
const event = await this.fetchRawSingleEvent(eventTicker);
|
|
73
|
+
// The REST API does not expose a full order book — construct a
|
|
74
|
+
// single-level book from the contract's best bid/ask prices.
|
|
75
|
+
// Full depth is only available via the WebSocket @depth streams.
|
|
76
|
+
const contract = event.contracts.find(c => c.instrumentSymbol === instrumentSymbol);
|
|
77
|
+
if (!contract?.prices)
|
|
78
|
+
return undefined;
|
|
79
|
+
const { bestBid, bestAsk } = contract.prices;
|
|
80
|
+
const bids = bestBid
|
|
81
|
+
? [{ price: bestBid, size: '0' }]
|
|
82
|
+
: [];
|
|
83
|
+
const asks = bestAsk
|
|
84
|
+
? [{ price: bestAsk, size: '0' }]
|
|
85
|
+
: [];
|
|
86
|
+
return { bids, asks, timestamp: Date.now() };
|
|
87
|
+
}
|
|
88
|
+
getEventTickerForSymbol(instrumentSymbol) {
|
|
89
|
+
return this.symbolToEventTicker.get(instrumentSymbol);
|
|
90
|
+
}
|
|
91
|
+
// -- Authenticated endpoints -----------------------------------------------
|
|
92
|
+
async submitRawOrder(payload) {
|
|
93
|
+
return this.postAuthenticated('/v1/prediction-markets/order', payload);
|
|
94
|
+
}
|
|
95
|
+
async cancelRawOrder(orderId) {
|
|
96
|
+
return this.postAuthenticated('/v1/prediction-markets/order/cancel', { orderId });
|
|
97
|
+
}
|
|
98
|
+
async fetchRawActiveOrders(symbol) {
|
|
99
|
+
const extra = {};
|
|
100
|
+
if (symbol)
|
|
101
|
+
extra.symbol = symbol;
|
|
102
|
+
const response = await this.postAuthenticated('/v1/prediction-markets/orders/active', extra);
|
|
103
|
+
return response.orders;
|
|
104
|
+
}
|
|
105
|
+
async fetchRawOrderHistory() {
|
|
106
|
+
const response = await this.postAuthenticated('/v1/prediction-markets/orders/history', {});
|
|
107
|
+
return Array.isArray(response) ? response : [];
|
|
108
|
+
}
|
|
109
|
+
async fetchRawPositions() {
|
|
110
|
+
const response = await this.postAuthenticated('/v1/prediction-markets/positions', {});
|
|
111
|
+
return response.positions;
|
|
112
|
+
}
|
|
113
|
+
// -- HTTP helpers ----------------------------------------------------------
|
|
114
|
+
async get(path, params) {
|
|
115
|
+
try {
|
|
116
|
+
const url = new URL(path, this.baseUrl);
|
|
117
|
+
if (params) {
|
|
118
|
+
for (const [key, value] of Object.entries(params)) {
|
|
119
|
+
url.searchParams.set(key, value);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const response = await this.ctx.http.get(url.toString());
|
|
123
|
+
return response.data;
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
throw errors_1.geminiErrorMapper.mapError(error);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async postAuthenticated(path, extraFields) {
|
|
130
|
+
if (!this.auth) {
|
|
131
|
+
throw new Error('Authentication required. Provide apiKey and apiSecret.');
|
|
132
|
+
}
|
|
133
|
+
const payload = {
|
|
134
|
+
request: path,
|
|
135
|
+
nonce: this.auth.nonce(),
|
|
136
|
+
...extraFields,
|
|
137
|
+
};
|
|
138
|
+
const headers = this.auth.buildHeaders(payload);
|
|
139
|
+
try {
|
|
140
|
+
const response = await this.ctx.http.post(`${this.baseUrl}${path}`, {}, { headers });
|
|
141
|
+
return response.data;
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
throw errors_1.geminiErrorMapper.mapError(error);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
exports.GeminiFetcher = GeminiFetcher;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { PredictionMarketExchange, MarketFilterParams, EventFetchParams, ExchangeCredentials } from '../../BaseExchange';
|
|
2
|
+
import { UnifiedMarket, UnifiedEvent, OrderBook, Trade, Order, Position, CreateOrderParams, BuiltOrder } from '../../types';
|
|
3
|
+
export interface GeminiTitanExchangeOptions {
|
|
4
|
+
credentials?: ExchangeCredentials;
|
|
5
|
+
sandbox?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare class GeminiTitanExchange extends PredictionMarketExchange {
|
|
8
|
+
private readonly config;
|
|
9
|
+
private readonly fetcher;
|
|
10
|
+
private readonly normalizer;
|
|
11
|
+
private readonly geminiAuth?;
|
|
12
|
+
private geminiWs?;
|
|
13
|
+
constructor(credentials?: ExchangeCredentials | GeminiTitanExchangeOptions);
|
|
14
|
+
get name(): string;
|
|
15
|
+
private requireAuth;
|
|
16
|
+
protected fetchMarketsImpl(params?: MarketFilterParams): Promise<UnifiedMarket[]>;
|
|
17
|
+
protected fetchEventsImpl(params: EventFetchParams): Promise<UnifiedEvent[]>;
|
|
18
|
+
fetchOrderBook(outcomeId: string): Promise<OrderBook>;
|
|
19
|
+
buildOrder(params: CreateOrderParams): Promise<BuiltOrder>;
|
|
20
|
+
submitOrder(built: BuiltOrder): Promise<Order>;
|
|
21
|
+
createOrder(params: CreateOrderParams): Promise<Order>;
|
|
22
|
+
cancelOrder(orderId: string): Promise<Order>;
|
|
23
|
+
fetchOpenOrders(marketId?: string): Promise<Order[]>;
|
|
24
|
+
fetchClosedOrders(): Promise<Order[]>;
|
|
25
|
+
fetchAllOrders(): Promise<Order[]>;
|
|
26
|
+
fetchPositions(): Promise<Position[]>;
|
|
27
|
+
private ensureWebSocket;
|
|
28
|
+
watchOrderBook(outcomeId: string): Promise<OrderBook>;
|
|
29
|
+
watchTrades(outcomeId: string): Promise<Trade[]>;
|
|
30
|
+
close(): Promise<void>;
|
|
31
|
+
}
|