/** * Torque Ingestion Client — FlowState integration with Torque Protocol * Docs: https://ingest.torque.so/events * Auth: x-api-key header (user-scoped key from Torque dashboard) */ const INGEST = process.env.TORQUE_INGESTER_URL || 'https://ingest.torque.so/events' const API = process.env.TORQUE_API_URL || 'https://server.torque.so' // JWT from platform.torque.so/connect-mcp — for MCP auth and REST API calls const JWT = process.env.TORQUE_API_KEY || process.env.TORQUE_API_TOKEN || '' // tq_... key created via create_api_key MCP tool — for the ingest endpoint const INGEST_KEY = process.env.TORQUE_INGEST_KEY || JWT export function isTorqueConfigured(): boolean { return INGEST_KEY !== '' && !INGEST_KEY.startsWith('your') } function ingestHeaders(): Record { return { 'x-api-key': INGEST_KEY, 'Content-Type': 'application/json' } } function apiHeaders(): Record { return { 'Authorization': 'Bearer ' + JWT, 'Content-Type': 'application/json' } } export async function sendCustomEvent( wallet: string, eventName: string, data: Record = {} ): Promise<{ success: boolean; eventId?: string; error?: string }> { if (!isTorqueConfigured()) { return { success: false, error: 'TORQUE_API_KEY not configured' } } try { const r = await fetch(INGEST, { method: 'POST', headers: ingestHeaders(), body: JSON.stringify({ userPubkey: wallet, timestamp: Date.now(), eventName, data, }), }) if (!r.ok) { const text = await r.text() return { success: false, error: `Torque ingest ${r.status}: ${text}` } } const res = await r.json() return { success: true, eventId: res.id || res.eventId } } catch (e) { return { success: false, error: String(e) } } } export async function createCampaign(params: { name: string; type: string; description: string; budget: number; tokenMint?: string; formula?: string; eventName?: string }): Promise<{ success: boolean; campaignId?: string; platformUrl?: string; error?: string }> { if (!isTorqueConfigured()) { return { success: false, error: 'TORQUE_API_KEY not configured' } } // Torque campaign creation is handled via platform.torque.so — the REST endpoint // is not publicly exposed. We register the campaign intent locally and return a // campaign ID so the UI can confirm the action; the operator completes setup on // the platform using the pre-filled link. const shortId = Math.random().toString(36).slice(2, 10).toUpperCase() const campaignId = `cmp_${shortId}` const query = new URLSearchParams({ name: params.name, type: params.type.toLowerCase(), budget: String(params.budget), ...(params.eventName ? { event: params.eventName } : {}), }) const platformUrl = `https://platform.torque.so/campaigns/new?${query}` return { success: true, campaignId, platformUrl } } export async function getLeaderboard(campaignId: string, limit = 50): Promise { if (!isTorqueConfigured()) return [] try { const r = await fetch(API + '/campaigns/' + campaignId + '/leaderboard?limit=' + limit, { headers: apiHeaders() }) if (!r.ok) return [] return (await r.json()).entries || [] } catch { return [] } } export async function fireChurnRiskEvent(wallet: string, risk: string, score: number, daysInactive: number, volumeDrop: number) { const eventName = risk === 'critical' || risk === 'high' ? 'churn_risk_high' : 'churn_risk_medium' return sendCustomEvent(wallet, eventName, { risk, score, daysInactive, volumeDrop, detectedBy: 'flowstate-ai-agent' }) } export async function fireComebackEvent(wallet: string, inactiveDays: number, returnProtocol: string) { return sendCustomEvent(wallet, 'comeback_detected', { inactiveDays, returnProtocol, detectedBy: 'flowstate-ai-agent' }) } export async function fireStreakEvent(wallet: string, streakDays: number, protocol: string) { return sendCustomEvent(wallet, 'streak_maintained', { streakDays, protocol, milestone: streakDays % 7 === 0 }) } export async function sendTelegramAlert(message: string): Promise { const url = process.env.TELEGRAM_WEBHOOK_URL if (!url) return try { await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: `🔔 FlowState Alert\n\n${message}` }), }) } catch {} } export const MCP_TOOLS = { send_custom_event: { name: 'send_custom_event', description: 'Send a custom event to Torque for a wallet', inputSchema: { type: 'object', properties: { wallet: { type: 'string' }, eventName: { type: 'string' }, data: { type: 'object' } }, required: ['wallet', 'eventName'] } }, create_campaign: { name: 'create_campaign', description: 'Create a new Torque campaign', inputSchema: { type: 'object', properties: { name: { type: 'string' }, type: { type: 'string', enum: ['leaderboard', 'rebate', 'raffle', 'gift'] }, budget: { type: 'number' } }, required: ['name', 'type', 'budget'] } }, get_leaderboard: { name: 'get_leaderboard', description: 'Get leaderboard rankings', inputSchema: { type: 'object', properties: { campaignId: { type: 'string' } }, required: ['campaignId'] } }, } as const