import crypto from 'crypto'; const DEFAULT_KALSHI_API_BASE = 'https://api.elections.kalshi.com'; const KALSHI_API_BASE = (process.env.KALSHI_API_BASE || DEFAULT_KALSHI_API_BASE).trim().replace(/\/+$/, ''); function normalizePrivateKey(value) { if (!value) return ''; let key = String(value).trim(); if ( (key.startsWith('"') && key.endsWith('"')) || (key.startsWith("'") && key.endsWith("'")) ) { key = key.slice(1, -1); } return key .replace(/\\r\\n/g, '\n') .replace(/\r\n/g, '\n') .replace(/\\n/g, '\n') .trim(); } /** * Signs a Kalshi API request using RSA-PSS with SHA-256. * Returns headers needed for authenticated requests. */ export function signRequest(method, path, timestampMs = Date.now()) { const keyId = process.env.KALSHI_API_KEY_ID?.trim(); const privateKeyPem = normalizePrivateKey(process.env.KALSHI_RSA_PRIVATE_KEY); if (!keyId || !privateKeyPem) { throw new Error('Missing KALSHI_API_KEY_ID or KALSHI_RSA_PRIVATE_KEY'); } // Strip query parameters before signing per Kalshi docs const pathWithoutQuery = path.split('?')[0]; const ts = String(timestampMs); const message = `${ts}${method.toUpperCase()}${pathWithoutQuery}`; const sign = crypto.createSign('RSA-SHA256'); sign.update(message); sign.end(); const signature = sign.sign({ key: privateKeyPem, padding: crypto.constants.RSA_PKCS1_PSS_PADDING, saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST }); return { 'KALSHI-ACCESS-KEY': keyId, 'KALSHI-ACCESS-SIGNATURE': signature.toString('base64'), 'KALSHI-ACCESS-TIMESTAMP': ts, 'Content-Type': 'application/json' }; } export { KALSHI_API_BASE };