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(); // Strip accidental wrapping quotes from env UIs if ( (key.startsWith('"') && key.endsWith('"')) || (key.startsWith("'") && key.endsWith("'")) ) { key = key.slice(1, -1); } // Normalize line breaks from various env formats 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'); } const ts = String(timestampMs); const message = `${ts}${method.toUpperCase()}${path}`; const signature = crypto.sign('sha256', Buffer.from(message), { 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 };