@torque-labs/mcp 0.2.0 → 0.3.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.
Files changed (3) hide show
  1. package/README.md +8 -0
  2. package/dist/index.js +278 -68
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -158,6 +158,13 @@ You can provide either an auth token or a private key, or authenticate at runtim
158
158
  | `get_recurring_incentive` | Get full details of a specific recurring incentive |
159
159
  | `get_recurring_incentive_analytics` | Get performance analytics for an incentive |
160
160
 
161
+ ### Results & Monitoring
162
+
163
+ | Tool | Description |
164
+ | ------------------------ | ------------------------------------------------------------------------------------------ |
165
+ | `get_incentive_results` | Fetch results for a live incentive — preview (evaluation scores), recipients (allocations & payout status), or download (full CSV) |
166
+ | `get_offer_details` | Get active offer summaries — reward pools, timeframes, and metadata |
167
+
161
168
  ### Context
162
169
 
163
170
  | Tool | Description |
@@ -181,6 +188,7 @@ Static reference guides available to the LLM for context:
181
188
  | `torque://capabilities` | Complete guide to all tools, prompts, and quick start instructions |
182
189
  | `torque://workflows` | Common workflows and tool pipelines |
183
190
  | `torque://incentive-types` | Incentive types (leaderboard, rebate, raffle, direct), formulas, and variables |
191
+ | `torque://building-landing-pages` | Guide for building user-facing landing pages with leaderboards and claims |
184
192
 
185
193
  ## Workflows
186
194
 
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- function Z(t){let e=new Set(t);if(e.has("--help")||e.has("-h"))return{action:"help",params:{}};if(e.has("--version")||e.has("-v"))return{action:"version",params:{}};let r={};for(let n=0;n<t.length;n+=2){let o=t[n]?.replace("--",""),i=t[n+1];o&&i&&(r[o]=i)}return{action:"run",params:r}}function ee(){let t=`
2
+ function ee(t){let e=new Set(t);if(e.has("--help")||e.has("-h"))return{action:"help",params:{}};if(e.has("--version")||e.has("-v"))return{action:"version",params:{}};let r={};for(let n=0;n<t.length;n+=2){let i=t[n]?.replace("--",""),o=t[n+1];i&&o&&(r[i]=o)}return{action:"run",params:r}}function te(){let t=`
3
3
  Torque MCP Server \u2014 connect AI assistants to the Torque incentive platform on Solana.
4
4
 
5
5
  Usage:
@@ -25,129 +25,146 @@ Examples:
25
25
  claude mcp add torque -- npx @torque-labs/mcp
26
26
  claude mcp add torque -e TORQUE_API_TOKEN=your-token -- npx @torque-labs/mcp
27
27
  npx @torque-labs/mcp --apiToken your-token
28
- `.trimStart();process.stderr.write(t)}function te(){process.stderr.write(`0.2.0
29
- `)}var it="https://server.torque.so",st="https://platform.torque.so",at="https://ingest.torque.so",ct="Sign in to Torque";function re(t){let e=t.apiUrl??process.env.TORQUE_API_URL??it,r=t.platformUrl??process.env.TORQUE_PLATFORM_URL??st,n=t.ingesterUrl??process.env.TORQUE_INGESTER_URL??at,o=t.privateKey??process.env.WALLET_PRIVATE_KEY??void 0,i=t.apiToken??process.env.TORQUE_API_TOKEN??void 0;return{apiBaseUrl:e,platformUrl:r,ingesterUrl:n,signStatement:ct,privateKey:o,apiToken:i}}import{McpServer as lr}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as dr}from"@modelcontextprotocol/sdk/server/stdio.js";import{createKeyPairSignerFromBytes as ut,getBase58Encoder as lt,getTransactionDecoder as dt,getTransactionEncoder as pt,partiallySignTransaction as mt,signBytes as ft}from"@solana/kit";async function ne(t){let e=t.trim(),r;if(e.startsWith("["))try{let o=JSON.parse(e);r=new Uint8Array(o)}catch{throw new Error("Invalid byte array format for private key")}else try{let i=lt().encode(e);r=Uint8Array.from(i)}catch{throw new Error("Invalid private key format. Provide a base58-encoded key or JSON byte array.")}let n=await ut(r);return{address:n.address,async signMessage(o){let i=await ft(n.keyPair.privateKey,o);return new Uint8Array(i)},async signTransaction(o){let i=dt(),s=pt(),c=i.decode(o),a=await mt([n.keyPair],c);return new Uint8Array(s.encode(a))}}}import j from"fs/promises";import C from"path";var N=class{token=null;authToken=null;baseUrl;cacheDir;constructor(e,r){this.baseUrl=e,this.cacheDir=r||C.join(import.meta.dirname,"..",".cache")}getToken(){return this.authToken??this.token}setAuthToken(e){this.authToken=e}getAuthHeader(){let e=this.getToken();return e?{Authorization:`Bearer ${e}`}:null}async setToken(e,r){this.token=e,await this.persistToken(e,r)}async clearToken(){this.token=null,this.authToken=null;let e=C.join(this.cacheDir,"token.json");try{await j.unlink(e)}catch{}}async loadPersistedToken(){let e=C.join(this.cacheDir,"token.json");try{let r=await j.readFile(e,"utf-8"),n=JSON.parse(r);this.token=n.token}catch{}}async login(e,r){return this.performLogin(e,"",r)}async loginWithStatement(e,r,n){return this.performLogin(e,r,n)}async performLogin(e,r,n){let o=await fetch(`${this.baseUrl}/login`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({authType:"basic",pubKey:e,payload:{input:r,output:n}})});if(!o.ok){let s=await o.text();throw new Error(`Login failed (${o.status}): ${s}`)}let i=await o.json();if(!i.token||typeof i.token!="string")throw new Error("Login response missing token field");return i.token}async verify(){let e=this.getToken();if(!e)return!1;try{return(await fetch(`${this.baseUrl}/verify`,{method:"GET",headers:{Authorization:`Bearer ${e}`}})).ok}catch{return!1}}async persistToken(e,r){await j.mkdir(this.cacheDir,{recursive:!0});let n=C.join(this.cacheDir,"token.json"),o={token:e,publicKey:r,timestamp:Date.now()};await j.writeFile(n,JSON.stringify(o,null,2))}};function F(t){let e=new N(t.apiBaseUrl),r=null,n=null,o=null;return t.apiToken&&e.setAuthToken(t.apiToken),{getWallet:async()=>{if(r)return r;if(!t.privateKey)throw new Error("No private key configured. Set WALLET_PRIVATE_KEY env var or privateKey in MCP config.");return r=await ne(t.privateKey),r},auth:{login:(i,s)=>e.login(i,s),loginWithStatement:(i,s,c)=>e.loginWithStatement(i,s,c),verify:()=>e.verify(),getToken:()=>e.getToken(),setToken:(i,s)=>e.setToken(i,s),clearToken:()=>e.clearToken(),loadPersistedToken:()=>e.loadPersistedToken(),setAuthToken:i=>e.setAuthToken(i),getAuthHeader:()=>e.getAuthHeader()},get activeProject(){return n},setActiveProject:i=>{i?.id!==n?.id&&(o=null),n=i},get aiContextCache(){return o},set aiContextCache(i){o=i},apiRequest:async(i,s)=>{let c=e.getAuthHeader();if(!c)throw new Error("Not authenticated. Use the authenticate tool first or set TORQUE_API_TOKEN.");let a=`${t.apiBaseUrl}${i}`,u=await fetch(a,{method:s?.method??"GET",headers:{...c,...s?.body?{"Content-Type":"application/json"}:{}},...s?.body?{body:JSON.stringify(s.body)}:{}});if(!u.ok){let d=await u.text();throw new Error(`API request failed (${u.status}): ${d}`)}return(await u.json()).data},config:t}}import{z as ht}from"zod";import{getBase58Decoder as yt}from"@solana/kit";var P="\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501";function S(t){let e=`${t}/connect-mcp`;return["\u{1F510} **Authentication Required**","",`Visit [${e}](${e}) to create an account and get your auth token.`,"","Then use the `authenticate` tool with your auth token to connect."].join(`
28
+ `.trimStart();process.stderr.write(t)}function re(){process.stderr.write(`0.3.0
29
+ `)}var lt="https://server.torque.so",dt="https://platform.torque.so",pt="https://ingest.torque.so",ft="Sign in to Torque";function ne(t){let e=t.apiUrl??process.env.TORQUE_API_URL??lt,r=t.platformUrl??process.env.TORQUE_PLATFORM_URL??dt,n=t.ingesterUrl??process.env.TORQUE_INGESTER_URL??pt,i=t.privateKey??process.env.WALLET_PRIVATE_KEY??void 0,o=t.apiToken??process.env.TORQUE_API_TOKEN??void 0;return{apiBaseUrl:e,platformUrl:r,ingesterUrl:n,signStatement:ft,privateKey:i,apiToken:o}}import{McpServer as br}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as Tr}from"@modelcontextprotocol/sdk/server/stdio.js";import{createKeyPairSignerFromBytes as mt,getBase58Encoder as ht,getTransactionDecoder as gt,getTransactionEncoder as yt,partiallySignTransaction as vt,signBytes as wt}from"@solana/kit";async function ie(t){let e=t.trim(),r;if(e.startsWith("["))try{let i=JSON.parse(e);r=new Uint8Array(i)}catch{throw new Error("Invalid byte array format for private key")}else try{let o=ht().encode(e);r=Uint8Array.from(o)}catch{throw new Error("Invalid private key format. Provide a base58-encoded key or JSON byte array.")}let n=await mt(r);return{address:n.address,async signMessage(i){let o=await wt(n.keyPair.privateKey,i);return new Uint8Array(o)},async signTransaction(i){let o=gt(),s=yt(),c=o.decode(i),a=await vt([n.keyPair],c);return new Uint8Array(s.encode(a))}}}import j from"fs/promises";import N from"path";var q=class{token=null;authToken=null;baseUrl;cacheDir;constructor(e,r){this.baseUrl=e,this.cacheDir=r||N.join(import.meta.dirname,"..",".cache")}getToken(){return this.authToken??this.token}setAuthToken(e){this.authToken=e}getAuthHeader(){let e=this.getToken();return e?{Authorization:`Bearer ${e}`}:null}async setToken(e,r){this.token=e,await this.persistToken(e,r)}async clearToken(){this.token=null,this.authToken=null;let e=N.join(this.cacheDir,"token.json");try{await j.unlink(e)}catch{}}async loadPersistedToken(){let e=N.join(this.cacheDir,"token.json");try{let r=await j.readFile(e,"utf-8"),n=JSON.parse(r);this.token=n.token}catch{}}async login(e,r){return this.performLogin(e,"",r)}async loginWithStatement(e,r,n){return this.performLogin(e,r,n)}async performLogin(e,r,n){let i=await fetch(`${this.baseUrl}/login`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({authType:"basic",pubKey:e,payload:{input:r,output:n}})});if(!i.ok){let s=await i.text();throw new Error(`Login failed (${i.status}): ${s}`)}let o=await i.json();if(!o.token||typeof o.token!="string")throw new Error("Login response missing token field");return o.token}async verify(){let e=this.getToken();if(!e)return!1;try{return(await fetch(`${this.baseUrl}/verify`,{method:"GET",headers:{Authorization:`Bearer ${e}`}})).ok}catch{return!1}}async persistToken(e,r){await j.mkdir(this.cacheDir,{recursive:!0});let n=N.join(this.cacheDir,"token.json"),i={token:e,publicKey:r,timestamp:Date.now()};await j.writeFile(n,JSON.stringify(i,null,2))}};function B(t){let e=new q(t.apiBaseUrl),r=null,n=null,i=null;return t.apiToken&&e.setAuthToken(t.apiToken),{getWallet:async()=>{if(r)return r;if(!t.privateKey)throw new Error("No private key configured. Set WALLET_PRIVATE_KEY env var or privateKey in MCP config.");return r=await ie(t.privateKey),r},auth:{login:(o,s)=>e.login(o,s),loginWithStatement:(o,s,c)=>e.loginWithStatement(o,s,c),verify:()=>e.verify(),getToken:()=>e.getToken(),setToken:(o,s)=>e.setToken(o,s),clearToken:()=>e.clearToken(),loadPersistedToken:()=>e.loadPersistedToken(),setAuthToken:o=>e.setAuthToken(o),getAuthHeader:()=>e.getAuthHeader()},get activeProject(){return n},setActiveProject:o=>{o?.id!==n?.id&&(i=null),n=o},get aiContextCache(){return i},set aiContextCache(o){i=o},apiRequest:async(o,s)=>{let c=e.getAuthHeader();if(!c)throw new Error("Not authenticated. Use the authenticate tool first or set TORQUE_API_TOKEN.");let a=`${t.apiBaseUrl}${o}`,u=await fetch(a,{method:s?.method??"GET",headers:{...c,...s?.body?{"Content-Type":"application/json"}:{}},...s?.body?{body:JSON.stringify(s.body)}:{}});if(!u.ok){let d=await u.text();throw new Error(`API request failed (${u.status}): ${d}`)}return(await u.json()).data},config:t}}import{z as bt}from"zod";import{getBase58Decoder as Tt}from"@solana/kit";var x="\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501";function D(t){let e=`${t}/connect-mcp`;return["\u{1F510} **Authentication Required**","",`Visit [${e}](${e}) to create an account and get your auth token.`,"","Then use the `authenticate` tool with your auth token to connect."].join(`
30
30
  `)}function oe(t,e){if(t.length===0)return["\u{1F4C1} **No projects found**","","Use `create_project` to create one."].join(`
31
- `);let r=[`\u{1F4C1} **Your Projects** (${t.length})`,"","| Project | ID | Status |","|---|---|---|"];for(let n of t){let o=n.id===e?"\u2705 active":"";r.push(`| **${n.name}** | \`${n.id}\` | ${o} |`)}return r.join(`
32
- `)}function ie(t){let e=["\u{1F4CB} **New Project Preview**",P,"",` Name: ${t.name}`];t.description&&e.push(` Description: ${t.description}`),t.image&&e.push(` Image: ${t.image}`),t.defaultTokenAddress&&e.push(` Token: ${t.defaultTokenAddress}`),t.defaultProgramAddress&&e.push(` Program: ${t.defaultProgramAddress}`);let r=t.adminWallets;if(r&&r.length>0){e.push(""),e.push(` Admins (${r.length}):`);for(let n of r)e.push(` ${n}`)}return e.push("",P,"\u26A0\uFE0F Confirm to proceed."),e.join(`
33
- `)}function se(t){if(t.length===0)return["\u{1F4CB} **No custom events on this project**","","To get started:"," \u2022 Use `attach_custom_event` to attach an existing event"," \u2022 Use `create_custom_event` to create a new one first"].join(`
34
- `);let e=[`\u{1F4CB} **Custom Events on Project** (${t.length})`,"","| Event | ID | Fields | Status |","|---|---|---|---|"];for(let r of t){let n=r.columnMapping?"\u2705 query-ready":"\u26A0\uFE0F awaiting first ingestion",o=r.fields.map(i=>`\`${i.fieldName}\` (${i.type})`).join(", ");e.push(`| **${r.name}** \`${r.eventName}\` | \`${r.id}\` | ${o} | ${n} |`)}return e.join(`
35
- `)}function ae(t,e){if(t.length===0)return["\u{1F4CB} **No custom events found**","","Use `create_custom_event` to create one."].join(`
36
- `);let r=[`\u{1F4CB} **Your Custom Events** (${t.length})`,"","| Event | ID | Fields | Projects |","|---|---|---|---|"];for(let n of t){let o=n.fields.map(s=>`\`${s.fieldName}\` (${s.type})`).join(", "),i;n.projects.length===0?i="\u2014":i=n.projects.map(s=>{let c=e?.get(s.projectId);return c?`**${c}**`:`\`${s.projectId}\``}).join(", "),r.push(`| **${n.name}** \`${n.eventName}\` | \`${n.id}\` | ${o} | ${i} |`)}return r.join(`
37
- `)}function ce(t){let e=t.fields,r=["\u{1F4CB} **New Custom Event Preview**",P,"",` Event ID: \`${t.eventName}\``,` Name: ${t.name}`,"",` Fields (${e.length}):`];for(let n of e){let o=n.label?` \u2014 ${n.label}`:"";r.push(` \`${n.fieldName}\` (${n.type})${o}`)}return r.push("","\u2139\uFE0F Note: `userPubkey` (wallet address) is always required as a top-level property when sending events \u2014 it is not part of the event schema.","",P,"\u26A0\uFE0F Confirm to proceed."),r.join(`
38
- `)}function ue(t,e,r,n){let o={userPubkey:"<wallet address>",timestamp:Date.now(),eventName:e,data:r.reduce((i,s)=>(i[s.fieldName]=`<${s.type.toLowerCase()}>`,i),{})};return["","\u{1F4E1} **Send events to the ingester**","",`**Endpoint:** \`POST ${t}/events\``,"","**Required headers:**","- `Content-Type: application/json`","- `x-api-key: <your-api-key>` \u2014 use `create_api_key` to generate one","","`userPubkey` (the user's wallet address) and `timestamp` are required top-level properties on every event. Your custom fields go inside `data`.","","```bash",`curl -X POST ${t}/events \\`,' -H "Content-Type: application/json" \\',' -H "x-api-key: <your-api-key>" \\',` -d '${JSON.stringify(o,null,2)}'`,"```","",`\u26A0\uFE0F **An API key is required to send events.** Use \`create_api_key\` to generate one, or manage your keys at [${n}/developer](${n}/developer).`].join(`
39
- `)}function le(t,e){if(t.length===0)return["\u{1F511} **No API keys found**","","Use `create_api_key` to create one for sending events to the ingestion pipeline.","",`Manage API keys and view ingestion logs at [${e}/developer](${e}/developer).`].join(`
40
- `);let r=[`\u{1F511} **Your API Keys** (${t.length})`,"","| Name | Key | Status | Last Used | Created |","|---|---|---|---|---|"];for(let n of t){let o=n.name??"\u2014",i=n.isActive?"\u2705 active":"\u274C revoked",s=n.lastUsedAt?new Date(n.lastUsedAt).toLocaleDateString():"never",c=new Date(n.createdAt).toLocaleDateString();r.push(`| ${o} | \`${n.key}\` | ${i} | ${s} | ${c} |`)}return r.push(""),r.push(`Manage API keys and view ingestion logs at [${e}/developer](${e}/developer).`),r.join(`
41
- `)}function de(t,e){return["\u{1F511} **API Key Created**","",` Name: ${t.name??"\u2014"}`,` Key: \`${t.key}\``,"","\u26A0\uFE0F **Copy this key now \u2014 it will not be shown again.**","","Use this key in the `x-api-key` header when sending events to the ingestion pipeline.","",`Manage API keys and view ingestion logs at [${e}/developer](${e}/developer).`].join(`
42
- `)}function pe(t){if(t.length===0)return["\u{1F4CA} **No recurring incentives found**","","Use `create_recurring_incentive` to create one, or use the `create-incentive` prompt for a guided workflow."].join(`
43
- `);let e=[`\u{1F4CA} **Recurring Incentives** (${t.length})`,"","| Name | ID | Type | Epoch | Status |","|---|---|---|---|---|"];for(let r of t){let n=r.isActive?"\u2705 active":"\u23F8 inactive",o=r.evalDurationDays?`${r.evalDurationDays}-day epochs`:"\u2014";e.push(`| **${r.name}** | \`${r.id}\` | ${r.type} | ${o} | ${n} |`)}return e.join(`
44
- `)}function me(t){let e=t.isActive?"\u2705 active":"\u23F8 inactive",r=[`\u{1F4CA} **${t.name||"Incentive Detail"}**`,"","| | |","|---|---|",`| **Status** | ${e} |`,`| **ID** | \`${t.id}\` |`];t.type&&r.push(`| **Type** | ${t.type} |`),t.evalDurationDays&&r.push(`| **Eval Period** | ${t.evalDurationDays} day(s) |`),t.startDate&&r.push(`| **Start** | ${t.startDate} |`),t.maxIterations&&r.push(`| **Max Epochs** | ${t.maxIterations} |`);let n=t.distributionConfig;n&&(n.distributionType&&r.push(`| **Distribution** | ${n.distributionType} |`),n.emissionType&&r.push(`| **Emission** | ${n.emissionType} |`),n.totalFundAmount!==void 0&&r.push(`| **Total Fund** | ${n.totalFundAmount} |`),n.customFormula&&r.push(`| **Formula** | \`${n.customFormula}\` |`));let o=t.offerMetadata;return o?.description&&(r.push(""),r.push(`${o.description}`)),r.join(`
45
- `)}function fe(t){let e={leaderboard:"Leaderboard Competition",rebate:"Rebate",raffle:"Raffle",direct:"Direct Distribution"},r=t.type,n=["\u{1F4CB} **New Incentive Preview**",P,"",` Name: ${t.name}`,` Type: ${e[r]||r}`,` Emission: ${t.emissionType}`,` Eval Period: ${t.evalDurationDays} day(s)`,` Start: ${t.startDate}`];if(t.description&&n.push(` Description: ${t.description}`),r==="direct"){n.push(" Claim Window: 24 hours");let o=t.allocations;if(o&&o.length>0){let i=o.reduce((s,c)=>s+c.amount,0);n.push(""),n.push(` **Allocations** (${o.length} recipients)`);for(let s of o)n.push(` ${s.address} \u2192 ${s.amount}`);n.push(""),n.push(` Total Fund: ${i}`)}}else if(r==="raffle"){let o=t.raffleBuckets;if(o){let i=o.reduce((s,c)=>s+c.amount*c.count,0);n.push(""),n.push(" **Prize Tiers**");for(let s of o)n.push(` ${s.count}\xD7 winner(s) \u2192 ${s.amount} each`);n.push(""),n.push(` Total Fund: ${i}`)}}else r==="rebate"?(n.push(` Rebate: ${t.rebatePercentage}%`),n.push(` Total Fund: ${t.totalFundAmount}`)):n.push(` Total Fund: ${t.totalFundAmount}`);return t.sqlQuery&&(n.push(""),n.push(" **SQL Query**"),n.push(` \`${t.sqlQuery}\``)),t.customEventId&&n.push(` Event ID: ${t.customEventId}`),t.customFormula&&n.push(` Formula: ${t.customFormula}`),t.maxIterations&&n.push(` Max Epochs: ${t.maxIterations}`),t.maxPerParticipant&&n.push(` Max/User: ${t.maxPerParticipant}`),n.push("",P,"\u26A0\uFE0F Confirm to proceed."),n.join(`
46
- `)}var he={name:"authenticate",description:"Authenticate with Torque. Accepts an auth token directly, or \u2014 if a private key is configured \u2014 automatically signs in using the wallet. If neither is available, returns instructions to get a token.",inputSchema:{authToken:ht.string().optional().describe("Torque auth token for authentication (optional if privateKey is configured)")},handler:async(t,e)=>{try{let r=t.authToken;if(r)return e.auth.setAuthToken(r),{content:[{type:"text",text:"\u2705 Successfully authenticated with auth token."}]};if(e.config.privateKey){let n=await e.getWallet(),o=new TextEncoder().encode(e.config.signStatement),i=await n.signMessage(o),s=yt().decode(i),c=await e.auth.loginWithStatement(n.address,e.config.signStatement,s);return await e.auth.setToken(c,n.address),{content:[{type:"text",text:`\u2705 Successfully authenticated with wallet \`${n.address}\`.`}]}}return{content:[{type:"text",text:S(e.config.platformUrl)}],isError:!0}}catch(r){return{content:[{type:"text",text:`Authentication failed: ${r instanceof Error?r.message:String(r)}`}],isError:!0}}}},ye={name:"check_auth_status",description:"Check if you are currently authenticated with Torque. Verifies the stored token with the server. ALWAYS call this automatically at the start of every session before attempting any other Torque operation. If not authenticated, use `authenticate`.",inputSchema:{},handler:async(t,e)=>e.auth.getToken()?await e.auth.verify()?{content:[{type:"text",text:"\u2705 You are authenticated. Your token is valid."}]}:{content:[{type:"text",text:`\u{1F504} Your token has expired or is invalid.
47
-
48
- `+S(e.config.platformUrl)}]}:{content:[{type:"text",text:S(e.config.platformUrl)}]}},ge={name:"logout",description:"Log out from Torque. Clears the stored authentication token from this session and local cache.",inputSchema:{},handler:async(t,e)=>{try{return await e.auth.clearToken(),{content:[{type:"text",text:"Successfully logged out. Your authentication has been cleared."}]}}catch(r){return{content:[{type:"text",text:`Logout failed: ${r instanceof Error?r.message:String(r)}`}],isError:!0}}}};import{z as gt}from"zod";function h(t){return t.auth.getToken()?null:{content:[{type:"text",text:S(t.config.platformUrl)}],isError:!0}}function E(t){return t.activeProject?null:{content:[{type:"text",text:"\u{1F4CB} No active project set. Use `set_active_project` or `create_project` first."}],isError:!0}}var ve={name:"list_api_keys",description:"List your API keys for the Torque event ingestion pipeline. These keys are used in the `x-api-key` header when sending custom events to the ingester. Keys are shown masked \u2014 to manage or revoke keys, visit the developer dashboard. IMPORTANT: Always display the full list to the user in your response. IMPORTANT: Always include the developer dashboard link from the response so the user knows where to manage keys and view ingestion logs.",requires:["auth"],inputSchema:{},handler:async(t,e)=>{let r=h(e);if(r)return r;try{let n=await e.apiRequest("/api-keys"),o=Array.isArray(n)?n:n.data??[];return{content:[{type:"text",text:le(o,e.config.platformUrl)}]}}catch(n){return{content:[{type:"text",text:`\u274C Failed to list API keys: ${n instanceof Error?n.message:String(n)}`}],isError:!0}}}},we={name:"create_api_key",description:"Create a new API key for sending custom events to the Torque ingestion pipeline. The key is shown once upon creation \u2014 it cannot be retrieved later. Use this key in the `x-api-key` header when sending events via the ingester. IMPORTANT: Always include the developer dashboard link from the response so the user knows where to manage keys and view ingestion logs.",requires:["auth"],inputSchema:{name:gt.string().optional().describe("Optional display name for the API key. If omitted, the API generates a default name.")},handler:async(t,e)=>{let r=h(e);if(r)return r;try{let n={};t.name&&(n.name=t.name);let o=await e.apiRequest("/api-keys",{method:"POST",body:n});return{content:[{type:"text",text:de(o,e.config.platformUrl)}]}}catch(n){return{content:[{type:"text",text:`\u274C Failed to create API key: ${n instanceof Error?n.message:String(n)}`}],isError:!0}}}};import{z as R}from"zod";var vt=.05,be={name:"create_project",description:"Create a new project on Torque. Projects are the workspace for incentives, custom events, and analytics. The created project automatically becomes your active project for subsequent operations. IMPORTANT: Before creating a project, always ask the user what type of project this is: (1) Token team \u2014 they have a token and want to incentivize activity around it (swaps, holds, bonding curve). Ask for their token mint address and set it as `defaultTokenAddress`. (2) Protocol/product \u2014 they have an on-chain program and want to incentivize interactions with it. Ask for their program address and set it as `defaultProgramAddress`. They may also have a token \u2014 ask if they do and collect both if so. This ensures incentives can be created efficiently without asking for addresses every time.",requires:["auth"],inputSchema:{name:R.string().describe("Project name"),description:R.string().optional().describe("Project description"),image:R.string().optional().describe("Project image URL"),defaultTokenAddress:R.string().optional().describe("Default SPL token mint address for the project. This is the token they want to incentivize \u2014 used as the default for swap, hold, and bonding curve incentives. Essential for token teams."),defaultProgramAddress:R.string().optional().describe("Default on-chain program address for the project. This is the program they want to track interactions for \u2014 used for IDL instruction incentives. Typically provided by protocol/product teams."),adminWallets:R.array(R.string()).optional().describe("Wallet addresses to add as project admins"),confirmed:R.boolean().optional().describe("Set to true to confirm and execute the creation. Omit to preview first.")},handler:async(t,e)=>{let r=h(e);if(r)return r;if(!t.confirmed)return{content:[{type:"text",text:ie(t)}]};try{let n={name:t.name,protocolFee:vt};if(t.description&&(n.description=t.description),t.image&&(n.image=t.image),t.defaultTokenAddress&&(n.defaultTokenAddress=t.defaultTokenAddress),t.defaultProgramAddress&&(n.defaultProgramAddress=t.defaultProgramAddress),t.adminWallets){let i=t.adminWallets;n.roles=i.map(s=>({userPubKey:s,role:"ADMIN"}))}let o=await e.apiRequest("/projects",{method:"POST",body:n});return e.setActiveProject({id:o.id,name:o.name}),{content:[{type:"text",text:`Project "${o.name}" created successfully (ID: ${o.id}). It is now your active project.`}]}}catch(n){return{content:[{type:"text",text:`Failed to create project: ${n instanceof Error?n.message:String(n)}`}],isError:!0}}}},Te={name:"list_projects",description:"List all your Torque projects and show which one is currently active. AUTOMATICALLY call this after authentication succeeds to show the user their projects. Also use when the user mentions projects, wants to switch projects, or needs a project ID for `set_active_project`. If no project is active, suggest selecting one \u2014 most tools require an active project. IMPORTANT: Always display the full project list to the user in your response.",requires:["auth"],inputSchema:{},handler:async(t,e)=>{let r=h(e);if(r)return r;try{let n=await e.apiRequest("/projects"),o=Array.isArray(n)?n:n.data??[];return{content:[{type:"text",text:oe(o,e.activeProject?.id)}]}}catch(n){return{content:[{type:"text",text:`Failed to list projects: ${n instanceof Error?n.message:String(n)}`}],isError:!0}}}},Ee={name:"set_active_project",description:"Set the active project for subsequent operations. All project-scoped tools (events, incentives, analytics, context) operate on the active project. IMPORTANT: Before calling this tool, you MUST first call `list_projects` and display the full project list to the user so they can choose. Never ask 'which project?' without showing the list. Switching projects changes which data all subsequent tools operate on.",requires:["auth"],inputSchema:{projectId:R.string().describe("The project ID to set as active")},handler:async(t,e)=>{let r=h(e);if(r)return r;try{let n=t.projectId,o=await e.apiRequest(`/projects/${n}`);return e.setActiveProject({id:o.id,name:o.name}),{content:[{type:"text",text:`Active project set to "${o.name}" (ID: ${o.id}).`}]}}catch(n){return{content:[{type:"text",text:`Failed to set active project: ${n instanceof Error?n.message:String(n)}`}],isError:!0}}}};import{z as f}from"zod";var Ae="tokenswap_partitioned",_e="launchlabtrade_partitioned",ke="mplxgenesislaunchpooldepositwithdraw_partitioned",q="tokenbalance_partitioned",wt=[/'/,/"/,/;/,/--/,/\/\*/,/\\/];function I(t,e){for(let r of wt)if(r.test(t))throw new Error(`Invalid ${e}: contains forbidden characters. Value must not contain quotes, semicolons, comments, or backslashes.`);return t}function L(t){return{content:[{type:"text",text:`${t.description}
31
+ `);let r=[`\u{1F4C1} **Your Projects** (${t.length})`,"","| Project | ID | Status |","|---|---|---|"];for(let n of t){let i=n.id===e?"\u2705 active":"";r.push(`| **${n.name}** | \`${n.id}\` | ${i} |`)}return r.join(`
32
+ `)}function se(t){let e=["\u{1F4CB} **New Project Preview**",x,"",` Name: ${t.name}`];t.description&&e.push(` Description: ${t.description}`),t.image&&e.push(` Image: ${t.image}`),t.defaultTokenAddress&&e.push(` Token: ${t.defaultTokenAddress}`),t.defaultProgramAddress&&e.push(` Program: ${t.defaultProgramAddress}`);let r=t.adminWallets;if(r&&r.length>0){e.push(""),e.push(` Admins (${r.length}):`);for(let n of r)e.push(` ${n}`)}return e.push("",x,"\u26A0\uFE0F Confirm to proceed."),e.join(`
33
+ `)}function ae(t){if(t.length===0)return["\u{1F4CB} **No custom events on this project**","","To get started:"," \u2022 Use `attach_custom_event` to attach an existing event"," \u2022 Use `create_custom_event` to create a new one first"].join(`
34
+ `);let e=[`\u{1F4CB} **Custom Events on Project** (${t.length})`,"","| Event | ID | Fields | Status |","|---|---|---|---|"];for(let r of t){let n=r.columnMapping?"\u2705 query-ready":"\u26A0\uFE0F awaiting first ingestion",i=r.fields.map(o=>`\`${o.fieldName}\` (${o.type})`).join(", ");e.push(`| **${r.name}** \`${r.eventName}\` | \`${r.id}\` | ${i} | ${n} |`)}return e.join(`
35
+ `)}function ce(t,e){if(t.length===0)return["\u{1F4CB} **No custom events found**","","Use `create_custom_event` to create one."].join(`
36
+ `);let r=[`\u{1F4CB} **Your Custom Events** (${t.length})`,"","| Event | ID | Fields | Projects |","|---|---|---|---|"];for(let n of t){let i=n.fields.map(s=>`\`${s.fieldName}\` (${s.type})`).join(", "),o;n.projects.length===0?o="\u2014":o=n.projects.map(s=>{let c=e?.get(s.projectId);return c?`**${c}**`:`\`${s.projectId}\``}).join(", "),r.push(`| **${n.name}** \`${n.eventName}\` | \`${n.id}\` | ${i} | ${o} |`)}return r.join(`
37
+ `)}function ue(t){let e=t.fields,r=["\u{1F4CB} **New Custom Event Preview**",x,"",` Event ID: \`${t.eventName}\``,` Name: ${t.name}`,"",` Fields (${e.length}):`];for(let n of e){let i=n.label?` \u2014 ${n.label}`:"";r.push(` \`${n.fieldName}\` (${n.type})${i}`)}return r.push("","\u2139\uFE0F Note: `userPubkey` (wallet address) is always required as a top-level property when sending events \u2014 it is not part of the event schema.","",x,"\u26A0\uFE0F Confirm to proceed."),r.join(`
38
+ `)}function le(t,e,r,n){let i={userPubkey:"<wallet address>",timestamp:Date.now(),eventName:e,data:r.reduce((o,s)=>(o[s.fieldName]=`<${s.type.toLowerCase()}>`,o),{})};return["","\u{1F4E1} **Send events to the ingester**","",`**Endpoint:** \`POST ${t}/events\``,"","**Required headers:**","- `Content-Type: application/json`","- `x-api-key: <your-api-key>` \u2014 use `create_api_key` to generate one","","`userPubkey` (the user's wallet address) and `timestamp` are required top-level properties on every event. Your custom fields go inside `data`.","","```bash",`curl -X POST ${t}/events \\`,' -H "Content-Type: application/json" \\',' -H "x-api-key: <your-api-key>" \\',` -d '${JSON.stringify(i,null,2)}'`,"```","",`\u26A0\uFE0F **An API key is required to send events.** Use \`create_api_key\` to generate one, or manage your keys at [${n}/developer](${n}/developer).`].join(`
39
+ `)}function de(t,e){if(t.length===0)return["\u{1F511} **No API keys found**","","Use `create_api_key` to create one for sending events to the ingestion pipeline.","",`Manage API keys and view ingestion logs at [${e}/developer](${e}/developer).`].join(`
40
+ `);let r=[`\u{1F511} **Your API Keys** (${t.length})`,"","| Name | Key | Status | Last Used | Created |","|---|---|---|---|---|"];for(let n of t){let i=n.name??"\u2014",o=n.isActive?"\u2705 active":"\u274C revoked",s=n.lastUsedAt?new Date(n.lastUsedAt).toLocaleDateString():"never",c=new Date(n.createdAt).toLocaleDateString();r.push(`| ${i} | \`${n.key}\` | ${o} | ${s} | ${c} |`)}return r.push(""),r.push(`Manage API keys and view ingestion logs at [${e}/developer](${e}/developer).`),r.join(`
41
+ `)}function pe(t,e){return["\u{1F511} **API Key Created**","",` Name: ${t.name??"\u2014"}`,` Key: \`${t.key}\``,"","\u26A0\uFE0F **Copy this key now \u2014 it will not be shown again.**","","Use this key in the `x-api-key` header when sending events to the ingestion pipeline.","",`Manage API keys and view ingestion logs at [${e}/developer](${e}/developer).`].join(`
42
+ `)}function fe(t){if(t.length===0)return["\u{1F4CA} **No recurring incentives found**","","Use `create_recurring_incentive` to create one, or use the `create-incentive` prompt for a guided workflow."].join(`
43
+ `);let e=[`\u{1F4CA} **Recurring Incentives** (${t.length})`,"","| Name | ID | Type | Epoch | Status |","|---|---|---|---|---|"];for(let r of t){let n=r.isActive?"\u2705 active":"\u23F8 inactive",i=r.evalDurationDays?`${r.evalDurationDays}-day epochs`:"\u2014";e.push(`| **${r.name}** | \`${r.id}\` | ${r.type} | ${i} | ${n} |`)}return e.join(`
44
+ `)}function me(t){let e=t.isActive?"\u2705 active":"\u23F8 inactive",r=[`\u{1F4CA} **${t.name||"Incentive Detail"}**`,"","| | |","|---|---|",`| **Status** | ${e} |`,`| **ID** | \`${t.id}\` |`];t.type&&r.push(`| **Type** | ${t.type} |`),t.evalDurationDays&&r.push(`| **Eval Period** | ${t.evalDurationDays} day(s) |`),t.startDate&&r.push(`| **Start** | ${t.startDate} |`),t.maxIterations&&r.push(`| **Max Epochs** | ${t.maxIterations} |`);let n=t.distributionConfig;n&&(n.distributionType&&r.push(`| **Distribution** | ${n.distributionType} |`),n.emissionType&&r.push(`| **Emission** | ${n.emissionType} |`),n.totalFundAmount!==void 0&&r.push(`| **Total Fund** | ${n.totalFundAmount} |`),n.customFormula&&r.push(`| **Formula** | \`${n.customFormula}\` |`));let i=t.offerMetadata;i?.description&&(r.push(""),r.push(`${i.description}`));let o=t.configs;if(Array.isArray(o)&&o.length>0){r.push(""),r.push("### Epochs"),r.push(""),r.push("| Epoch | Status | Eval Window | Claim Window | Offer ID | Query ID |"),r.push("| --- | --- | --- | --- | --- | --- |");for(let s of o){let c=s.epochNumber??"\u2014",a=s.status??"\u2014",u=s.evalStart?new Date(s.evalStart).toISOString().split("T")[0]:"\u2014",l=s.evalEnd?new Date(s.evalEnd).toISOString().split("T")[0]:"\u2014",d=s.claimStart?new Date(s.claimStart).toISOString().split("T")[0]:"\u2014",f=s.claimEnd?new Date(s.claimEnd).toISOString().split("T")[0]:"\u2014",p=s.sqlQueryId?`\`${s.sqlQueryId}\``:"\u2014",m=s.offerId??s.offer?.id,T=m?`\`${m}\``:"\u2014";r.push(`| ${c} | ${a} | ${u} \u2192 ${l} | ${d} \u2192 ${f} | ${T} | ${p} |`)}}return r.join(`
45
+ `)}function he(t){let e={leaderboard:"Leaderboard Competition",rebate:"Rebate",raffle:"Raffle",direct:"Direct Distribution"},r=t.type,n=["\u{1F4CB} **New Incentive Preview**",x,"",` Name: ${t.name}`,` Type: ${e[r]||r}`,` Emission: ${t.emissionType}`,` Eval Period: ${t.evalDurationDays} day(s)`,` Start: ${t.startDate}`];if(t.description&&n.push(` Description: ${t.description}`),r==="direct"){n.push(" Claim Window: 24 hours");let i=t.allocations;if(i&&i.length>0){let o=i.reduce((s,c)=>s+c.amount,0);n.push(""),n.push(` **Allocations** (${i.length} recipients)`);for(let s of i)n.push(` ${s.address} \u2192 ${s.amount}`);n.push(""),n.push(` Total Fund: ${o}`)}}else if(r==="raffle"){let i=t.raffleBuckets;if(i){let o=i.reduce((s,c)=>s+c.amount*c.count,0);n.push(""),n.push(" **Prize Tiers**");for(let s of i)n.push(` ${s.count}\xD7 winner(s) \u2192 ${s.amount} each`);n.push(""),n.push(` Total Fund: ${o}`)}}else r==="rebate"?(n.push(` Rebate: ${t.rebatePercentage}%`),n.push(` Total Fund: ${t.totalFundAmount}`)):n.push(` Total Fund: ${t.totalFundAmount}`);return t.sqlQuery&&(n.push(""),n.push(" **SQL Query**"),n.push(` \`${t.sqlQuery}\``)),t.customEventId&&n.push(` Event ID: ${t.customEventId}`),t.customFormula&&n.push(` Formula: ${t.customFormula}`),t.maxIterations&&n.push(` Max Epochs: ${t.maxIterations}`),t.maxPerParticipant&&n.push(` Max/User: ${t.maxPerParticipant}`),n.push("",x,"\u26A0\uFE0F Confirm to proceed."),n.join(`
46
+ `)}var ge={name:"authenticate",description:"Authenticate with Torque. Accepts an auth token directly, or \u2014 if a private key is configured \u2014 automatically signs in using the wallet. If neither is available, returns instructions to get a token.",inputSchema:{authToken:bt.string().optional().describe("Torque auth token for authentication (optional if privateKey is configured)")},handler:async(t,e)=>{try{let r=t.authToken;if(r)return e.auth.setAuthToken(r),{content:[{type:"text",text:"\u2705 Successfully authenticated with auth token."}]};if(e.config.privateKey){let n=await e.getWallet(),i=new TextEncoder().encode(e.config.signStatement),o=await n.signMessage(i),s=Tt().decode(o),c=await e.auth.loginWithStatement(n.address,e.config.signStatement,s);return await e.auth.setToken(c,n.address),{content:[{type:"text",text:`\u2705 Successfully authenticated with wallet \`${n.address}\`.`}]}}return{content:[{type:"text",text:D(e.config.platformUrl)}],isError:!0}}catch(r){return{content:[{type:"text",text:`Authentication failed: ${r instanceof Error?r.message:String(r)}`}],isError:!0}}}},ye={name:"check_auth_status",description:"Check if you are currently authenticated with Torque. Verifies the stored token with the server. ALWAYS call this automatically at the start of every session before attempting any other Torque operation. If not authenticated, use `authenticate`.",inputSchema:{},handler:async(t,e)=>e.auth.getToken()?await e.auth.verify()?{content:[{type:"text",text:"\u2705 You are authenticated. Your token is valid."}]}:{content:[{type:"text",text:`\u{1F504} Your token has expired or is invalid.
47
+
48
+ `+D(e.config.platformUrl)}]}:{content:[{type:"text",text:D(e.config.platformUrl)}]}},ve={name:"logout",description:"Log out from Torque. Clears the stored authentication token from this session and local cache.",inputSchema:{},handler:async(t,e)=>{try{return await e.auth.clearToken(),{content:[{type:"text",text:"Successfully logged out. Your authentication has been cleared."}]}}catch(r){return{content:[{type:"text",text:`Logout failed: ${r instanceof Error?r.message:String(r)}`}],isError:!0}}}};import{z as Et}from"zod";function h(t){return t.auth.getToken()?null:{content:[{type:"text",text:D(t.config.platformUrl)}],isError:!0}}function w(t){return t.activeProject?null:{content:[{type:"text",text:"\u{1F4CB} No active project set. Use `set_active_project` or `create_project` first."}],isError:!0}}var we={name:"list_api_keys",description:"List your API keys for the Torque event ingestion pipeline. These keys are used in the `x-api-key` header when sending custom events to the ingester. Keys are shown masked \u2014 to manage or revoke keys, visit the developer dashboard. IMPORTANT: Always display the full list to the user in your response. IMPORTANT: Always include the developer dashboard link from the response so the user knows where to manage keys and view ingestion logs.",requires:["auth"],inputSchema:{},handler:async(t,e)=>{let r=h(e);if(r)return r;try{let n=await e.apiRequest("/api-keys"),i=Array.isArray(n)?n:n.data??[];return{content:[{type:"text",text:de(i,e.config.platformUrl)}]}}catch(n){return{content:[{type:"text",text:`\u274C Failed to list API keys: ${n instanceof Error?n.message:String(n)}`}],isError:!0}}}},be={name:"create_api_key",description:"Create a new API key for sending custom events to the Torque ingestion pipeline. The key is shown once upon creation \u2014 it cannot be retrieved later. Use this key in the `x-api-key` header when sending events via the ingester. IMPORTANT: Always include the developer dashboard link from the response so the user knows where to manage keys and view ingestion logs.",requires:["auth"],inputSchema:{name:Et.string().optional().describe("Optional display name for the API key. If omitted, the API generates a default name.")},handler:async(t,e)=>{let r=h(e);if(r)return r;try{let n={};t.name&&(n.name=t.name);let i=await e.apiRequest("/api-keys",{method:"POST",body:n});return{content:[{type:"text",text:pe(i,e.config.platformUrl)}]}}catch(n){return{content:[{type:"text",text:`\u274C Failed to create API key: ${n instanceof Error?n.message:String(n)}`}],isError:!0}}}};import{z as I}from"zod";var At=.05,Te={name:"create_project",description:"Create a new project on Torque. Projects are the workspace for incentives, custom events, and analytics. The created project automatically becomes your active project for subsequent operations. IMPORTANT: Before creating a project, always ask the user what type of project this is: (1) Token team \u2014 they have a token and want to incentivize activity around it (swaps, holds, bonding curve). Ask for their token mint address and set it as `defaultTokenAddress`. (2) Protocol/product \u2014 they have an on-chain program and want to incentivize interactions with it. Ask for their program address and set it as `defaultProgramAddress`. They may also have a token \u2014 ask if they do and collect both if so. This ensures incentives can be created efficiently without asking for addresses every time.",requires:["auth"],inputSchema:{name:I.string().describe("Project name"),description:I.string().optional().describe("Project description"),image:I.string().optional().describe("Project image URL"),defaultTokenAddress:I.string().optional().describe("Default SPL token mint address for the project. This is the token they want to incentivize \u2014 used as the default for swap, hold, and bonding curve incentives. Essential for token teams."),defaultProgramAddress:I.string().optional().describe("Default on-chain program address for the project. This is the program they want to track interactions for \u2014 used for IDL instruction incentives. Typically provided by protocol/product teams."),adminWallets:I.array(I.string()).optional().describe("Wallet addresses to add as project admins"),confirmed:I.boolean().optional().describe("Set to true to confirm and execute the creation. Omit to preview first.")},handler:async(t,e)=>{let r=h(e);if(r)return r;if(!t.confirmed)return{content:[{type:"text",text:se(t)}]};try{let n={name:t.name,protocolFee:At};if(t.description&&(n.description=t.description),t.image&&(n.image=t.image),t.defaultTokenAddress&&(n.defaultTokenAddress=t.defaultTokenAddress),t.defaultProgramAddress&&(n.defaultProgramAddress=t.defaultProgramAddress),t.adminWallets){let o=t.adminWallets;n.roles=o.map(s=>({userPubKey:s,role:"ADMIN"}))}let i=await e.apiRequest("/projects",{method:"POST",body:n});return e.setActiveProject({id:i.id,name:i.name}),{content:[{type:"text",text:`Project "${i.name}" created successfully (ID: ${i.id}). It is now your active project.`}]}}catch(n){return{content:[{type:"text",text:`Failed to create project: ${n instanceof Error?n.message:String(n)}`}],isError:!0}}}},Ee={name:"list_projects",description:"List all your Torque projects and show which one is currently active. AUTOMATICALLY call this after authentication succeeds to show the user their projects. Also use when the user mentions projects, wants to switch projects, or needs a project ID for `set_active_project`. If no project is active, suggest selecting one \u2014 most tools require an active project. IMPORTANT: Always display the full project list to the user in your response.",requires:["auth"],inputSchema:{},handler:async(t,e)=>{let r=h(e);if(r)return r;try{let n=await e.apiRequest("/projects"),i=Array.isArray(n)?n:n.data??[];return{content:[{type:"text",text:oe(i,e.activeProject?.id)}]}}catch(n){return{content:[{type:"text",text:`Failed to list projects: ${n instanceof Error?n.message:String(n)}`}],isError:!0}}}},Ae={name:"set_active_project",description:"Set the active project for subsequent operations. All project-scoped tools (events, incentives, analytics, context) operate on the active project. IMPORTANT: Before calling this tool, you MUST first call `list_projects` and display the full project list to the user so they can choose. Never ask 'which project?' without showing the list. Switching projects changes which data all subsequent tools operate on.",requires:["auth"],inputSchema:{projectId:I.string().describe("The project ID to set as active")},handler:async(t,e)=>{let r=h(e);if(r)return r;try{let n=t.projectId,i=await e.apiRequest(`/projects/${n}`);return e.setActiveProject({id:i.id,name:i.name}),{content:[{type:"text",text:`Active project set to "${i.name}" (ID: ${i.id}).`}]}}catch(n){return{content:[{type:"text",text:`Failed to set active project: ${n instanceof Error?n.message:String(n)}`}],isError:!0}}}};import{z as g}from"zod";var _e="tokenswap_partitioned",ke="launchlabtrade_partitioned",Ie="mplxgenesislaunchpooldepositwithdraw_partitioned",L="tokenbalance_partitioned",_t=[/'/,/"/,/;/,/--/,/\/\*/,/\\/];function R(t,e){for(let r of _t)if(r.test(t))throw new Error(`Invalid ${e}: contains forbidden characters. Value must not contain quotes, semicolons, comments, or backslashes.`);return t}function O(t){return{content:[{type:"text",text:`${t.description}
49
49
 
50
50
  \`\`\`sql
51
51
  ${t.sql}
52
52
  \`\`\`
53
53
 
54
54
  Use this query with \`create_recurring_incentive\` to set up the incentive.`},{type:"text",text:JSON.stringify({sql:t.sql,...t.metadata})}]}}function Q(t){return Array.isArray(t)?t:t.split(`
55
- `).map(e=>e.trim()).filter(e=>e.length>0).map(e=>{let r=e.split(",");if(r.length<2)throw new Error(`Invalid allocation line: "${e}". Expected "address,amount".`);let n=r[0].trim(),o=Number(r[1].trim());if(isNaN(o))throw new Error(`Invalid amount in line: "${e}". Amount must be a number.`);return{address:n,amount:o}})}var B={leaderboard:{name:"leaderboard",displayName:"Leaderboard Competition",description:"Rank-based reward distribution. IMPORTANT: Always ask the user how they want rewards distributed before creating. Common patterns: fixed rank prizes (1st=10000, 2nd=5000, etc.), tiered ranges (top 10 get X, 11-50 get Y), or proportional splits (TOTAL_REWARD_POOL / TOTAL_PARTICIPANTS). The fallback formula N pays each user their raw metric value \u2014 this is rarely desired since it would e.g. pay a trader back their full volume. Build a customFormula from the user's stated payout intent.",type:"LEADERBOARD",distributionType:"FORMULA",customFormula:"N",primitive:{type:"COMPETITION"},requiredInputs:[]},rebate:{name:"rebate",displayName:"Rebate",description:"Percentage-based return on a tracked value. The MCP builds the formula automatically from rebatePercentage (e.g. 5% \u2192 VALUE * 0.05). The query determines what value is measured (spend, volume, etc.).",type:"REBATE",distributionType:"FORMULA",formulaTemplate:"VALUE * {rebateMultiplier}",primitive:{type:"REBATE"},requiredInputs:["rebatePercentage"]},raffle:{name:"raffle",displayName:"Raffle",description:"Random winner selection from qualifying users. Requires prize tier configuration (raffleBuckets). Default weighting is WEIGHTED_BY_METRIC \u2014 each user's query metric value determines their ticket count (more activity = more raffle entries). Use raffleWeighting: 'EQUAL_CHANCES' for equal odds regardless of metric value.",type:"RAFFLE",distributionType:"RAFFLE",primitive:{type:"RAFFLE"},requiredInputs:["raffleBuckets"]},direct:{name:"direct",displayName:"Direct Distribution",description:"Fixed amounts to specific wallet addresses. Provide a list of allocations \u2014 the MCP constructs an inline SQL query and uses FORMULA distribution internally. Recipients must claim their rewards.",type:"LEADERBOARD",distributionType:"FORMULA",customFormula:"N",primitive:{type:"COMPETITION"},requiredInputs:["allocations"]}};function bt(t,e){if(!(t in B))return`Unknown type "${t}". Valid types: leaderboard, rebate, raffle, direct. See the torque://incentive-types resource for details.`;let r=B[t];for(let o of r.requiredInputs)if(e[o]===void 0||e[o]===null)return`Type "${t}" requires "${o}" to be provided.`;if(e.emissionType==="TOKENS"){if(!e.tokenAddress)return"emissionType TOKENS requires tokenAddress to be provided.";if(e.tokenDecimals===void 0)return"emissionType TOKENS requires tokenDecimals to be provided."}return null}function Tt(t,e){let r=new Date(e.startDate),n=e.evalDurationDays,o=new Date(r.getTime()+n*24*60*60*1e3),i={distributionType:t.distributionType,distributionMethod:"CLAIM",claimWindowStart:o.toISOString(),claimWindowDuration:604800,emissionType:e.emissionType,totalFundAmount:e.totalFundAmount};e.maxPerParticipant!==void 0&&(i.maxPerParticipant=e.maxPerParticipant),e.emissionType==="TOKENS"&&(i.tokenAddress=e.tokenAddress,i.tokenDecimals=e.tokenDecimals);let s=e.customFormula;if(t.name==="leaderboard")i.customFormula=s||"N";else if(t.name==="rebate"){let c=e.rebatePercentage;i.customFormula=s||`VALUE * ${c/100}`}else if(t.name==="raffle"){let c=e.raffleBuckets;i.prizeBuckets=c,i.totalFundAmount=c.reduce((u,l)=>u+l.amount*l.count,0);let a=e.raffleWeighting;i.selectionLogic=a==="EQUAL_CHANCES"?"EQUAL_CHANCES":"WEIGHTED_BY_METRIC",i.allowDuplicateWinners=!1}else if(t.name==="direct"){let c=Q(e.allocations),a=e.customFormula;i.customFormula=a||"N",i.totalFundAmount=c.reduce((u,l)=>u+l.amount,0)}return i}var Re={DAILY:1,WEEKLY:7,MONTHLY:30};function Et(t){if(t.evalDurationDays)return t.evalDurationDays;let e=t.interval;return e&&e in Re?Re[e]:null}var Ie={name:"create_recurring_incentive",requires:["auth","activeProject"],description:"Create a recurring incentive (reward program) for the active project. Consider calling `get_ai_context` first to understand the project's domain. An incentive has three layers: (1) a query that defines how users are tracked and measured \u2014 use `generate_incentive_query` with the appropriate `source` param (swap, bonding_curve, hold, custom_event, or idl_instruction), or provide raw SQL via `sqlQuery`, (2) a type that determines reward distribution \u2014 leaderboard (rank-based), rebate (percentage-back), raffle (random winners), or direct (fixed allocations), and (3) reward configuration (tokens/SOL, amounts, frequency). IMPORTANT for leaderboard: Always ask the user how they want rewards distributed before creating. Common payout patterns: fixed rank prizes ('1st gets 10000, 2nd gets 5000'), tiered ranges ('top 50 split the pool'), or proportional splits. Build a `customFormula` from their stated intent \u2014 do not silently default to `N` which pays raw metric values. IMPORTANT for raffle: Ask the user how raffle tickets should be distributed \u2014 WEIGHTED_BY_METRIC (default) means users with higher query values (e.g., more volume) get more tickets and higher chances of winning, while EQUAL_CHANCES means every qualifying user gets exactly 1 ticket regardless of their metric value. The query results must be sorted (highest value first) for weighted raffles to work correctly. See `torque://incentive-types` resource for formula variables, type details, and examples. For custom event incentives: list_project_events \u2192 generate_incentive_query (source: \"custom_event\") \u2192 create_recurring_incentive. For IDL instruction incentives: list_idls \u2192 generate_incentive_query (source: \"idl_instruction\") \u2192 create_recurring_incentive.",inputSchema:{name:f.string().describe("Display name for the recurring incentive"),description:f.string().optional().describe("Optional description"),type:f.string().describe("Incentive type: 'leaderboard', 'rebate', 'raffle', or 'direct'. Determines the reward distribution shape and what additional inputs are required."),emissionType:f.enum(["TOKENS","SOL"]).describe("Type of reward emission"),tokenAddress:f.string().optional().describe("SPL token mint address (required if emissionType is TOKENS)"),tokenDecimals:f.number().optional().describe("Token decimal places (required if emissionType is TOKENS)"),totalFundAmount:f.number().describe("Total reward pool per epoch (ignored for raffle \u2014 calculated from buckets)"),evalDurationDays:f.number().optional().describe("Duration of each evaluation period in days. Common values: 1 (daily), 7 (weekly), 30 (monthly). This is how long user activity is tracked before rewards are distributed. Either this or interval must be provided. Takes priority over interval if both given."),interval:f.enum(["DAILY","WEEKLY","MONTHLY"]).optional().describe("Shorthand for evalDurationDays: DAILY=1, WEEKLY=7, MONTHLY=30. Either this or evalDurationDays must be provided."),startDate:f.string().describe("When the first epoch's evaluation period begins (ISO 8601 date string)"),maxIterations:f.number().optional().describe("Maximum number of epochs to run. Omit for unlimited."),maxPerParticipant:f.number().optional().describe("Maximum reward per user per epoch. Useful for capping rewards."),rebatePercentage:f.number().optional().describe("Rebate percentage (required if type is 'rebate'). E.g. 5 for 5%."),raffleBuckets:f.array(f.object({amount:f.number(),count:f.number()})).optional().describe("Prize tiers for raffle (required if type is 'raffle'). Array of {amount, count}."),raffleWeighting:f.enum(["WEIGHTED_BY_METRIC","EQUAL_CHANCES"]).optional().describe("How raffle tickets are assigned. Default: WEIGHTED_BY_METRIC \u2014 each user's query metric value determines their number of tickets (e.g., a user with 100 volume gets 100 tickets vs 10 for someone with 10 volume). Use EQUAL_CHANCES for equal odds regardless of metric value (1 ticket per qualifying user)."),allocations:f.union([f.string(),f.array(f.object({address:f.string(),amount:f.number()}))]).optional().describe("Wallet allocations for direct distribution (required if type is 'direct'). Either a CSV string (address,amount per line) or an array of {address, amount}."),sqlQuery:f.string().optional().describe("Custom SQL query generated by generate_incentive_query. When provided, replaces the default stub query."),customEventId:f.string().optional().describe("ID of the custom event this incentive is based on. Mutually exclusive with instructionId."),instructionId:f.string().optional().describe("ID of the IDL instruction this incentive is based on. Mutually exclusive with customEventId."),customFormula:f.string().optional().describe("Reward formula that produces the direct payout amount for each user. Each user receives exactly what the formula evaluates to, capped by the remaining reward pool (users are processed highest-value first). Variables: N/VALUE (user's metric), RANK (1-based position), INDEX (0-based), TOTAL_PARTICIPANTS, TOTAL_REWARD_POOL. Functions: sqrt, pow, abs, floor, ceil, round, min, max, log, exp. Common patterns: 'RANK == 1 ? 10000 : RANK == 2 ? 5000 : RANK <= 10 ? 1000 : 0' (tiered rank prizes), 'TOTAL_REWARD_POOL / TOTAL_PARTICIPANTS' (equal split), 'min(N * 0.1, 500)' (10% of metric, capped at 500). For leaderboards: always ask the user how they want payouts structured and build the formula from their intent. The fallback N pays raw metric values which is rarely desired (e.g. would pay back full trade volume). For rebates: omit this \u2014 the formula is auto-built from rebatePercentage. See `torque://incentive-types` resource for full reference."),confirmed:f.boolean().optional().describe("Set to true to confirm and execute the creation. Omit to preview first.")},handler:async(t,e)=>{let r=h(e);if(r)return r;let n=E(e);if(n)return n;let o=t.type,i=bt(o,t);if(i)return{content:[{type:"text",text:i}],isError:!0};let s=Et(t);if(!s)return{content:[{type:"text",text:"\u274C Either `evalDurationDays` or `interval` (DAILY, WEEKLY, MONTHLY) must be provided."}],isError:!0};if(t.evalDurationDays=s,!t.confirmed){let u={...t};return o==="direct"&&t.allocations&&(u={...u,allocations:Q(t.allocations)}),{content:[{type:"text",text:fe(u)}]}}let c=B[o],a=e.activeProject.id;try{if(o==="direct"&&t.allocations){let T=Q(t.allocations).map(v=>`('${I(v.address,"address")}', ${v.amount})`).join(", ");t.sqlQuery=`SELECT address, value FROM (VALUES ${T}) AS t(address, value)`}let u=await e.apiRequest(`/project/${a}/query`,{method:"POST",body:{name:`${t.name} - Query - ${Date.now()}`,query:t.sqlQuery||"SELECT 1",paramMap:{startDate:"DATE",endDate:"DATE"}}}),l=Tt(c,t),d={title:t.name,primitive:c.primitive};t.description&&(d.description=t.description);let m={name:t.name,type:c.type,evalDurationDays:s,startDate:t.startDate,sqlQueryId:u.id,distributionConfig:l,offerMetadata:d,metricColumn:"value",addressColumn:"address"};t.description&&(m.description=t.description),t.maxIterations!==void 0&&(m.maxIterations=t.maxIterations),t.customEventId&&(m.customEventId=t.customEventId),t.instructionId&&(m.instructionId=t.instructionId);let p=await e.apiRequest(`/project/${a}/recurring-offer`,{method:"POST",body:m});return{content:[{type:"text",text:`Recurring incentive "${p.name||t.name}" created successfully (ID: ${p.id}). Fund it here: ${e.config.platformUrl}/${a}/incentives`}]}}catch(u){return{content:[{type:"text",text:`Failed to create recurring incentive: ${u instanceof Error?u.message:String(u)}`}],isError:!0}}}},xe={name:"list_recurring_incentives",requires:["auth","activeProject"],description:"List all recurring incentives (reward programs) for the active project. Shows name, type, status, and epoch duration. Use this when the user asks about their incentives, rewards, campaigns, leaderboards, or reward programs. Also use when the user says 'show me what I have', 'what incentives exist', or 'how are my rewards doing'. Provides incentive IDs needed for `get_recurring_incentive` and `get_recurring_incentive_analytics`. IMPORTANT: Always display the full incentive list to the user.",inputSchema:{},handler:async(t,e)=>{let r=h(e);if(r)return r;let n=E(e);if(n)return n;let o=e.activeProject.id;try{let i=await e.apiRequest(`/project/${o}/recurring-offer`);return{content:[{type:"text",text:pe(Array.isArray(i)?i:[])}]}}catch(i){return{content:[{type:"text",text:`Failed to list recurring incentives: ${i instanceof Error?i.message:String(i)}`}],isError:!0}}}},Pe={name:"get_recurring_incentive",requires:["auth","activeProject"],description:"Get full details of a specific recurring incentive \u2014 configuration, distribution settings, current epoch status, and query info. Use this when the user wants to inspect or understand how a specific incentive is set up. Use `list_recurring_incentives` to find the incentive ID.",inputSchema:{recurringOfferId:f.string().describe("The recurring incentive ID")},handler:async(t,e)=>{let r=h(e);if(r)return r;let n=E(e);if(n)return n;let o=e.activeProject.id,i=t.recurringOfferId;try{let s=await e.apiRequest(`/project/${o}/recurring-offer/${i}`);return{content:[{type:"text",text:me(s)}]}}catch(s){return{content:[{type:"text",text:`Failed to get recurring incentive: ${s instanceof Error?s.message:String(s)}`}],isError:!0}}}},Se={name:"get_recurring_incentive_analytics",requires:["auth","activeProject"],description:"Get performance analytics for a recurring incentive \u2014 participation counts, rewards distributed, and epoch-by-epoch history. Use this when the user asks how an incentive is performing, wants stats, or needs to evaluate results. Consider calling `get_ai_context` for project domain context when interpreting results. Use `list_recurring_incentives` to find the incentive ID.",inputSchema:{recurringOfferId:f.string().describe("The recurring incentive ID")},handler:async(t,e)=>{let r=h(e);if(r)return r;let n=E(e);if(n)return n;let o=e.activeProject.id,i=t.recurringOfferId;try{let s=await e.apiRequest(`/project/${o}/recurring-offer/${i}/analytics`);return{content:[{type:"text",text:JSON.stringify(s,null,2)}]}}catch(s){return{content:[{type:"text",text:`Failed to get recurring incentive analytics: ${s instanceof Error?s.message:String(s)}`}],isError:!0}}}};import{z as k}from"zod";var De={name:"create_custom_event",description:"Create a custom event schema for tracking off-chain user activity (e.g. purchases, game actions, signups). Use this when the user wants to track activity that isn't on-chain. Events are independent of projects \u2014 they can be attached to one or multiple projects via `attach_custom_event`. Does NOT require an active project \u2014 do not prompt the user to select a project before creating an event. If called during an existing workflow (e.g. incentive creation), continue that workflow after creation. RULES: (1) Never include wallet/pubkey as a field \u2014 `userPubkey` is a required top-level event property, not a schema field. (2) Confirm the `eventName` with the user before creating \u2014 suggest a snake_case version of the display name (e.g. 'Sign Up' \u2192 'sign_up'), but let them choose. (3) After creation: if an active project exists, offer to attach the event to it by name. If no active project, ask if they want to attach to a project \u2014 if yes, call `list_projects` to let them choose, then `set_active_project` + `attach_custom_event`.",requires:["auth"],inputSchema:{eventName:k.string().describe("Identifier for the custom event"),name:k.string().describe("Display name for the custom event"),confirmed:k.boolean().optional().describe("Set to true to confirm and execute the creation. Omit to preview first."),fields:k.array(k.object({fieldName:k.string().describe("Field name"),type:k.enum(["string","boolean","number"]).describe("Field data type"),label:k.string().optional().describe("Display label for the field"),description:k.string().optional().describe("Description of what this field represents")})).describe("Fields that define the event schema")},handler:async(t,e)=>{let r=h(e);if(r)return r;let n=t.fields;if(!n.length)return{content:[{type:"text",text:"Custom event must have at least one field."}],isError:!0};if(!t.confirmed)return{content:[{type:"text",text:ce(t)}]};try{let o=await e.apiRequest("/events",{method:"POST",body:{eventName:t.eventName,name:t.name,fields:n}}),i=ue(e.config.ingesterUrl,t.eventName,n,e.config.platformUrl),s=e.activeProject,c=s?`
55
+ `).map(e=>e.trim()).filter(e=>e.length>0).map(e=>{let r=e.split(",");if(r.length<2)throw new Error(`Invalid allocation line: "${e}". Expected "address,amount".`);let n=r[0].trim(),i=Number(r[1].trim());if(isNaN(i))throw new Error(`Invalid amount in line: "${e}". Amount must be a number.`);return{address:n,amount:i}})}var G={leaderboard:{name:"leaderboard",displayName:"Leaderboard Competition",description:"Rank-based reward distribution. IMPORTANT: Always ask the user how they want rewards distributed before creating. Common patterns: fixed rank prizes (1st=10000, 2nd=5000, etc.), tiered ranges (top 10 get X, 11-50 get Y), or proportional splits (TOTAL_REWARD_POOL / TOTAL_PARTICIPANTS). The fallback formula N pays each user their raw metric value \u2014 this is rarely desired since it would e.g. pay a trader back their full volume. Build a customFormula from the user's stated payout intent.",type:"LEADERBOARD",distributionType:"FORMULA",customFormula:"N",primitive:{type:"COMPETITION"},requiredInputs:[]},rebate:{name:"rebate",displayName:"Rebate",description:"Percentage-based return on a tracked value. The MCP builds the formula automatically from rebatePercentage (e.g. 5% \u2192 VALUE * 0.05). The query determines what value is measured (spend, volume, etc.).",type:"REBATE",distributionType:"FORMULA",formulaTemplate:"VALUE * {rebateMultiplier}",primitive:{type:"REBATE"},requiredInputs:["rebatePercentage"]},raffle:{name:"raffle",displayName:"Raffle",description:"Random winner selection from qualifying users. Requires prize tier configuration (raffleBuckets). Default weighting is WEIGHTED_BY_METRIC \u2014 each user's query metric value determines their ticket count (more activity = more raffle entries). Use raffleWeighting: 'EQUAL_CHANCES' for equal odds regardless of metric value.",type:"RAFFLE",distributionType:"RAFFLE",primitive:{type:"RAFFLE"},requiredInputs:["raffleBuckets"]},direct:{name:"direct",displayName:"Direct Distribution",description:"Fixed amounts to specific wallet addresses. Provide a list of allocations \u2014 the MCP constructs an inline SQL query and uses FORMULA distribution internally. Recipients must claim their rewards.",type:"LEADERBOARD",distributionType:"FORMULA",customFormula:"N",primitive:{type:"COMPETITION"},requiredInputs:["allocations"]}};function kt(t,e){if(!(t in G))return`Unknown type "${t}". Valid types: leaderboard, rebate, raffle, direct. See the torque://incentive-types resource for details.`;let r=G[t];for(let i of r.requiredInputs)if(e[i]===void 0||e[i]===null)return`Type "${t}" requires "${i}" to be provided.`;if(e.emissionType==="TOKENS"){if(!e.tokenAddress)return"emissionType TOKENS requires tokenAddress to be provided.";if(e.tokenDecimals===void 0)return"emissionType TOKENS requires tokenDecimals to be provided."}return null}function It(t,e){let r=new Date(e.startDate),n=e.evalDurationDays,i=new Date(r.getTime()+n*24*60*60*1e3),o={distributionType:t.distributionType,distributionMethod:"CLAIM",claimWindowStart:i.toISOString(),claimWindowDuration:604800,emissionType:e.emissionType,totalFundAmount:e.totalFundAmount};e.maxPerParticipant!==void 0&&(o.maxPerParticipant=e.maxPerParticipant),e.emissionType==="TOKENS"&&(o.tokenAddress=e.tokenAddress,o.tokenDecimals=e.tokenDecimals);let s=e.customFormula;if(t.name==="leaderboard")o.customFormula=s||"N";else if(t.name==="rebate"){let c=e.rebatePercentage;o.customFormula=s||`VALUE * ${c/100}`}else if(t.name==="raffle"){let c=e.raffleBuckets;o.prizeBuckets=c,o.totalFundAmount=c.reduce((u,l)=>u+l.amount*l.count,0);let a=e.raffleWeighting;o.selectionLogic=a==="EQUAL_CHANCES"?"EQUAL_CHANCES":"WEIGHTED_BY_METRIC",o.allowDuplicateWinners=!1}else if(t.name==="direct"){let c=Q(e.allocations),a=e.customFormula;o.customFormula=a||"N",o.totalFundAmount=c.reduce((u,l)=>u+l.amount,0)}return o}var Re={DAILY:1,WEEKLY:7,MONTHLY:30};function Rt(t){if(t.evalDurationDays)return t.evalDurationDays;let e=t.interval;return e&&e in Re?Re[e]:null}var Pe={name:"create_recurring_incentive",requires:["auth","activeProject"],description:"Create a recurring incentive (reward program) for the active project. Consider calling `get_ai_context` first to understand the project's domain. An incentive has three layers: (1) a query that defines how users are tracked and measured \u2014 use `generate_incentive_query` with the appropriate `source` param (swap, bonding_curve, hold, custom_event, or idl_instruction), or provide raw SQL via `sqlQuery`, (2) a type that determines reward distribution \u2014 leaderboard (rank-based), rebate (percentage-back), raffle (random winners), or direct (fixed allocations), and (3) reward configuration (tokens/SOL, amounts, frequency). IMPORTANT for leaderboard: Always ask the user how they want rewards distributed before creating. Common payout patterns: fixed rank prizes ('1st gets 10000, 2nd gets 5000'), tiered ranges ('top 50 split the pool'), or proportional splits. Build a `customFormula` from their stated intent \u2014 do not silently default to `N` which pays raw metric values. IMPORTANT for raffle: Ask the user how raffle tickets should be distributed \u2014 WEIGHTED_BY_METRIC (default) means users with higher query values (e.g., more volume) get more tickets and higher chances of winning, while EQUAL_CHANCES means every qualifying user gets exactly 1 ticket regardless of their metric value. The query results must be sorted (highest value first) for weighted raffles to work correctly. See `torque://incentive-types` resource for formula variables, type details, and examples. For custom event incentives: list_project_events \u2192 generate_incentive_query (source: \"custom_event\") \u2192 create_recurring_incentive. For IDL instruction incentives: list_idls \u2192 generate_incentive_query (source: \"idl_instruction\") \u2192 create_recurring_incentive.",inputSchema:{name:g.string().describe("Display name for the recurring incentive"),description:g.string().optional().describe("Optional description"),type:g.string().describe("Incentive type: 'leaderboard', 'rebate', 'raffle', or 'direct'. Determines the reward distribution shape and what additional inputs are required."),emissionType:g.enum(["TOKENS","SOL"]).describe("Type of reward emission"),tokenAddress:g.string().optional().describe("SPL token mint address (required if emissionType is TOKENS)"),tokenDecimals:g.number().optional().describe("Token decimal places (required if emissionType is TOKENS)"),totalFundAmount:g.number().describe("Total reward pool per epoch (ignored for raffle \u2014 calculated from buckets)"),evalDurationDays:g.number().optional().describe("Duration of each evaluation period in days. Common values: 1 (daily), 7 (weekly), 30 (monthly). This is how long user activity is tracked before rewards are distributed. Either this or interval must be provided. Takes priority over interval if both given."),interval:g.enum(["DAILY","WEEKLY","MONTHLY"]).optional().describe("Shorthand for evalDurationDays: DAILY=1, WEEKLY=7, MONTHLY=30. Either this or evalDurationDays must be provided."),startDate:g.string().describe("When the first epoch's evaluation period begins (ISO 8601 date string)"),maxIterations:g.number().optional().describe("Maximum number of epochs to run. Omit for unlimited."),maxPerParticipant:g.number().optional().describe("Maximum reward per user per epoch. Useful for capping rewards."),rebatePercentage:g.number().optional().describe("Rebate percentage (required if type is 'rebate'). E.g. 5 for 5%."),raffleBuckets:g.array(g.object({amount:g.number(),count:g.number()})).optional().describe("Prize tiers for raffle (required if type is 'raffle'). Array of {amount, count}."),raffleWeighting:g.enum(["WEIGHTED_BY_METRIC","EQUAL_CHANCES"]).optional().describe("How raffle tickets are assigned. Default: WEIGHTED_BY_METRIC \u2014 each user's query metric value determines their number of tickets (e.g., a user with 100 volume gets 100 tickets vs 10 for someone with 10 volume). Use EQUAL_CHANCES for equal odds regardless of metric value (1 ticket per qualifying user)."),allocations:g.union([g.string(),g.array(g.object({address:g.string(),amount:g.number()}))]).optional().describe("Wallet allocations for direct distribution (required if type is 'direct'). Either a CSV string (address,amount per line) or an array of {address, amount}."),sqlQuery:g.string().optional().describe("Custom SQL query generated by generate_incentive_query. When provided, replaces the default stub query."),customEventId:g.string().optional().describe("ID of the custom event this incentive is based on. Mutually exclusive with instructionId."),instructionId:g.string().optional().describe("ID of the IDL instruction this incentive is based on. Mutually exclusive with customEventId."),customFormula:g.string().optional().describe("Reward formula that produces the direct payout amount for each user. Each user receives exactly what the formula evaluates to, capped by the remaining reward pool (users are processed highest-value first). Variables: N/VALUE (user's metric), RANK (1-based position), INDEX (0-based), TOTAL_PARTICIPANTS, TOTAL_REWARD_POOL. Functions: sqrt, pow, abs, floor, ceil, round, min, max, log, exp. Common patterns: 'RANK == 1 ? 10000 : RANK == 2 ? 5000 : RANK <= 10 ? 1000 : 0' (tiered rank prizes), 'TOTAL_REWARD_POOL / TOTAL_PARTICIPANTS' (equal split), 'min(N * 0.1, 500)' (10% of metric, capped at 500). For leaderboards: always ask the user how they want payouts structured and build the formula from their intent. The fallback N pays raw metric values which is rarely desired (e.g. would pay back full trade volume). For rebates: omit this \u2014 the formula is auto-built from rebatePercentage. See `torque://incentive-types` resource for full reference."),confirmed:g.boolean().optional().describe("Set to true to confirm and execute the creation. Omit to preview first.")},handler:async(t,e)=>{let r=h(e);if(r)return r;let n=w(e);if(n)return n;let i=t.type,o=kt(i,t);if(o)return{content:[{type:"text",text:o}],isError:!0};let s=Rt(t);if(!s)return{content:[{type:"text",text:"\u274C Either `evalDurationDays` or `interval` (DAILY, WEEKLY, MONTHLY) must be provided."}],isError:!0};if(t.evalDurationDays=s,!t.confirmed){let u={...t};return i==="direct"&&t.allocations&&(u={...u,allocations:Q(t.allocations)}),{content:[{type:"text",text:he(u)}]}}let c=G[i],a=e.activeProject.id;try{if(i==="direct"&&t.allocations){let T=Q(t.allocations).map(b=>`('${R(b.address,"address")}', ${b.amount})`).join(", ");t.sqlQuery=`SELECT address, value FROM (VALUES ${T}) AS t(address, value)`}let u=await e.apiRequest(`/project/${a}/query`,{method:"POST",body:{name:`${t.name} - Query - ${Date.now()}`,query:t.sqlQuery||"SELECT 1",paramMap:{startDate:"DATE",endDate:"DATE"}}}),l=It(c,t),d={title:t.name,primitive:c.primitive};t.description&&(d.description=t.description);let f={name:t.name,type:c.type,evalDurationDays:s,startDate:t.startDate,sqlQueryId:u.id,distributionConfig:l,offerMetadata:d,metricColumn:"value",addressColumn:"address"};t.description&&(f.description=t.description),t.maxIterations!==void 0&&(f.maxIterations=t.maxIterations),t.customEventId&&(f.customEventId=t.customEventId),t.instructionId&&(f.instructionId=t.instructionId);let p=await e.apiRequest(`/project/${a}/recurring-offer`,{method:"POST",body:f});return{content:[{type:"text",text:`Recurring incentive "${p.name||t.name}" created successfully (ID: ${p.id}). Fund it here: ${e.config.platformUrl}/${a}/incentives`}]}}catch(u){return{content:[{type:"text",text:`Failed to create recurring incentive: ${u instanceof Error?u.message:String(u)}`}],isError:!0}}}},xe={name:"list_recurring_incentives",requires:["auth","activeProject"],description:"List all recurring incentives (reward programs) for the active project. Shows name, type, status, and epoch duration. Use this when the user asks about their incentives, rewards, campaigns, leaderboards, or reward programs. Also use when the user says 'show me what I have', 'what incentives exist', or 'how are my rewards doing'. Provides incentive IDs needed for `get_recurring_incentive` and `get_recurring_incentive_analytics`. IMPORTANT: Always display the full incentive list to the user.",inputSchema:{},handler:async(t,e)=>{let r=h(e);if(r)return r;let n=w(e);if(n)return n;let i=e.activeProject.id;try{let o=await e.apiRequest(`/project/${i}/recurring-offer`);return{content:[{type:"text",text:fe(Array.isArray(o)?o:[])}]}}catch(o){return{content:[{type:"text",text:`Failed to list recurring incentives: ${o instanceof Error?o.message:String(o)}`}],isError:!0}}}},De={name:"get_recurring_incentive",requires:["auth","activeProject"],description:"Get full details of a specific recurring incentive \u2014 configuration, distribution settings, current epoch status, and query info. Use this when the user wants to inspect or understand how a specific incentive is set up. Use `list_recurring_incentives` to find the incentive ID.",inputSchema:{recurringOfferId:g.string().describe("The recurring incentive ID")},handler:async(t,e)=>{let r=h(e);if(r)return r;let n=w(e);if(n)return n;let i=e.activeProject.id,o=t.recurringOfferId;try{let s=await e.apiRequest(`/project/${i}/recurring-offer/${o}`);return{content:[{type:"text",text:me(s)}]}}catch(s){return{content:[{type:"text",text:`Failed to get recurring incentive: ${s instanceof Error?s.message:String(s)}`}],isError:!0}}}},Se={name:"get_recurring_incentive_analytics",requires:["auth","activeProject"],description:"Get performance analytics for a recurring incentive \u2014 participation counts, rewards distributed, and epoch-by-epoch history. Use this when the user asks how an incentive is performing, wants stats, or needs to evaluate results. Consider calling `get_ai_context` for project domain context when interpreting results. Use `list_recurring_incentives` to find the incentive ID.",inputSchema:{recurringOfferId:g.string().describe("The recurring incentive ID")},handler:async(t,e)=>{let r=h(e);if(r)return r;let n=w(e);if(n)return n;let i=e.activeProject.id,o=t.recurringOfferId;try{let s=await e.apiRequest(`/project/${i}/recurring-offer/${o}/analytics`);return{content:[{type:"text",text:JSON.stringify(s,null,2)}]}}catch(s){return{content:[{type:"text",text:`Failed to get recurring incentive analytics: ${s instanceof Error?s.message:String(s)}`}],isError:!0}}}};import{z as k}from"zod";var $e={name:"create_custom_event",description:"Create a custom event schema for tracking off-chain user activity (e.g. purchases, game actions, signups). Use this when the user wants to track activity that isn't on-chain. Events are independent of projects \u2014 they can be attached to one or multiple projects via `attach_custom_event`. Does NOT require an active project \u2014 do not prompt the user to select a project before creating an event. If called during an existing workflow (e.g. incentive creation), continue that workflow after creation. RULES: (1) Never include wallet/pubkey as a field \u2014 `userPubkey` is a required top-level event property, not a schema field. (2) Confirm the `eventName` with the user before creating \u2014 suggest a snake_case version of the display name (e.g. 'Sign Up' \u2192 'sign_up'), but let them choose. (3) After creation: if an active project exists, offer to attach the event to it by name. If no active project, ask if they want to attach to a project \u2014 if yes, call `list_projects` to let them choose, then `set_active_project` + `attach_custom_event`.",requires:["auth"],inputSchema:{eventName:k.string().describe("Identifier for the custom event"),name:k.string().describe("Display name for the custom event"),confirmed:k.boolean().optional().describe("Set to true to confirm and execute the creation. Omit to preview first."),fields:k.array(k.object({fieldName:k.string().describe("Field name"),type:k.enum(["string","boolean","number"]).describe("Field data type"),label:k.string().optional().describe("Display label for the field"),description:k.string().optional().describe("Description of what this field represents")})).describe("Fields that define the event schema")},handler:async(t,e)=>{let r=h(e);if(r)return r;let n=t.fields;if(!n.length)return{content:[{type:"text",text:"Custom event must have at least one field."}],isError:!0};if(!t.confirmed)return{content:[{type:"text",text:ue(t)}]};try{let i=await e.apiRequest("/events",{method:"POST",body:{eventName:t.eventName,name:t.name,fields:n}}),o=le(e.config.ingesterUrl,t.eventName,n,e.config.platformUrl),s=e.activeProject,c=s?`
56
56
 
57
- \u{1F4A1} Would you like to attach this event to project "${s.name}"? Use \`attach_custom_event\` to attach it.`:"\n\n\u{1F4A1} Would you like to attach this event to a project? Use `list_projects` to see your projects, then `set_active_project` and `attach_custom_event` to attach it.";return{content:[{type:"text",text:`Custom event "${t.eventName}" created successfully (ID: ${o.id}). Manage your events and get your API key at ${e.config.platformUrl}/developer
58
- ${i}${c}`},{type:"text",text:JSON.stringify({id:o.id,eventName:t.eventName})}]}}catch(o){return{content:[{type:"text",text:`Failed to create custom event: ${o instanceof Error?o.message:String(o)}`}],isError:!0}}}},$e={name:"list_project_events",description:'List custom events attached to the active project, including events from any project admin. Shows event schemas, fields, and query-readiness status (events must be ingested at least once before they can be used in queries). Use this to find events available for `generate_incentive_query` (with `source: "custom_event"`) when building incentives. To see your own events across all projects, use `list_custom_events` instead. IMPORTANT: Always display the full event list to the user in your response (preferably as a table) so they can see and select an event.',requires:["auth","activeProject"],inputSchema:{},handler:async(t,e)=>{let r=h(e);if(r)return r;let n=E(e);if(n)return n;let o=e.activeProject.id;try{let i=await e.apiRequest(`/projects/${o}/events`),s=i.data??i;return{content:[{type:"text",text:se(s)}]}}catch(i){return{content:[{type:"text",text:`Failed to list project events: ${i instanceof Error?i.message:String(i)}`}],isError:!0}}}},je={name:"list_custom_events",description:"List all custom events you have created across all projects. Shows schemas, fields, and which projects they are attached to. Use this to browse your own events \u2014 for example, when you want to find one to attach to a project with `attach_custom_event`. To see events already on a project (including events from other admins), use `list_project_events` instead. IMPORTANT: Always display the full event list to the user in your response (preferably as a table) so they can see and select an event.",requires:["auth"],inputSchema:{},handler:async(t,e)=>{let r=h(e);if(r)return r;try{let[n,o]=await Promise.all([e.apiRequest("/events"),e.apiRequest("/projects").catch(()=>({}))]),i=n.data??n,s=new Map,c=Array.isArray(o)?o:[];for(let a of c)a.id&&a.name&&s.set(a.id,a.name);return{content:[{type:"text",text:ae(i,s)}]}}catch(n){return{content:[{type:"text",text:`Failed to list custom events: ${n instanceof Error?n.message:String(n)}`}],isError:!0}}}},Ce={name:"attach_custom_event",description:'Attach an existing custom event to the active project so it can be used in incentives. The event must already exist \u2014 use `list_custom_events` to find available events, or `create_custom_event` to create one first. After attaching, the event needs to receive data via ingestion before it becomes query-ready for `generate_incentive_query` (with `source: "custom_event"`).',requires:["auth","activeProject"],inputSchema:{customEventId:k.string().describe("ID of the custom event to attach. Use list_custom_events to find available events, or create_custom_event to create a new one first.")},handler:async(t,e)=>{let r=h(e);if(r)return r;let n=E(e);if(n)return n;let o=e.activeProject.id,i=t.customEventId;try{return await e.apiRequest(`/projects/${o}/events/${i}/attach`,{method:"POST"}),{content:[{type:"text",text:`\u2705 Event attached to project "${e.activeProject.name}" successfully. Use \`list_project_events\` to see all events on this project.`}]}}catch(s){let c=s instanceof Error?s.message:String(s);return c.includes("403")?{content:[{type:"text",text:"\u274C You need to be a project admin to attach events to this project."}],isError:!0}:{content:[{type:"text",text:`\u274C Failed to attach event: ${c}`}],isError:!0}}}};import{z as b}from"zod";var W="customevent_partitioned",At=new Set(["id","ingestionId","eventName","apiKeyId","eventId","userPubkey","projectIds","num_val_1","num_val_2","num_val_3","num_val_4","num_val_5","num_val_6","num_val_7","num_val_8","num_val_9","num_val_10","str_val_1","str_val_2","str_val_3","str_val_4","str_val_5","bool_val_1","bool_val_2","bool_val_3","bool_val_4","bool_val_5","data","receivedAt","createdAt"]);function H(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function _t(t){return t.replace(/\\/g,"\\\\").replace(/'/g,"''")}function Ne(t,e){let r=[],n=new Map;for(let s of e.fields){let c=e.columnMapping[s.fieldName];c&&n.set(s.fieldName,{column:c,type:s.type})}let o=Array.from(n.keys()).sort((s,c)=>c.length-s.length),i=t;for(let s of o)if(new RegExp(`\\b${H(s)}\\b`,"g").test(i)){let{column:a,type:u}=n.get(s);r.push({fieldName:s,column:a,type:u}),i=i.replace(new RegExp(`\\b${H(s)}\\b`,"g"),`"${a}"`)}return{resolved:i,referencedFields:r}}var kt=new Set(["SUM","AVG"]);function qe(t,e){let r=[],n=/\b(SUM|AVG|COUNT|MIN|MAX)\s*\(\s*([^)]+)\s*\)/gi,o;for(;(o=n.exec(t))!==null;){let i=o[1].toUpperCase(),s=o[2].trim();if(s!=="*")for(let c of e.fields)new RegExp(`\\b${H(c.fieldName)}\\b`).test(s)&&kt.has(i)&&c.type!=="number"&&r.push(`Cannot use ${i}() on "${c.fieldName}" \u2014 it is a ${c.type} field, not a NUMBER.`)}return r}function Le(t,e){let r=[],n=[],o=qe(e.valueExpression,t);r.push(...o);let{resolved:i}=Ne(e.valueExpression,t),s=/^\s*[\d.]+\s*$/.test(e.valueExpression),c=/\bCOUNT\s*\(\s*\*\s*\)/i.test(e.valueExpression);!s&&!c&&i===e.valueExpression&&(/\b(num_val_|str_val_|bool_val_)\d+\b/.test(i)||n.push(`Value expression "${e.valueExpression}" did not match any known fields. Available fields: ${t.fields.map(T=>T.fieldName).join(", ")}`));let a=[];if(e.filters)for(let y of e.filters){let T=qe(y,t);r.push(...T);let{resolved:v}=Ne(y,t);a.push(v)}if(r.length>0)return{errors:r};let u=e.groupByPubkey!==!1,l=e.orderBy||"DESC",d=[' "userPubkey" AS address',` ${i} AS value`],m=[`"eventId" = '${_t(t.id)}'`,'"receivedAt" BETWEEN {{startDate}} AND {{endDate}}',...a],p=`SELECT
57
+ \u{1F4A1} Would you like to attach this event to project "${s.name}"? Use \`attach_custom_event\` to attach it.`:"\n\n\u{1F4A1} Would you like to attach this event to a project? Use `list_projects` to see your projects, then `set_active_project` and `attach_custom_event` to attach it.";return{content:[{type:"text",text:`Custom event "${t.eventName}" created successfully (ID: ${i.id}). Manage your events and get your API key at ${e.config.platformUrl}/developer
58
+ ${o}${c}`},{type:"text",text:JSON.stringify({id:i.id,eventName:t.eventName})}]}}catch(i){return{content:[{type:"text",text:`Failed to create custom event: ${i instanceof Error?i.message:String(i)}`}],isError:!0}}}},Ce={name:"list_project_events",description:'List custom events attached to the active project, including events from any project admin. Shows event schemas, fields, and query-readiness status (events must be ingested at least once before they can be used in queries). Use this to find events available for `generate_incentive_query` (with `source: "custom_event"`) when building incentives. To see your own events across all projects, use `list_custom_events` instead. IMPORTANT: Always display the full event list to the user in your response (preferably as a table) so they can see and select an event.',requires:["auth","activeProject"],inputSchema:{},handler:async(t,e)=>{let r=h(e);if(r)return r;let n=w(e);if(n)return n;let i=e.activeProject.id;try{let o=await e.apiRequest(`/projects/${i}/events`),s=o.data??o;return{content:[{type:"text",text:ae(s)}]}}catch(o){return{content:[{type:"text",text:`Failed to list project events: ${o instanceof Error?o.message:String(o)}`}],isError:!0}}}},je={name:"list_custom_events",description:"List all custom events you have created across all projects. Shows schemas, fields, and which projects they are attached to. Use this to browse your own events \u2014 for example, when you want to find one to attach to a project with `attach_custom_event`. To see events already on a project (including events from other admins), use `list_project_events` instead. IMPORTANT: Always display the full event list to the user in your response (preferably as a table) so they can see and select an event.",requires:["auth"],inputSchema:{},handler:async(t,e)=>{let r=h(e);if(r)return r;try{let[n,i]=await Promise.all([e.apiRequest("/events"),e.apiRequest("/projects").catch(()=>({}))]),o=n.data??n,s=new Map,c=Array.isArray(i)?i:[];for(let a of c)a.id&&a.name&&s.set(a.id,a.name);return{content:[{type:"text",text:ce(o,s)}]}}catch(n){return{content:[{type:"text",text:`Failed to list custom events: ${n instanceof Error?n.message:String(n)}`}],isError:!0}}}},Ne={name:"attach_custom_event",description:'Attach an existing custom event to the active project so it can be used in incentives. The event must already exist \u2014 use `list_custom_events` to find available events, or `create_custom_event` to create one first. After attaching, the event needs to receive data via ingestion before it becomes query-ready for `generate_incentive_query` (with `source: "custom_event"`).',requires:["auth","activeProject"],inputSchema:{customEventId:k.string().describe("ID of the custom event to attach. Use list_custom_events to find available events, or create_custom_event to create a new one first.")},handler:async(t,e)=>{let r=h(e);if(r)return r;let n=w(e);if(n)return n;let i=e.activeProject.id,o=t.customEventId;try{return await e.apiRequest(`/projects/${i}/events/${o}/attach`,{method:"POST"}),{content:[{type:"text",text:`\u2705 Event attached to project "${e.activeProject.name}" successfully. Use \`list_project_events\` to see all events on this project.`}]}}catch(s){let c=s instanceof Error?s.message:String(s);return c.includes("403")?{content:[{type:"text",text:"\u274C You need to be a project admin to attach events to this project."}],isError:!0}:{content:[{type:"text",text:`\u274C Failed to attach event: ${c}`}],isError:!0}}}};import{z as E}from"zod";var W="customevent_partitioned",Pt=new Set(["id","ingestionId","eventName","apiKeyId","eventId","userPubkey","projectIds","num_val_1","num_val_2","num_val_3","num_val_4","num_val_5","num_val_6","num_val_7","num_val_8","num_val_9","num_val_10","str_val_1","str_val_2","str_val_3","str_val_4","str_val_5","bool_val_1","bool_val_2","bool_val_3","bool_val_4","bool_val_5","data","receivedAt","createdAt"]);function H(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function xt(t){return t.replace(/\\/g,"\\\\").replace(/'/g,"''")}function qe(t,e){let r=[],n=new Map;for(let s of e.fields){let c=e.columnMapping[s.fieldName];c&&n.set(s.fieldName,{column:c,type:s.type})}let i=Array.from(n.keys()).sort((s,c)=>c.length-s.length),o=t;for(let s of i)if(new RegExp(`\\b${H(s)}\\b`,"g").test(o)){let{column:a,type:u}=n.get(s);r.push({fieldName:s,column:a,type:u}),o=o.replace(new RegExp(`\\b${H(s)}\\b`,"g"),`"${a}"`)}return{resolved:o,referencedFields:r}}var Dt=new Set(["SUM","AVG"]);function Le(t,e){let r=[],n=/\b(SUM|AVG|COUNT|MIN|MAX)\s*\(\s*([^)]+)\s*\)/gi,i;for(;(i=n.exec(t))!==null;){let o=i[1].toUpperCase(),s=i[2].trim();if(s!=="*")for(let c of e.fields)new RegExp(`\\b${H(c.fieldName)}\\b`).test(s)&&Dt.has(o)&&c.type!=="number"&&r.push(`Cannot use ${o}() on "${c.fieldName}" \u2014 it is a ${c.type} field, not a NUMBER.`)}return r}function Oe(t,e){let r=[],n=[],i=Le(e.valueExpression,t);r.push(...i);let{resolved:o}=qe(e.valueExpression,t),s=/^\s*[\d.]+\s*$/.test(e.valueExpression),c=/\bCOUNT\s*\(\s*\*\s*\)/i.test(e.valueExpression);!s&&!c&&o===e.valueExpression&&(/\b(num_val_|str_val_|bool_val_)\d+\b/.test(o)||n.push(`Value expression "${e.valueExpression}" did not match any known fields. Available fields: ${t.fields.map(T=>T.fieldName).join(", ")}`));let a=[];if(e.filters)for(let m of e.filters){let T=Le(m,t);r.push(...T);let{resolved:b}=qe(m,t);a.push(b)}if(r.length>0)return{errors:r};let u=e.groupByPubkey!==!1,l=e.orderBy||"DESC",d=[' "userPubkey" AS address',` ${o} AS value`],f=[`"eventId" = '${xt(t.id)}'`,'"receivedAt" BETWEEN {{startDate}} AND {{endDate}}',...a],p=`SELECT
59
59
  ${d.join(`,
60
60
  `)}
61
61
  FROM ${W}
62
- WHERE ${m.join(`
62
+ WHERE ${f.join(`
63
63
  AND `)}`;return u&&(p+=`
64
64
  GROUP BY "userPubkey"`),p+=`
65
65
  ORDER BY value ${l}`,e.limit!==void 0&&e.limit>0&&(p+=`
66
- LIMIT ${e.limit}`),{sql:p,warnings:n}}var Rt=/\b(INSERT|UPDATE|DELETE|DROP|ALTER|TRUNCATE|CREATE)\b/i;function Oe(t,e){let r=[],n=[];/^\s*SELECT\b/i.test(t)||r.push("Query must be a SELECT statement."),Rt.test(t)&&r.push("Query contains forbidden keywords (INSERT, UPDATE, DELETE, DROP, ALTER, TRUNCATE, CREATE)."),t.includes(";")&&r.push("Query must not contain semicolons (multi-statement prevention)."),/\bUNION\b/i.test(t)&&r.push("Query must not contain UNION (cross-table prevention)."),t.includes(W)||r.push(`Query must reference the "${W}" table.`),(!/\{\{startDate\}\}/i.test(t)||!/\{\{endDate\}\}/i.test(t))&&r.push("Query must include {{startDate}} and {{endDate}} parameters for the evaluation period filter."),/["']?userPubkey["']?/i.test(t)||r.push('Query must select "userPubkey" (aliased as "address") for participant identification.'),/["']?eventId["']?\s*=/i.test(t)||r.push(`Query must include an "eventId" filter for event scoping. Add: AND "eventId" = '${e.id}'`),/\bAS\s+value\b/i.test(t)||n.push('Query should alias the metric column as "value" for reward calculation.');let o=/"(num_val_\d+|str_val_\d+|bool_val_\d+)"/g,i;for(;(i=o.exec(t))!==null;)At.has(i[1])||r.push(`Referenced column "${i[1]}" does not exist in the table schema.`);let s=new Map;for(let a of e.fields){let u=e.columnMapping[a.fieldName];u&&s.set(u,a.type)}let c=/\b(SUM|AVG)\s*\(\s*"?([\w]+)"?\s*\)/gi;for(;(i=c.exec(t))!==null;){let a=i[1].toUpperCase(),u=i[2],l=s.get(u);l&&l!=="number"&&r.push(`Cannot use ${a}() on column "${u}" \u2014 it maps to a ${l} field.`)}return{valid:r.length===0,errors:r,warnings:n}}function It(t,e){if(e.isRawQuery)return`Custom SQL query against "${t}" events. Review the SQL below to understand what it does.`;let r=e.valueExpression??"",n=[],o=r.match(/^SUM\((\w+)\)(?:\s*\*\s*([\d.]+))?$/i),i=r.match(/^COUNT\(\*\)$/i),s=r.match(/^AVG\((\w+)\)$/i);if(i)n.push(`Counts how many "${t}" events each user triggered`);else if(o){let c=o[1],a=o[2];a?n.push(`Calculates ${a}x the total "${c}" per user from "${t}" events`):n.push(`Calculates the total "${c}" per user from "${t}" events`)}else s?n.push(`Calculates the average "${s[1]}" per user from "${t}" events`):r==="1"?n.push(`Gives a flat value of 1 to every user who triggered a "${t}" event`):n.push(`Evaluates "${r}" per user from "${t}" events`);if(e.filters&&e.filters.length>0){let c=e.filters.map(a=>`"${a}"`);n.push(`Only includes events where ${c.join(" and ")}`)}if(e.limit){let c=e.orderBy==="ASC"?"bottom":"top";n.push(`Returns the ${c} ${e.limit} users`)}return n.join(". ")+"."}function xt(t){let e=[t.eventName,t.name,...t.fields.map(r=>r.fieldName)].join(" ").toLowerCase();return/purchase|order|checkout|buy|cart|shop|spend|price|product|merchant|store|payment/.test(e)?"commerce":/swap|trade|stake|lend|borrow|mint|bridge|deposit|withdraw|liquidity|volume|fee|pool|yield|apy|tvl|token_pair/.test(e)?"defi":/game|match|play|score|level|round|battle|kill|win|xp|achievement|quest|player|rank/.test(e)?"gaming":/referral|signup|share|post|comment|like|invite|follow|view|watch|stream|engage/.test(e)?"social":"generic"}function Pt(t,e){let r=t.name.toLowerCase(),n=xt(t),o=e?.valueExpression??"",i=o.match(/^SUM\((\w+)\)/i),s=o.match(/^COUNT\(\*\)$/i),c;s?c=`number of ${r} events`:i?c=`total ${i[1]}`:o==="1"?c="participation":c="calculated value";let a=[`Each user's **${c}** from the query above will be used to calculate their reward.`,"Describe how you'd like rewards distributed \u2014 here are some ideas:",""];if(i){let u=i[1];n==="commerce"?(a.push(`- "Give shoppers cashback \u2014 5% of their total ${u}"`),a.push('- "Leaderboard: top 50 spenders share the reward pool"'),a.push(`- "Loyalty reward proportional to ${u}, capped at 500 tokens per customer"`),a.push('- "Everyone who spent over $10 gets an equal share of the pool"')):n==="defi"?(a.push(`- "Rebate based on trading ${u} \u2014 give back 2% of their volume"`),a.push(`- "Top 100 traders by ${u} share the reward pool"`),a.push(`- "Proportional to ${u} but cap rewards at 10,000 tokens per wallet"`),a.push(`- "Liquidity mining: split the pool based on each user's share of total ${u}"`)):n==="gaming"?(a.push(`- "Top players by ${u} win prizes \u2014 leaderboard style"`),a.push(`- "Reward proportional to ${u}, with diminishing returns for top ranks"`),a.push(`- "Bonus for every 100 ${u} earned"`),a.push(`- "All players with ${u} above 0 split the pool equally"`)):n==="social"?(a.push(`- "Reward based on engagement \u2014 proportional to their ${u}"`),a.push(`- "Top 100 contributors by ${u} share the pool"`),a.push(`- "Everyone who contributed gets a share, weighted by ${u}"`)):(a.push(`- "Give each user 5% of their ${u} back as a reward"`),a.push(`- "Top 50 users by ${u} share the reward pool"`),a.push(`- "Reward proportional to ${u}, but cap at 1000 tokens per user"`),a.push(`- "Split the pool equally among everyone with ${u} above 0"`))}else s?n==="commerce"?(a.push('- "Reward repeat customers \u2014 10 tokens per purchase"'),a.push('- "Top 100 most frequent shoppers share the pool"'),a.push('- "Everyone who made at least 3 purchases gets an equal share"')):n==="defi"?(a.push('- "Reward active traders \u2014 bonus for each transaction"'),a.push('- "Top 100 most active wallets share the pool"'),a.push('- "Everyone who traded at least once gets an equal share"')):n==="gaming"?(a.push('- "Play-to-earn: reward for every match completed"'),a.push('- "Most active players share the prize pool"'),a.push('- "Everyone who played at least 5 matches enters a raffle"')):n==="social"?(a.push('- "Reward for every post or share \u2014 more activity, more reward"'),a.push('- "Top referrers share the pool"'),a.push('- "Everyone who invited at least one friend gets tokens"')):(a.push(`- "Reward based on how many ${r} events they triggered"`),a.push(`- "5 tokens for every ${r} event"`),a.push('- "Top 100 most active users share the pool"')):o==="1"?n==="commerce"?(a.push('- "Everyone who made a purchase gets an equal reward"'),a.push('- "Raffle among all customers \u2014 random winners get prizes"')):n==="defi"?(a.push('- "Airdrop to everyone who interacted with the protocol"'),a.push('- "Equal distribution among all active wallets"')):n==="gaming"?(a.push('- "Participation reward \u2014 everyone who played gets tokens"'),a.push('- "Raffle among all players for bonus prizes"')):n==="social"?(a.push('- "Reward everyone who participated equally"'),a.push('- "Raffle among all engaged users"')):(a.push('- "Split the pool equally among all participants"'),a.push('- "Raffle among all qualifying users"')):(a.push(`- "Reward proportional to their ${c}"`),a.push('- "Split the pool equally among qualifying users"'),a.push('- "Top performers get more, with a cap per user"'));return a.join(`
67
- `)}function Ue(t,e,r,n){let o=["\u{1F4CA} Generated SQL Query Preview","\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",`Event: ${e.name} (${e.eventName})`];n&&(o.push(""),o.push(`**What this does:** ${It(e.eventName,n)}`)),o.push("","```sql",t,"```","","Field Mapping:");for(let i of e.fields){let s=e.columnMapping[i.fieldName];o.push(` ${i.fieldName} (${i.type}) \u2192 ${s}`)}if(r.length>0){o.push(""),o.push("\u26A0\uFE0F Warnings:");for(let i of r)o.push(` - ${i}`)}return o.push(""),o.push("**Next step: How should rewards be calculated?**"),o.push(""),o.push(Pt(e,n)),o.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),o.push("\u26A0\uFE0F Review the query above and confirm to proceed."),o.join(`
68
- `)}var G="mapped_custom_instruction",St=new Set(["id","signature","slot","programId","instructionName","feePayer","idlId","instructionId",...Array.from({length:15},(t,e)=>`num_val_${e+1}`),...Array.from({length:15},(t,e)=>`str_val_${e+1}`),...Array.from({length:10},(t,e)=>`bool_val_${e+1}`),...Array.from({length:30},(t,e)=>`acc_val_${e+1}`),"receivedAt","createdAt","updatedAt","innerIxIndex","ixIndex","stackHeight"]);function K(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Dt(t){return t.replace(/\\/g,"\\\\").replace(/'/g,"''")}function Me(t,e){let r=[],n=new Map;for(let[s,c]of Object.entries(e.accountMapping))n.set(s,{column:c,type:"string"});for(let s of e.instruction){let c=e.fieldMapping[s.fieldName];c&&n.set(s.fieldName,{column:c,type:s.type})}let o=Array.from(n.keys()).sort((s,c)=>c.length-s.length),i=t;for(let s of o)if(new RegExp(`\\b${K(s)}\\b`,"g").test(i)){let{column:a,type:u}=n.get(s);r.push({fieldName:s,column:a,type:u}),i=i.replace(new RegExp(`\\b${K(s)}\\b`,"g"),`"${a}"`)}return{resolved:i,referencedFields:r}}var $t=new Set(["SUM","AVG"]);function Fe(t,e){let r=[],n=/\b(SUM|AVG|COUNT|MIN|MAX)\s*\(\s*([^)]+)\s*\)/gi,o;for(;(o=n.exec(t))!==null;){let i=o[1].toUpperCase(),s=o[2].trim();if(s!=="*")for(let c of e)new RegExp(`\\b${K(c.fieldName)}\\b`).test(s)&&$t.has(i)&&c.type!=="number"&&r.push(`Cannot use ${i}() on "${c.fieldName}" \u2014 it is a ${c.type} field, not a NUMBER.`)}return r}function Qe(t,e){let r=[],n=[],o=Fe(e.valueExpression,t.instruction);r.push(...o);let{resolved:i}=Me(e.valueExpression,t),s=/^\s*[\d.]+\s*$/.test(e.valueExpression),c=/\bCOUNT\s*\(\s*\*\s*\)/i.test(e.valueExpression);!s&&!c&&i===e.valueExpression&&(/\b(num_val_|str_val_|bool_val_|acc_val_)\d+\b/.test(i)||n.push(`Value expression "${e.valueExpression}" did not match any known fields. Available fields: ${t.instruction.map(T=>T.fieldName).join(", ")}`));let a=[];if(e.filters)for(let y of e.filters){let T=Fe(y,t.instruction);r.push(...T);let{resolved:v}=Me(y,t);a.push(v)}if(r.length>0)return{errors:r};let u=e.groupByFeePayer!==!1,l=e.orderBy||"DESC",d=[' "feePayer" AS address',` ${i} AS value`],m=[`"instructionId" = '${Dt(t.id)}'`,'"receivedAt" BETWEEN {{startDate}} AND {{endDate}}',...a],p=`SELECT
66
+ LIMIT ${e.limit}`),{sql:p,warnings:n}}var St=/\b(INSERT|UPDATE|DELETE|DROP|ALTER|TRUNCATE|CREATE)\b/i;function Ue(t,e){let r=[],n=[];/^\s*SELECT\b/i.test(t)||r.push("Query must be a SELECT statement."),St.test(t)&&r.push("Query contains forbidden keywords (INSERT, UPDATE, DELETE, DROP, ALTER, TRUNCATE, CREATE)."),t.includes(";")&&r.push("Query must not contain semicolons (multi-statement prevention)."),/\bUNION\b/i.test(t)&&r.push("Query must not contain UNION (cross-table prevention)."),t.includes(W)||r.push(`Query must reference the "${W}" table.`),(!/\{\{startDate\}\}/i.test(t)||!/\{\{endDate\}\}/i.test(t))&&r.push("Query must include {{startDate}} and {{endDate}} parameters for the evaluation period filter."),/["']?userPubkey["']?/i.test(t)||r.push('Query must select "userPubkey" (aliased as "address") for participant identification.'),/["']?eventId["']?\s*=/i.test(t)||r.push(`Query must include an "eventId" filter for event scoping. Add: AND "eventId" = '${e.id}'`),/\bAS\s+value\b/i.test(t)||n.push('Query should alias the metric column as "value" for reward calculation.');let i=/"(num_val_\d+|str_val_\d+|bool_val_\d+)"/g,o;for(;(o=i.exec(t))!==null;)Pt.has(o[1])||r.push(`Referenced column "${o[1]}" does not exist in the table schema.`);let s=new Map;for(let a of e.fields){let u=e.columnMapping[a.fieldName];u&&s.set(u,a.type)}let c=/\b(SUM|AVG)\s*\(\s*"?([\w]+)"?\s*\)/gi;for(;(o=c.exec(t))!==null;){let a=o[1].toUpperCase(),u=o[2],l=s.get(u);l&&l!=="number"&&r.push(`Cannot use ${a}() on column "${u}" \u2014 it maps to a ${l} field.`)}return{valid:r.length===0,errors:r,warnings:n}}function $t(t,e){if(e.isRawQuery)return`Custom SQL query against "${t}" events. Review the SQL below to understand what it does.`;let r=e.valueExpression??"",n=[],i=r.match(/^SUM\((\w+)\)(?:\s*\*\s*([\d.]+))?$/i),o=r.match(/^COUNT\(\*\)$/i),s=r.match(/^AVG\((\w+)\)$/i);if(o)n.push(`Counts how many "${t}" events each user triggered`);else if(i){let c=i[1],a=i[2];a?n.push(`Calculates ${a}x the total "${c}" per user from "${t}" events`):n.push(`Calculates the total "${c}" per user from "${t}" events`)}else s?n.push(`Calculates the average "${s[1]}" per user from "${t}" events`):r==="1"?n.push(`Gives a flat value of 1 to every user who triggered a "${t}" event`):n.push(`Evaluates "${r}" per user from "${t}" events`);if(e.filters&&e.filters.length>0){let c=e.filters.map(a=>`"${a}"`);n.push(`Only includes events where ${c.join(" and ")}`)}if(e.limit){let c=e.orderBy==="ASC"?"bottom":"top";n.push(`Returns the ${c} ${e.limit} users`)}return n.join(". ")+"."}function Ct(t){let e=[t.eventName,t.name,...t.fields.map(r=>r.fieldName)].join(" ").toLowerCase();return/purchase|order|checkout|buy|cart|shop|spend|price|product|merchant|store|payment/.test(e)?"commerce":/swap|trade|stake|lend|borrow|mint|bridge|deposit|withdraw|liquidity|volume|fee|pool|yield|apy|tvl|token_pair/.test(e)?"defi":/game|match|play|score|level|round|battle|kill|win|xp|achievement|quest|player|rank/.test(e)?"gaming":/referral|signup|share|post|comment|like|invite|follow|view|watch|stream|engage/.test(e)?"social":"generic"}function jt(t,e){let r=t.name.toLowerCase(),n=Ct(t),i=e?.valueExpression??"",o=i.match(/^SUM\((\w+)\)/i),s=i.match(/^COUNT\(\*\)$/i),c;s?c=`number of ${r} events`:o?c=`total ${o[1]}`:i==="1"?c="participation":c="calculated value";let a=[`Each user's **${c}** from the query above will be used to calculate their reward.`,"Describe how you'd like rewards distributed \u2014 here are some ideas:",""];if(o){let u=o[1];n==="commerce"?(a.push(`- "Give shoppers cashback \u2014 5% of their total ${u}"`),a.push('- "Leaderboard: top 50 spenders share the reward pool"'),a.push(`- "Loyalty reward proportional to ${u}, capped at 500 tokens per customer"`),a.push('- "Everyone who spent over $10 gets an equal share of the pool"')):n==="defi"?(a.push(`- "Rebate based on trading ${u} \u2014 give back 2% of their volume"`),a.push(`- "Top 100 traders by ${u} share the reward pool"`),a.push(`- "Proportional to ${u} but cap rewards at 10,000 tokens per wallet"`),a.push(`- "Liquidity mining: split the pool based on each user's share of total ${u}"`)):n==="gaming"?(a.push(`- "Top players by ${u} win prizes \u2014 leaderboard style"`),a.push(`- "Reward proportional to ${u}, with diminishing returns for top ranks"`),a.push(`- "Bonus for every 100 ${u} earned"`),a.push(`- "All players with ${u} above 0 split the pool equally"`)):n==="social"?(a.push(`- "Reward based on engagement \u2014 proportional to their ${u}"`),a.push(`- "Top 100 contributors by ${u} share the pool"`),a.push(`- "Everyone who contributed gets a share, weighted by ${u}"`)):(a.push(`- "Give each user 5% of their ${u} back as a reward"`),a.push(`- "Top 50 users by ${u} share the reward pool"`),a.push(`- "Reward proportional to ${u}, but cap at 1000 tokens per user"`),a.push(`- "Split the pool equally among everyone with ${u} above 0"`))}else s?n==="commerce"?(a.push('- "Reward repeat customers \u2014 10 tokens per purchase"'),a.push('- "Top 100 most frequent shoppers share the pool"'),a.push('- "Everyone who made at least 3 purchases gets an equal share"')):n==="defi"?(a.push('- "Reward active traders \u2014 bonus for each transaction"'),a.push('- "Top 100 most active wallets share the pool"'),a.push('- "Everyone who traded at least once gets an equal share"')):n==="gaming"?(a.push('- "Play-to-earn: reward for every match completed"'),a.push('- "Most active players share the prize pool"'),a.push('- "Everyone who played at least 5 matches enters a raffle"')):n==="social"?(a.push('- "Reward for every post or share \u2014 more activity, more reward"'),a.push('- "Top referrers share the pool"'),a.push('- "Everyone who invited at least one friend gets tokens"')):(a.push(`- "Reward based on how many ${r} events they triggered"`),a.push(`- "5 tokens for every ${r} event"`),a.push('- "Top 100 most active users share the pool"')):i==="1"?n==="commerce"?(a.push('- "Everyone who made a purchase gets an equal reward"'),a.push('- "Raffle among all customers \u2014 random winners get prizes"')):n==="defi"?(a.push('- "Airdrop to everyone who interacted with the protocol"'),a.push('- "Equal distribution among all active wallets"')):n==="gaming"?(a.push('- "Participation reward \u2014 everyone who played gets tokens"'),a.push('- "Raffle among all players for bonus prizes"')):n==="social"?(a.push('- "Reward everyone who participated equally"'),a.push('- "Raffle among all engaged users"')):(a.push('- "Split the pool equally among all participants"'),a.push('- "Raffle among all qualifying users"')):(a.push(`- "Reward proportional to their ${c}"`),a.push('- "Split the pool equally among qualifying users"'),a.push('- "Top performers get more, with a cap per user"'));return a.join(`
67
+ `)}function Me(t,e,r,n){let i=["\u{1F4CA} Generated SQL Query Preview","\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",`Event: ${e.name} (${e.eventName})`];n&&(i.push(""),i.push(`**What this does:** ${$t(e.eventName,n)}`)),i.push("","```sql",t,"```","","Field Mapping:");for(let o of e.fields){let s=e.columnMapping[o.fieldName];i.push(` ${o.fieldName} (${o.type}) \u2192 ${s}`)}if(r.length>0){i.push(""),i.push("\u26A0\uFE0F Warnings:");for(let o of r)i.push(` - ${o}`)}return i.push(""),i.push("**Next step: How should rewards be calculated?**"),i.push(""),i.push(jt(e,n)),i.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),i.push("\u26A0\uFE0F Review the query above and confirm to proceed."),i.join(`
68
+ `)}var V="mapped_custom_instruction",Nt=new Set(["id","signature","slot","programId","instructionName","feePayer","idlId","instructionId",...Array.from({length:15},(t,e)=>`num_val_${e+1}`),...Array.from({length:15},(t,e)=>`str_val_${e+1}`),...Array.from({length:10},(t,e)=>`bool_val_${e+1}`),...Array.from({length:30},(t,e)=>`acc_val_${e+1}`),"receivedAt","createdAt","updatedAt","innerIxIndex","ixIndex","stackHeight"]);function K(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function qt(t){return t.replace(/\\/g,"\\\\").replace(/'/g,"''")}function Fe(t,e){let r=[],n=new Map;for(let[s,c]of Object.entries(e.accountMapping))n.set(s,{column:c,type:"string"});for(let s of e.instruction){let c=e.fieldMapping[s.fieldName];c&&n.set(s.fieldName,{column:c,type:s.type})}let i=Array.from(n.keys()).sort((s,c)=>c.length-s.length),o=t;for(let s of i)if(new RegExp(`\\b${K(s)}\\b`,"g").test(o)){let{column:a,type:u}=n.get(s);r.push({fieldName:s,column:a,type:u}),o=o.replace(new RegExp(`\\b${K(s)}\\b`,"g"),`"${a}"`)}return{resolved:o,referencedFields:r}}var Lt=new Set(["SUM","AVG"]);function Be(t,e){let r=[],n=/\b(SUM|AVG|COUNT|MIN|MAX)\s*\(\s*([^)]+)\s*\)/gi,i;for(;(i=n.exec(t))!==null;){let o=i[1].toUpperCase(),s=i[2].trim();if(s!=="*")for(let c of e)new RegExp(`\\b${K(c.fieldName)}\\b`).test(s)&&Lt.has(o)&&c.type!=="number"&&r.push(`Cannot use ${o}() on "${c.fieldName}" \u2014 it is a ${c.type} field, not a NUMBER.`)}return r}function Qe(t,e){let r=[],n=[],i=Be(e.valueExpression,t.instruction);r.push(...i);let{resolved:o}=Fe(e.valueExpression,t),s=/^\s*[\d.]+\s*$/.test(e.valueExpression),c=/\bCOUNT\s*\(\s*\*\s*\)/i.test(e.valueExpression);!s&&!c&&o===e.valueExpression&&(/\b(num_val_|str_val_|bool_val_|acc_val_)\d+\b/.test(o)||n.push(`Value expression "${e.valueExpression}" did not match any known fields. Available fields: ${t.instruction.map(T=>T.fieldName).join(", ")}`));let a=[];if(e.filters)for(let m of e.filters){let T=Be(m,t.instruction);r.push(...T);let{resolved:b}=Fe(m,t);a.push(b)}if(r.length>0)return{errors:r};let u=e.groupByFeePayer!==!1,l=e.orderBy||"DESC",d=[' "feePayer" AS address',` ${o} AS value`],f=[`"instructionId" = '${qt(t.id)}'`,'"receivedAt" BETWEEN {{startDate}} AND {{endDate}}',...a],p=`SELECT
69
69
  ${d.join(`,
70
70
  `)}
71
- FROM ${G}
72
- WHERE ${m.join(`
71
+ FROM ${V}
72
+ WHERE ${f.join(`
73
73
  AND `)}`;return u&&(p+=`
74
74
  GROUP BY "feePayer"`),p+=`
75
- ORDER BY value ${l}`,{sql:p,warnings:n}}var jt=/\b(INSERT|UPDATE|DELETE|DROP|ALTER|TRUNCATE|CREATE)\b/i;function Be(t,e){let r=[],n=[];/^\s*SELECT\b/i.test(t)||r.push("Query must be a SELECT statement."),jt.test(t)&&r.push("Query contains forbidden keywords (INSERT, UPDATE, DELETE, DROP, ALTER, TRUNCATE, CREATE)."),t.includes(";")&&r.push("Query must not contain semicolons (multi-statement prevention)."),/\bUNION\b/i.test(t)&&r.push("Query must not contain UNION (cross-table prevention)."),t.includes(G)||r.push(`Query must reference the "${G}" table.`),(!/\{\{startDate\}\}/i.test(t)||!/\{\{endDate\}\}/i.test(t))&&r.push("Query must include {{startDate}} and {{endDate}} parameters for the evaluation period filter."),/["']?feePayer["']?/i.test(t)||r.push('Query must select "feePayer" (aliased as "address") for participant identification.'),/["']?instructionId["']?\s*=/i.test(t)||r.push('Query must include an "instructionId" filter for instruction scoping.'),/\bAS\s+value\b/i.test(t)||n.push('Query should alias the metric column as "value" for reward calculation.');let o=/"(num_val_\d+|str_val_\d+|bool_val_\d+|acc_val_\d+)"/g,i;for(;(i=o.exec(t))!==null;)St.has(i[1])||r.push(`Referenced column "${i[1]}" does not exist in the table schema.`);let s=new Map;for(let a of e.instruction){let u=e.fieldMapping[a.fieldName];u&&s.set(u,a.type)}let c=/\b(SUM|AVG)\s*\(\s*"?([\w]+)"?\s*\)/gi;for(;(i=c.exec(t))!==null;){let a=i[1].toUpperCase(),u=i[2],l=s.get(u);l&&l!=="number"&&r.push(`Cannot use ${a}() on column "${u}" \u2014 it maps to a ${l} field.`)}return{valid:r.length===0,errors:r,warnings:n}}function Ct(t,e){if(e.isRawQuery)return`Custom SQL query against "${t}" instructions. Review the SQL below to understand what it does.`;let r=e.valueExpression??"",n=[],o=r.match(/^SUM\((\w+)\)(?:\s*\*\s*([\d.]+))?$/i),i=r.match(/^COUNT\(\*\)$/i),s=r.match(/^AVG\((\w+)\)$/i);if(i)n.push(`Counts how many "${t}" instructions each user executed`);else if(o){let c=o[1],a=o[2];a?n.push(`Calculates ${a}x the total "${c}" per user from "${t}" instructions`):n.push(`Calculates the total "${c}" per user from "${t}" instructions`)}else s?n.push(`Calculates the average "${s[1]}" per user from "${t}" instructions`):r==="1"?n.push(`Gives a flat value of 1 to every user who executed a "${t}" instruction`):n.push(`Evaluates "${r}" per user from "${t}" instructions`);if(e.filters&&e.filters.length>0){let c=e.filters.map(a=>`"${a}"`);n.push(`Only includes instructions where ${c.join(" and ")}`)}return n.join(". ")+"."}function We(t,e,r,n,o){let i=r.displayName?` \u2014 ${r.displayName}`:"",s=["\u{1F4CA} Generated SQL Query Preview","\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",`Program: ${r.name} (\`${r.programAddress}\`)${i}`,`Instruction: ${e.instructionName}`];o&&(s.push(""),s.push(`**What this does:** ${Ct(e.instructionName,o)}`)),s.push("","```sql",t,"```","","Field Mapping:");for(let a of e.instruction){let u=e.fieldMapping[a.fieldName];s.push(` ${a.fieldName} (${a.type}) \u2192 ${u}`)}let c=Object.entries(e.accountMapping);if(c.length>0){s.push(""),s.push("Account Mapping:");for(let[a,u]of c)s.push(` ${a} \u2192 ${u}`)}if(n.length>0){s.push(""),s.push("\u26A0\uFE0F Warnings:");for(let a of n)s.push(` - ${a}`)}return s.push(""),s.push("**Next step: How should rewards be calculated?**"),s.push(""),s.push(Nt(e,o)),s.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),s.push("\u26A0\uFE0F Review the query above and confirm to proceed."),s.join(`
76
- `)}function Nt(t,e){let r=t.instructionName,n=e?.valueExpression??"",o=n.match(/^SUM\((\w+)\)/i),i=n.match(/^COUNT\(\*\)$/i),s;i?s=`number of ${r} instructions`:o?s=`total ${o[1]}`:n==="1"?s="participation":s="calculated value";let c=[`Each user's **${s}** from the query above will be used to calculate their reward.`,"Describe how you'd like rewards distributed \u2014 here are some ideas:",""];if(o){let a=o[1];c.push(`- "Rebate based on ${a} \u2014 give back 2% of their volume"`),c.push(`- "Top 100 users by ${a} share the reward pool"`),c.push(`- "Proportional to ${a} but cap rewards at 10,000 tokens per wallet"`),c.push(`- "Split the pool based on each user's share of total ${a}"`)}else i?(c.push(`- "Reward active users \u2014 bonus for each ${r} executed"`),c.push('- "Top 100 most active wallets share the pool"'),c.push(`- "Everyone who executed at least one ${r} gets an equal share"`)):n==="1"?(c.push('- "Airdrop to everyone who interacted with the program"'),c.push('- "Equal distribution among all active wallets"')):(c.push(`- "Reward proportional to their ${s}"`),c.push('- "Split the pool equally among qualifying users"'),c.push('- "Top performers get more, with a cap per user"'));return c.join(`
77
- `)}var He={raydium_launchlab:{table:_e,identifierColumn:"poolState",identifierParam:"poolState",amountColumn:"amountIn",volumeExpression:'SUM("amountIn")',directionMap:{buy:"buy",sell:"sell"},label:"Raydium LaunchLab"},metaplex_genesis:{table:ke,identifierColumn:"genesisAccount",identifierParam:"genesisAccount",amountColumn:"amountQuoteToken",volumeExpression:'SUM(COALESCE("usdAmount", "uiAmountQuoteToken"))',directionMap:{buy:"deposit",sell:"withdraw"},label:"Metaplex Genesis"}};function qt(t,e){return['SELECT "owner" AS address, "amount" AS value',"FROM (",' SELECT "owner", "amount",',' ROW_NUMBER() OVER (PARTITION BY "owner" ORDER BY "createdAt" DESC) AS rn',` FROM ${q}`,` WHERE "mint" = '${t}'`,` AND "amount" >= ${e}`,' AND "createdAt" BETWEEN {{startDate}} AND {{endDate}}',") sub","WHERE rn = 1","ORDER BY value DESC"].join(`
78
- `)}function Lt(t,e){return['SELECT "owner" AS address, COUNT(DISTINCT DATE("createdAt")) AS value',`FROM ${q}`,`WHERE "mint" = '${t}'`,` AND "amount" >= ${e}`,' AND "createdAt" BETWEEN {{startDate}} AND {{endDate}}','GROUP BY "owner"',"ORDER BY value DESC"].join(`
79
- `)}function Ot(t,e){return['SELECT "owner" AS address, COUNT(*) AS value',`FROM ${q}`,`WHERE "mint" = '${t}'`,` AND "amount" >= ${e}`,' AND "createdAt" BETWEEN {{startDate}} AND {{endDate}}','GROUP BY "owner"',"ORDER BY value DESC"].join(`
80
- `)}var Ut={balance:qt,duration:Lt,count:Ot},Mt={balance:"latest qualifying token balance per wallet",duration:"number of distinct days with qualifying balance",count:"number of qualifying balance records"},Y={swap:["volume","count"],bonding_curve:["volume","count"],hold:["balance","duration","count"]};function g(t){return{isError:!0,content:[{type:"text",text:`\u274C ${t}`}]}}function Ft(t){let e=t.tokenMint;if(!e)return g("tokenMint is required for swap source.");let r=t.direction??"buy",n=t.minUsdAmount,o=t.protocol,i=t.measure;try{I(e,"tokenMint"),o&&I(o,"protocol")}catch(d){return g(d.message)}let s;r==="buy"?s=`"tokenOut" = '${e}'`:r==="sell"?s=`"tokenIn" = '${e}'`:s=`("tokenIn" = '${e}' OR "tokenOut" = '${e}')`;let c=i==="volume"?'SUM("usdAmount") AS value':"COUNT(*) AS value",a=[s,'"receivedAt" BETWEEN {{startDate}} AND {{endDate}}'];n!==void 0&&a.push(`"usdAmount" >= ${n}`),o&&a.push(`"protocol" = '${o}'`);let u=[`SELECT "feePayer" AS address, ${c}`,`FROM ${Ae}`,`WHERE ${a.join(`
75
+ ORDER BY value ${l}`,{sql:p,warnings:n}}var Ot=/\b(INSERT|UPDATE|DELETE|DROP|ALTER|TRUNCATE|CREATE)\b/i;function Ge(t,e){let r=[],n=[];/^\s*SELECT\b/i.test(t)||r.push("Query must be a SELECT statement."),Ot.test(t)&&r.push("Query contains forbidden keywords (INSERT, UPDATE, DELETE, DROP, ALTER, TRUNCATE, CREATE)."),t.includes(";")&&r.push("Query must not contain semicolons (multi-statement prevention)."),/\bUNION\b/i.test(t)&&r.push("Query must not contain UNION (cross-table prevention)."),t.includes(V)||r.push(`Query must reference the "${V}" table.`),(!/\{\{startDate\}\}/i.test(t)||!/\{\{endDate\}\}/i.test(t))&&r.push("Query must include {{startDate}} and {{endDate}} parameters for the evaluation period filter."),/["']?feePayer["']?/i.test(t)||r.push('Query must select "feePayer" (aliased as "address") for participant identification.'),/["']?instructionId["']?\s*=/i.test(t)||r.push('Query must include an "instructionId" filter for instruction scoping.'),/\bAS\s+value\b/i.test(t)||n.push('Query should alias the metric column as "value" for reward calculation.');let i=/"(num_val_\d+|str_val_\d+|bool_val_\d+|acc_val_\d+)"/g,o;for(;(o=i.exec(t))!==null;)Nt.has(o[1])||r.push(`Referenced column "${o[1]}" does not exist in the table schema.`);let s=new Map;for(let a of e.instruction){let u=e.fieldMapping[a.fieldName];u&&s.set(u,a.type)}let c=/\b(SUM|AVG)\s*\(\s*"?([\w]+)"?\s*\)/gi;for(;(o=c.exec(t))!==null;){let a=o[1].toUpperCase(),u=o[2],l=s.get(u);l&&l!=="number"&&r.push(`Cannot use ${a}() on column "${u}" \u2014 it maps to a ${l} field.`)}return{valid:r.length===0,errors:r,warnings:n}}function Ut(t,e){if(e.isRawQuery)return`Custom SQL query against "${t}" instructions. Review the SQL below to understand what it does.`;let r=e.valueExpression??"",n=[],i=r.match(/^SUM\((\w+)\)(?:\s*\*\s*([\d.]+))?$/i),o=r.match(/^COUNT\(\*\)$/i),s=r.match(/^AVG\((\w+)\)$/i);if(o)n.push(`Counts how many "${t}" instructions each user executed`);else if(i){let c=i[1],a=i[2];a?n.push(`Calculates ${a}x the total "${c}" per user from "${t}" instructions`):n.push(`Calculates the total "${c}" per user from "${t}" instructions`)}else s?n.push(`Calculates the average "${s[1]}" per user from "${t}" instructions`):r==="1"?n.push(`Gives a flat value of 1 to every user who executed a "${t}" instruction`):n.push(`Evaluates "${r}" per user from "${t}" instructions`);if(e.filters&&e.filters.length>0){let c=e.filters.map(a=>`"${a}"`);n.push(`Only includes instructions where ${c.join(" and ")}`)}return n.join(". ")+"."}function We(t,e,r,n,i){let o=r.displayName?` \u2014 ${r.displayName}`:"",s=["\u{1F4CA} Generated SQL Query Preview","\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",`Program: ${r.name} (\`${r.programAddress}\`)${o}`,`Instruction: ${e.instructionName}`];i&&(s.push(""),s.push(`**What this does:** ${Ut(e.instructionName,i)}`)),s.push("","```sql",t,"```","","Field Mapping:");for(let a of e.instruction){let u=e.fieldMapping[a.fieldName];s.push(` ${a.fieldName} (${a.type}) \u2192 ${u}`)}let c=Object.entries(e.accountMapping);if(c.length>0){s.push(""),s.push("Account Mapping:");for(let[a,u]of c)s.push(` ${a} \u2192 ${u}`)}if(n.length>0){s.push(""),s.push("\u26A0\uFE0F Warnings:");for(let a of n)s.push(` - ${a}`)}return s.push(""),s.push("**Next step: How should rewards be calculated?**"),s.push(""),s.push(Mt(e,i)),s.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),s.push("\u26A0\uFE0F Review the query above and confirm to proceed."),s.join(`
76
+ `)}function Mt(t,e){let r=t.instructionName,n=e?.valueExpression??"",i=n.match(/^SUM\((\w+)\)/i),o=n.match(/^COUNT\(\*\)$/i),s;o?s=`number of ${r} instructions`:i?s=`total ${i[1]}`:n==="1"?s="participation":s="calculated value";let c=[`Each user's **${s}** from the query above will be used to calculate their reward.`,"Describe how you'd like rewards distributed \u2014 here are some ideas:",""];if(i){let a=i[1];c.push(`- "Rebate based on ${a} \u2014 give back 2% of their volume"`),c.push(`- "Top 100 users by ${a} share the reward pool"`),c.push(`- "Proportional to ${a} but cap rewards at 10,000 tokens per wallet"`),c.push(`- "Split the pool based on each user's share of total ${a}"`)}else o?(c.push(`- "Reward active users \u2014 bonus for each ${r} executed"`),c.push('- "Top 100 most active wallets share the pool"'),c.push(`- "Everyone who executed at least one ${r} gets an equal share"`)):n==="1"?(c.push('- "Airdrop to everyone who interacted with the program"'),c.push('- "Equal distribution among all active wallets"')):(c.push(`- "Reward proportional to their ${s}"`),c.push('- "Split the pool equally among qualifying users"'),c.push('- "Top performers get more, with a cap per user"'));return c.join(`
77
+ `)}var He={raydium_launchlab:{table:ke,identifierColumn:"poolState",identifierParam:"poolState",amountColumn:"amountIn",volumeExpression:'SUM("amountIn")',directionMap:{buy:"buy",sell:"sell"},label:"Raydium LaunchLab"},metaplex_genesis:{table:Ie,identifierColumn:"genesisAccount",identifierParam:"genesisAccount",amountColumn:"amountQuoteToken",volumeExpression:'SUM(COALESCE("usdAmount", "uiAmountQuoteToken"))',directionMap:{buy:"deposit",sell:"withdraw"},label:"Metaplex Genesis"}};function Ft(t,e){return['SELECT "owner" AS address, "amount" AS value',"FROM (",' SELECT "owner", "amount",',' ROW_NUMBER() OVER (PARTITION BY "owner" ORDER BY "createdAt" DESC) AS rn',` FROM ${L}`,` WHERE "mint" = '${t}'`,` AND "amount" >= ${e}`,' AND "createdAt" BETWEEN {{startDate}} AND {{endDate}}',") sub","WHERE rn = 1","ORDER BY value DESC"].join(`
78
+ `)}function Bt(t,e){return['SELECT "owner" AS address, COUNT(DISTINCT DATE("createdAt")) AS value',`FROM ${L}`,`WHERE "mint" = '${t}'`,` AND "amount" >= ${e}`,' AND "createdAt" BETWEEN {{startDate}} AND {{endDate}}','GROUP BY "owner"',"ORDER BY value DESC"].join(`
79
+ `)}function Qt(t,e){return['SELECT "owner" AS address, COUNT(*) AS value',`FROM ${L}`,`WHERE "mint" = '${t}'`,` AND "amount" >= ${e}`,' AND "createdAt" BETWEEN {{startDate}} AND {{endDate}}','GROUP BY "owner"',"ORDER BY value DESC"].join(`
80
+ `)}var Gt={balance:Ft,duration:Bt,count:Qt},Wt={balance:"latest qualifying token balance per wallet",duration:"number of distinct days with qualifying balance",count:"number of qualifying balance records"},Y={swap:["volume","count"],bonding_curve:["volume","count"],hold:["balance","duration","count"]};function y(t){return{isError:!0,content:[{type:"text",text:`\u274C ${t}`}]}}function Ht(t){let e=t.tokenMint;if(!e)return y("tokenMint is required for swap source.");let r=t.direction??"buy",n=t.minUsdAmount,i=t.protocol,o=t.measure;try{R(e,"tokenMint"),i&&R(i,"protocol")}catch(d){return y(d.message)}let s;r==="buy"?s=`"tokenOut" = '${e}'`:r==="sell"?s=`"tokenIn" = '${e}'`:s=`("tokenIn" = '${e}' OR "tokenOut" = '${e}')`;let c=o==="volume"?'SUM("usdAmount") AS value':"COUNT(*) AS value",a=[s,'"receivedAt" BETWEEN {{startDate}} AND {{endDate}}'];n!==void 0&&a.push(`"usdAmount" >= ${n}`),i&&a.push(`"protocol" = '${i}'`);let u=[`SELECT "feePayer" AS address, ${c}`,`FROM ${_e}`,`WHERE ${a.join(`
81
81
  AND `)}`,'GROUP BY "feePayer"',"ORDER BY value DESC"].join(`
82
82
  `),l=`\u2705 Generated swap query for token \`${e}\`
83
83
 
84
84
  **Direction:** ${r}
85
- **Measure:** ${i}${i==="volume"?" (USD)":" (transactions)"}
85
+ **Measure:** ${o}${o==="volume"?" (USD)":" (transactions)"}
86
86
  `+(n!==void 0?`**Min USD per swap:** ${n}
87
- `:"")+(o?`**Protocol:** ${o}
88
- `:"**Protocol:** all");return L({description:l,sql:u,metadata:{source:"swap",tokenMint:e,direction:r,measure:i,minUsdAmount:n,protocol:o}})}function Qt(t){let e=t.launchpad;if(!e||!(e in He))return g("launchpad is required for bonding_curve source. Use 'raydium_launchlab' or 'metaplex_genesis'.");let r=He[e],n=e==="raydium_launchlab"?t.poolState:t.genesisAccount;if(!n)return g(`${r.identifierParam} is required for ${r.label}. This identifies the specific token's bonding curve.`);try{I(n,r.identifierParam)}catch(d){return g(d.message)}let o=t.direction??"buy",i=t.minAmountIn,s=t.measure,c=s==="volume"?`${r.volumeExpression} AS value`:"COUNT(*) AS value",a=[`"${r.identifierColumn}" = '${n}'`,'"receivedAt" BETWEEN {{startDate}} AND {{endDate}}'];if(o!=="both"){let d=r.directionMap[o];a.push(`"direction" = '${d}'`)}i!==void 0&&a.push(`"${r.amountColumn}" >= ${i}`);let u=[`SELECT "feePayer" AS address, ${c}`,`FROM ${r.table}`,`WHERE ${a.join(`
87
+ `:"")+(i?`**Protocol:** ${i}
88
+ `:"**Protocol:** all");return O({description:l,sql:u,metadata:{source:"swap",tokenMint:e,direction:r,measure:o,minUsdAmount:n,protocol:i}})}function Vt(t){let e=t.launchpad;if(!e||!(e in He))return y("launchpad is required for bonding_curve source. Use 'raydium_launchlab' or 'metaplex_genesis'.");let r=He[e],n=e==="raydium_launchlab"?t.poolState:t.genesisAccount;if(!n)return y(`${r.identifierParam} is required for ${r.label}. This identifies the specific token's bonding curve.`);try{R(n,r.identifierParam)}catch(d){return y(d.message)}let i=t.direction??"buy",o=t.minAmountIn,s=t.measure,c=s==="volume"?`${r.volumeExpression} AS value`:"COUNT(*) AS value",a=[`"${r.identifierColumn}" = '${n}'`,'"receivedAt" BETWEEN {{startDate}} AND {{endDate}}'];if(i!=="both"){let d=r.directionMap[i];a.push(`"direction" = '${d}'`)}o!==void 0&&a.push(`"${r.amountColumn}" >= ${o}`);let u=[`SELECT "feePayer" AS address, ${c}`,`FROM ${r.table}`,`WHERE ${a.join(`
89
89
  AND `)}`,'GROUP BY "feePayer"',"ORDER BY value DESC"].join(`
90
90
  `),l=`\u2705 Generated bonding curve query for ${r.label}
91
91
 
92
92
  **${r.identifierParam}:** \`${n}\`
93
- **Direction:** ${o}${o!=="both"?` (mapped to '${r.directionMap[o]??o}')`:""}
94
- **Measure:** ${s}`;return L({description:l,sql:u,metadata:{source:"bonding_curve",launchpad:e,[r.identifierParam]:n,direction:o,measure:s,minAmountIn:i}})}function Bt(t){let e=t.tokenMint;if(!e)return g("tokenMint is required for hold source.");let r=t.minBalance;if(r==null)return g("minBalance is required for hold source.");if(r<=0)return g("minBalance must be greater than 0. A hold incentive without a minimum balance threshold would include all wallets.");let n=t.measure;try{I(e,"tokenMint")}catch(s){return g(s.message)}let o=Ut[n](e,r),i=`\u2705 Generated hold query for token \`${e}\`
93
+ **Direction:** ${i}${i!=="both"?` (mapped to '${r.directionMap[i]??i}')`:""}
94
+ **Measure:** ${s}`;return O({description:l,sql:u,metadata:{source:"bonding_curve",launchpad:e,[r.identifierParam]:n,direction:i,measure:s,minAmountIn:o}})}function Kt(t){let e=t.tokenMint;if(!e)return y("tokenMint is required for hold source.");let r=t.minBalance;if(r==null)return y("minBalance is required for hold source.");if(r<=0)return y("minBalance must be greater than 0. A hold incentive without a minimum balance threshold would include all wallets.");let n=t.measure;try{R(e,"tokenMint")}catch(s){return y(s.message)}let i=Gt[n](e,r),o=`\u2705 Generated hold query for token \`${e}\`
95
95
 
96
96
  **Min balance:** ${r} (raw amount)
97
- **Measure:** ${n} \u2014 ${Mt[n]}`;return L({description:i,sql:o,metadata:{source:"hold",tokenMint:e,minBalance:r,measure:n}})}async function Wt(t,e){let r=t.customEventId;if(!r)return g("customEventId is required for custom_event source.");let n=e.activeProject.id,o;try{let l=await e.apiRequest(`/projects/${n}/events`),m=(l.data??l).find(p=>p.id===r);if(!m)return g("Event not found on this project. Use `list_project_events` to see available events, or `attach_custom_event` to attach one.");if(!m.columnMapping)return g("This event does not have column mappings yet. Events need to be ingested at least once before queries can be generated.");o=m}catch(l){return g(`Failed to fetch project events: ${l instanceof Error?l.message:String(l)}`)}let i=t.rawQuery,s=t.valueExpression;if(i&&s)return g('Provide either "valueExpression" or "rawQuery", not both.');if(!i&&!s)return g(`Provide either "valueExpression" (structured mode) or "rawQuery" (raw SQL mode).
97
+ **Measure:** ${n} \u2014 ${Wt[n]}`;return O({description:o,sql:i,metadata:{source:"hold",tokenMint:e,minBalance:r,measure:n}})}async function Yt(t,e){let r=t.customEventId;if(!r)return y("customEventId is required for custom_event source.");let n=e.activeProject.id,i;try{let l=await e.apiRequest(`/projects/${n}/events`),f=(l.data??l).find(p=>p.id===r);if(!f)return y("Event not found on this project. Use `list_project_events` to see available events, or `attach_custom_event` to attach one.");if(!f.columnMapping)return y("This event does not have column mappings yet. Events need to be ingested at least once before queries can be generated.");i=f}catch(l){return y(`Failed to fetch project events: ${l instanceof Error?l.message:String(l)}`)}let o=t.rawQuery,s=t.valueExpression;if(o&&s)return y('Provide either "valueExpression" or "rawQuery", not both.');if(!o&&!s)return y(`Provide either "valueExpression" (structured mode) or "rawQuery" (raw SQL mode).
98
98
 
99
99
  **Structured mode examples:**
100
100
  - valueExpression: "SUM(amount)" \u2014 total per user
101
101
  - valueExpression: "COUNT(*)" \u2014 event count per user
102
102
  - valueExpression: "1" \u2014 constant value for everyone
103
103
 
104
- **Raw SQL mode:** Provide a full SELECT query with {{startDate}} and {{endDate}} parameters.`);let c,a;if(i){let l=Oe(i,o);if(!l.valid)return g(`SQL query validation failed:
104
+ **Raw SQL mode:** Provide a full SELECT query with {{startDate}} and {{endDate}} parameters.`);let c,a;if(o){let l=Ue(o,i);if(!l.valid)return y(`SQL query validation failed:
105
105
  `+l.errors.map(d=>` - ${d}`).join(`
106
106
  `)+(l.warnings.length>0?`
107
107
 
108
108
  \u26A0\uFE0F Warnings:
109
109
  `+l.warnings.map(d=>` - ${d}`).join(`
110
- `):""));c=i,a=l.warnings}else{let l=Le(o,{valueExpression:s,filters:t.filters,groupByPubkey:t.groupByPubkey,orderBy:t.orderBy,limit:t.limit});if("errors"in l)return g(`Query build failed:
110
+ `):""));c=o,a=l.warnings}else{let l=Oe(i,{valueExpression:s,filters:t.filters,groupByPubkey:t.groupByPubkey,orderBy:t.orderBy,limit:t.limit});if("errors"in l)return y(`Query build failed:
111
111
  `+l.errors.map(d=>` - ${d}`).join(`
112
- `));c=l.sql,a=l.warnings}let u=i?{isRawQuery:!0}:{valueExpression:s,filters:t.filters,limit:t.limit,orderBy:t.orderBy,groupByPubkey:t.groupByPubkey};return t.confirmed?{content:[{type:"text",text:"\u2705 Query confirmed and ready to use.\n\n```sql\n"+c+"\n```\n\nPass this query as the `sqlQuery` parameter when creating a recurring incentive with `create_recurring_incentive`."},{type:"text",text:JSON.stringify({sql:c,eventId:o.id,eventName:o.eventName})}]}:{content:[{type:"text",text:Ue(c,o,a,u)}]}}async function Ht(t,e){let r=t.instructionId;if(!r)return g("instructionId is required for idl_instruction source.");let n=e.activeProject.id,o,i;try{let d=await e.apiRequest(`/projects/${n}/idl`),m=Array.isArray(d)?d:d.data??[],p,y;for(let T of m){if(!T.instructions)continue;let v=T.instructions.find(X=>X.id===r);if(v){p=v,y=T;break}}if(!p||!y)return g("Instruction not found on this project. Use `list_idls` to see available IDLs and their instructions.");o=p,i=y}catch(d){return g(`Failed to fetch project IDLs: ${d instanceof Error?d.message:String(d)}`)}let s=t.rawQuery,c=t.valueExpression;if(s&&c)return g('Provide either "valueExpression" or "rawQuery", not both.');if(!s&&!c)return g(`Provide either "valueExpression" (structured mode) or "rawQuery" (raw SQL mode).
112
+ `));c=l.sql,a=l.warnings}let u=o?{isRawQuery:!0}:{valueExpression:s,filters:t.filters,limit:t.limit,orderBy:t.orderBy,groupByPubkey:t.groupByPubkey};return t.confirmed?{content:[{type:"text",text:"\u2705 Query confirmed and ready to use.\n\n```sql\n"+c+"\n```\n\nPass this query as the `sqlQuery` parameter when creating a recurring incentive with `create_recurring_incentive`."},{type:"text",text:JSON.stringify({sql:c,eventId:i.id,eventName:i.eventName})}]}:{content:[{type:"text",text:Me(c,i,a,u)}]}}async function zt(t,e){let r=t.instructionId;if(!r)return y("instructionId is required for idl_instruction source.");let n=e.activeProject.id,i,o;try{let d=await e.apiRequest(`/projects/${n}/idl`),f=Array.isArray(d)?d:d.data??[],p,m;for(let T of f){if(!T.instructions)continue;let b=T.instructions.find(Z=>Z.id===r);if(b){p=b,m=T;break}}if(!p||!m)return y("Instruction not found on this project. Use `list_idls` to see available IDLs and their instructions.");i=p,o=m}catch(d){return y(`Failed to fetch project IDLs: ${d instanceof Error?d.message:String(d)}`)}let s=t.rawQuery,c=t.valueExpression;if(s&&c)return y('Provide either "valueExpression" or "rawQuery", not both.');if(!s&&!c)return y(`Provide either "valueExpression" (structured mode) or "rawQuery" (raw SQL mode).
113
113
 
114
114
  **Structured mode examples:**
115
- - valueExpression: "SUM(${o.instruction[0]?.fieldName??"field"})" \u2014 total per user
115
+ - valueExpression: "SUM(${i.instruction[0]?.fieldName??"field"})" \u2014 total per user
116
116
  - valueExpression: "COUNT(*)" \u2014 instruction count per user
117
117
  - valueExpression: "1" \u2014 constant value for everyone
118
118
 
119
- **Raw SQL mode:** Provide a full SELECT query with {{startDate}} and {{endDate}} parameters.`);let a,u;if(s){let d=Be(s,o);if(!d.valid)return g(`SQL query validation failed:
120
- `+d.errors.map(m=>` - ${m}`).join(`
119
+ **Raw SQL mode:** Provide a full SELECT query with {{startDate}} and {{endDate}} parameters.`);let a,u;if(s){let d=Ge(s,i);if(!d.valid)return y(`SQL query validation failed:
120
+ `+d.errors.map(f=>` - ${f}`).join(`
121
121
  `)+(d.warnings.length>0?`
122
122
 
123
123
  \u26A0\uFE0F Warnings:
124
- `+d.warnings.map(m=>` - ${m}`).join(`
125
- `):""));a=s,u=d.warnings}else{let d=Qe(o,{valueExpression:c,filters:t.filters,groupByFeePayer:t.groupByFeePayer,orderBy:t.orderBy});if("errors"in d)return g(`Query build failed:
126
- `+d.errors.map(m=>` - ${m}`).join(`
127
- `));a=d.sql,u=d.warnings}let l=s?{isRawQuery:!0}:{valueExpression:c,filters:t.filters,orderBy:t.orderBy,groupByFeePayer:t.groupByFeePayer};return t.confirmed?{content:[{type:"text",text:"\u2705 Query confirmed and ready to use.\n\n```sql\n"+a+"\n```\n\nPass this query as the `sqlQuery` parameter when creating a recurring incentive with `create_recurring_incentive`."},{type:"text",text:JSON.stringify({sql:a,instructionId:o.id,instructionName:o.instructionName,idlId:o.idlId})}]}:{content:[{type:"text",text:We(a,o,i,u,l)}]}}var Ge={name:"generate_incentive_query",description:"Generate a SQL query for any incentive source \u2014 token swaps, bonding curve trades, token holds, custom events, or IDL instructions. Use this when the user wants to create any type of incentive \u2014 leaderboards, rebates, raffles, direct distributions, reward programs, or campaigns. This is the first step before `create_recurring_incentive` \u2014 the query defines what user activity is tracked and measured. Set the `source` parameter to select the data source, then provide source-specific parameters. Token sources (swap, bonding_curve, hold) require no setup \u2014 data is already indexed. Custom event and IDL instruction sources require prior event/IDL setup. Pass the resulting query to `preview_incentive_query` to validate, then to `create_recurring_incentive` as the `sqlQuery` parameter.",requires:["auth","activeProject"],inputSchema:{source:b.enum(["swap","bonding_curve","hold","custom_event","idl_instruction"]).describe("Data source for the query. Token sources (swap, bonding_curve, hold) use indexed on-chain data. custom_event uses off-chain event data. idl_instruction uses on-chain program instruction data."),tokenMint:b.string().optional().describe("SPL token mint address (required for swap, hold)"),direction:b.enum(["buy","sell","both"]).optional().describe("Trade direction (swap, bonding_curve). Default: buy"),measure:b.enum(["volume","count","balance","duration"]).optional().describe("What to measure. swap/bonding_curve: volume or count. hold: balance, duration, or count. Not used for custom_event/idl_instruction."),minUsdAmount:b.number().positive().optional().describe("Minimum USD value per swap (swap only). Uses >= (inclusive). When a user says 'over 100' or '100+', use 100 \u2014 only exclude the stated number if they explicitly say 'more than' or 'above' without inclusion."),protocol:b.string().optional().describe("DEX protocol filter, e.g. Jupiter, Raydium (swap only)"),launchpad:b.enum(["raydium_launchlab","metaplex_genesis"]).optional().describe("Launchpad program (required for bonding_curve)"),poolState:b.string().optional().describe("Pool state address (required for raydium_launchlab)"),genesisAccount:b.string().optional().describe("Genesis account (required for metaplex_genesis)"),minAmountIn:b.number().positive().optional().describe("Minimum purchase size in raw amount (bonding_curve only). Uses >= (inclusive). When a user says 'over N' or 'N+', use N \u2014 only exclude the stated number if they explicitly say 'more than' or 'above' without inclusion."),minBalance:b.number().positive().optional().describe("Minimum token balance, must be > 0 (required for hold). Uses >= (inclusive). When a user says 'over N' or 'N+', use N \u2014 only exclude the stated number if they explicitly say 'more than' or 'above' without inclusion."),customEventId:b.string().optional().describe("Custom event ID (required for custom_event). Use list_project_events to find events."),valueExpression:b.string().optional().describe('Value expression for structured queries (custom_event, idl_instruction). E.g. "SUM(amount)", "COUNT(*)"'),filters:b.array(b.string()).optional().describe("WHERE conditions using field names (custom_event, idl_instruction). Use >= (inclusive) for threshold filters by default. When a user says 'over N' or 'N+', use >= N \u2014 only use > N if they explicitly say 'more than' or 'above' without inclusion."),groupByPubkey:b.boolean().optional().describe("Group by pubkey (custom_event only, default: true)"),orderBy:b.enum(["DESC","ASC"]).optional().describe("Sort order (custom_event, idl_instruction)"),limit:b.number().optional().describe("Max rows (custom_event only)"),rawQuery:b.string().optional().describe("Raw SQL query with {{startDate}}/{{endDate}} params (custom_event, idl_instruction)"),instructionId:b.string().optional().describe("Instruction ID (required for idl_instruction). Use list_idls to find instructions."),groupByFeePayer:b.boolean().optional().describe("Group by fee payer (idl_instruction only, default: true)"),confirmed:b.boolean().optional().describe("Set to true to confirm (custom_event, idl_instruction). Omit to preview.")},handler:async(t,e)=>{let r=h(e);if(r)return r;let n=E(e);if(n)return n;let o=t.source;if(o in Y){let i=t.measure;if(!i)return g(`measure is required for ${o} source.`);if(!Y[o].includes(i))return g(`Invalid measure '${i}' for ${o} source. Allowed: ${Y[o].join(", ")}`)}switch(o){case"swap":return Ft(t);case"bonding_curve":return Qt(t);case"hold":return Bt(t);case"custom_event":return Wt(t,e);case"idl_instruction":return Ht(t,e);default:return g(`Unknown source: ${o}`)}}};import{z as V}from"zod";var Gt=5e3,Ke=6e4,Kt=3,Yt=20;function Vt(){let t=new Date,e=new Date(Date.UTC(t.getUTCFullYear(),t.getUTCMonth(),t.getUTCDate())),r=new Date(e);return r.setUTCDate(r.getUTCDate()-Kt),{startDate:r.toISOString(),endDate:e.toISOString()}}function zt(t){return new Promise(e=>setTimeout(e,t))}function Jt(t,e){let r=t.trim().split(`
128
- `);if(r.length===0)return"_No data_";let o=r[0].split(","),i=r.slice(1,e+1),s=[`| ${o.join(" | ")} |`,`| ${o.map(()=>"---").join(" | ")} |`,...i.map(c=>`| ${c.split(",").join(" | ")} |`)];return r.length-1>e&&s.push(`| _...and ${r.length-1-e} more rows_ |`),s.join(`
129
- `)}async function Xt(t,e,r){let n=Date.now();for(;Date.now()-n<Ke;){let o=await t.apiRequest(`/project/${e}/query/${r}/result`),i=o.data??o;if(i.status==="COMPLETED"||i.status==="FAILED"||i.error)return{status:i.status,preview:i.preview??null,error:i.error??null,totalRows:i.totalRows??null,duration:i.duration??null,timedOut:!1};await zt(Gt)}return{status:"TIMEOUT",preview:null,error:null,totalRows:null,duration:null,timedOut:!0}}var Ye={name:"preview_incentive_query",description:"Execute a SQL query against real indexed data and preview the results. Use this to validate that a query from `generate_incentive_query` produces the expected shape and data before creating an incentive. Offer this as an optional step \u2014 it's most useful for established tokens or events that already have indexed data. For newly tracked tokens, freshly created events, or new IDL instructions, data may not exist yet \u2014 empty results are normal and the user can skip preview. The query runs asynchronously \u2014 submits to a queue and polls for results (up to 60 seconds). IMPORTANT: Before calling this tool, tell the user you are submitting the query and will wait for results. The query may be queued before it starts executing \u2014 let the user know you are waiting and will share results as soon as they are ready. By default, previews the last 3 complete days of data. Only accepts queries generated by `generate_incentive_query` or valid incentive SQL with {{startDate}}/{{endDate}} parameters.",requires:["auth","activeProject"],inputSchema:{sqlQuery:V.string().describe("The SQL query to execute and preview. Typically from generate_incentive_query output."),startDate:V.string().optional().describe("ISO 8601 start date for the query window. Default: midnight 3 days ago."),endDate:V.string().optional().describe("ISO 8601 end date for the query window. Default: midnight today (excludes today's partial data).")},handler:async(t,e)=>{let r=h(e);if(r)return r;let n=E(e);if(n)return n;let o=e.activeProject.id,i=t.sqlQuery,s=Vt(),c=t.startDate??s.startDate,a=t.endDate??s.endDate,u;try{let v=await e.apiRequest(`/project/${o}/query/request`,{method:"POST",body:{type:"RAW",query:i,paramMap:{startDate:"DATE",endDate:"DATE"},paramValues:{startDate:c,endDate:a},targetDatabase:"INDEXER"}});u=(v.data??v).queryId}catch(v){return{isError:!0,content:[{type:"text",text:`\u274C Failed to submit query for preview: ${v instanceof Error?v.message:String(v)}`}]}}let l;try{l=await Xt(e,o,u)}catch(v){return{isError:!0,content:[{type:"text",text:`\u274C Failed to poll query results (queryId: ${u}): ${v instanceof Error?v.message:String(v)}`}]}}if(l.timedOut)return{content:[{type:"text",text:`\u23F3 Query is still processing after ${Ke/1e3}s.
124
+ `+d.warnings.map(f=>` - ${f}`).join(`
125
+ `):""));a=s,u=d.warnings}else{let d=Qe(i,{valueExpression:c,filters:t.filters,groupByFeePayer:t.groupByFeePayer,orderBy:t.orderBy});if("errors"in d)return y(`Query build failed:
126
+ `+d.errors.map(f=>` - ${f}`).join(`
127
+ `));a=d.sql,u=d.warnings}let l=s?{isRawQuery:!0}:{valueExpression:c,filters:t.filters,orderBy:t.orderBy,groupByFeePayer:t.groupByFeePayer};return t.confirmed?{content:[{type:"text",text:"\u2705 Query confirmed and ready to use.\n\n```sql\n"+a+"\n```\n\nPass this query as the `sqlQuery` parameter when creating a recurring incentive with `create_recurring_incentive`."},{type:"text",text:JSON.stringify({sql:a,instructionId:i.id,instructionName:i.instructionName,idlId:i.idlId})}]}:{content:[{type:"text",text:We(a,i,o,u,l)}]}}var Ve={name:"generate_incentive_query",description:"Generate a SQL query for any incentive source \u2014 token swaps, bonding curve trades, token holds, custom events, or IDL instructions. Use this when the user wants to create any type of incentive \u2014 leaderboards, rebates, raffles, direct distributions, reward programs, or campaigns. This is the first step before `create_recurring_incentive` \u2014 the query defines what user activity is tracked and measured. Set the `source` parameter to select the data source, then provide source-specific parameters. Token sources (swap, bonding_curve, hold) require no setup \u2014 data is already indexed. Custom event and IDL instruction sources require prior event/IDL setup. Pass the resulting query to `preview_incentive_query` to validate, then to `create_recurring_incentive` as the `sqlQuery` parameter.",requires:["auth","activeProject"],inputSchema:{source:E.enum(["swap","bonding_curve","hold","custom_event","idl_instruction"]).describe("Data source for the query. Token sources (swap, bonding_curve, hold) use indexed on-chain data. custom_event uses off-chain event data. idl_instruction uses on-chain program instruction data."),tokenMint:E.string().optional().describe("SPL token mint address (required for swap, hold)"),direction:E.enum(["buy","sell","both"]).optional().describe("Trade direction (swap, bonding_curve). Default: buy"),measure:E.enum(["volume","count","balance","duration"]).optional().describe("What to measure. swap/bonding_curve: volume or count. hold: balance, duration, or count. Not used for custom_event/idl_instruction."),minUsdAmount:E.number().positive().optional().describe("Minimum USD value per swap (swap only). Uses >= (inclusive). When a user says 'over 100' or '100+', use 100 \u2014 only exclude the stated number if they explicitly say 'more than' or 'above' without inclusion."),protocol:E.string().optional().describe("DEX protocol filter, e.g. Jupiter, Raydium (swap only)"),launchpad:E.enum(["raydium_launchlab","metaplex_genesis"]).optional().describe("Launchpad program (required for bonding_curve)"),poolState:E.string().optional().describe("Pool state address (required for raydium_launchlab)"),genesisAccount:E.string().optional().describe("Genesis account (required for metaplex_genesis)"),minAmountIn:E.number().positive().optional().describe("Minimum purchase size in raw amount (bonding_curve only). Uses >= (inclusive). When a user says 'over N' or 'N+', use N \u2014 only exclude the stated number if they explicitly say 'more than' or 'above' without inclusion."),minBalance:E.number().positive().optional().describe("Minimum token balance, must be > 0 (required for hold). Uses >= (inclusive). When a user says 'over N' or 'N+', use N \u2014 only exclude the stated number if they explicitly say 'more than' or 'above' without inclusion."),customEventId:E.string().optional().describe("Custom event ID (required for custom_event). Use list_project_events to find events."),valueExpression:E.string().optional().describe('Value expression for structured queries (custom_event, idl_instruction). E.g. "SUM(amount)", "COUNT(*)"'),filters:E.array(E.string()).optional().describe("WHERE conditions using field names (custom_event, idl_instruction). Use >= (inclusive) for threshold filters by default. When a user says 'over N' or 'N+', use >= N \u2014 only use > N if they explicitly say 'more than' or 'above' without inclusion."),groupByPubkey:E.boolean().optional().describe("Group by pubkey (custom_event only, default: true)"),orderBy:E.enum(["DESC","ASC"]).optional().describe("Sort order (custom_event, idl_instruction)"),limit:E.number().optional().describe("Max rows (custom_event only)"),rawQuery:E.string().optional().describe("Raw SQL query with {{startDate}}/{{endDate}} params (custom_event, idl_instruction)"),instructionId:E.string().optional().describe("Instruction ID (required for idl_instruction). Use list_idls to find instructions."),groupByFeePayer:E.boolean().optional().describe("Group by fee payer (idl_instruction only, default: true)"),confirmed:E.boolean().optional().describe("Set to true to confirm (custom_event, idl_instruction). Omit to preview.")},handler:async(t,e)=>{let r=h(e);if(r)return r;let n=w(e);if(n)return n;let i=t.source;if(i in Y){let o=t.measure;if(!o)return y(`measure is required for ${i} source.`);if(!Y[i].includes(o))return y(`Invalid measure '${o}' for ${i} source. Allowed: ${Y[i].join(", ")}`)}switch(i){case"swap":return Ht(t);case"bonding_curve":return Vt(t);case"hold":return Kt(t);case"custom_event":return Yt(t,e);case"idl_instruction":return zt(t,e);default:return y(`Unknown source: ${i}`)}}};import{z}from"zod";var Jt=5e3,Ke=6e4,Xt=3,Zt=20;function er(){let t=new Date,e=new Date(Date.UTC(t.getUTCFullYear(),t.getUTCMonth(),t.getUTCDate())),r=new Date(e);return r.setUTCDate(r.getUTCDate()-Xt),{startDate:r.toISOString(),endDate:e.toISOString()}}function tr(t){return new Promise(e=>setTimeout(e,t))}function rr(t,e){let r=t.trim().split(`
128
+ `);if(r.length===0)return"_No data_";let i=r[0].split(","),o=r.slice(1,e+1),s=[`| ${i.join(" | ")} |`,`| ${i.map(()=>"---").join(" | ")} |`,...o.map(c=>`| ${c.split(",").join(" | ")} |`)];return r.length-1>e&&s.push(`| _...and ${r.length-1-e} more rows_ |`),s.join(`
129
+ `)}async function nr(t,e,r){let n=Date.now();for(;Date.now()-n<Ke;){let i=await t.apiRequest(`/project/${e}/query/${r}/result`),o=i.data??i;if(o.status==="COMPLETED"||o.status==="FAILED"||o.error)return{status:o.status,preview:o.preview??null,error:o.error??null,totalRows:o.totalRows??null,duration:o.duration??null,timedOut:!1};await tr(Jt)}return{status:"TIMEOUT",preview:null,error:null,totalRows:null,duration:null,timedOut:!0}}var Ye={name:"preview_incentive_query",description:"\u26A0\uFE0F CREATION-TIME ONLY \u2014 Execute a SQL query against real indexed data and preview the results BEFORE creating an incentive. Use this to validate that a query from `generate_incentive_query` produces the expected shape and data before creating an incentive. Do NOT use this tool to check results of a live/active incentive \u2014 use `get_incentive_results` instead. Offer this as an optional step \u2014 it's most useful for established tokens or events that already have indexed data. For newly tracked tokens, freshly created events, or new IDL instructions, data may not exist yet \u2014 empty results are normal and the user can skip preview. The query runs asynchronously \u2014 submits to a queue and polls for results (up to 60 seconds). IMPORTANT: Before calling this tool, tell the user you are submitting the query and will wait for results. The query may be queued before it starts executing \u2014 let the user know you are waiting and will share results as soon as they are ready. By default, previews the last 3 complete days of data. Only accepts queries generated by `generate_incentive_query` or valid incentive SQL with {{startDate}}/{{endDate}} parameters.",requires:["auth","activeProject"],inputSchema:{sqlQuery:z.string().describe("The SQL query to execute and preview. Typically from generate_incentive_query output."),startDate:z.string().optional().describe("ISO 8601 start date for the query window. Default: midnight 3 days ago."),endDate:z.string().optional().describe("ISO 8601 end date for the query window. Default: midnight today (excludes today's partial data).")},handler:async(t,e)=>{let r=h(e);if(r)return r;let n=w(e);if(n)return n;let i=e.activeProject.id,o=t.sqlQuery,s=er(),c=t.startDate??s.startDate,a=t.endDate??s.endDate,u;try{let b=await e.apiRequest(`/project/${i}/query/request`,{method:"POST",body:{type:"RAW",query:o,paramMap:{startDate:"DATE",endDate:"DATE"},paramValues:{startDate:c,endDate:a},targetDatabase:"INDEXER"}});u=(b.data??b).queryId}catch(b){return{isError:!0,content:[{type:"text",text:`\u274C Failed to submit query for preview: ${b instanceof Error?b.message:String(b)}`}]}}let l;try{l=await nr(e,i,u)}catch(b){return{isError:!0,content:[{type:"text",text:`\u274C Failed to poll query results (queryId: ${u}): ${b instanceof Error?b.message:String(b)}`}]}}if(l.timedOut)return{content:[{type:"text",text:`\u23F3 Query is still processing after ${Ke/1e3}s.
130
130
 
131
131
  **Query ID:** \`${u}\`
132
132
 
133
- The query may still complete \u2014 you can check results later. The query was submitted with date range ${c} to ${a}.`},{type:"text",text:JSON.stringify({queryId:u,status:"TIMEOUT",startDate:c,endDate:a})}]};if(l.error||l.status==="FAILED")return{isError:!0,content:[{type:"text",text:`\u274C Query preview failed: ${l.error??"Unknown error"}`}]};let d=l.totalRows??0,m=l.duration,p=l.preview,y;return!p||p.trim()===""?y="_No matching data found for the given date range._":y=Jt(p,Yt),{content:[{type:"text",text:`\u2705 Query preview complete
133
+ The query may still complete \u2014 you can check results later. The query was submitted with date range ${c} to ${a}.`},{type:"text",text:JSON.stringify({queryId:u,status:"TIMEOUT",startDate:c,endDate:a})}]};if(l.error||l.status==="FAILED")return{isError:!0,content:[{type:"text",text:`\u274C Query preview failed: ${l.error??"Unknown error"}`}]};let d=l.totalRows??0,f=l.duration,p=l.preview,m;return!p||p.trim()===""?m="_No matching data found for the given date range._":m=rr(p,Zt),{content:[{type:"text",text:`\u2705 Query preview complete
134
134
 
135
- **Results:** ${d} rows`+(m!==null?` (${(m/1e3).toFixed(1)}s)`:"")+`
135
+ **Results:** ${d} rows`+(f!==null?` (${(f/1e3).toFixed(1)}s)`:"")+`
136
136
  **Date range:** ${c} \u2192 ${a}
137
137
 
138
- `+y},{type:"text",text:JSON.stringify({queryId:u,totalRows:d,duration:m,preview:p,startDate:c,endDate:a})}]}}};import{z as U}from"zod";import nr from"fs/promises";var Zt=new Set(["u8","u16","u32","u64","u128","i8","i16","i32","i64","i128","f32","f64"]),er=new Set(["string","publicKey","pubkey","bytes"]);function tr(t){if(Zt.has(t))return"number";if(er.has(t))return"string";if(t==="bool")return"boolean"}function z(t){if(typeof t=="string")return t;if(typeof t=="object"&&t!==null){let e=t.name;if(typeof e=="string")return e}}function $(t){if(typeof t=="string")return t;if(typeof t=="object"&&t!==null){let e=t;if("defined"in e)return z(e.defined)??"unknown";if("option"in e)return`option<${$(e.option)}>`;if("vec"in e)return`vec<${$(e.vec)}>`;if("array"in e){let r=e.array;return`array<${$(r[0])}>`}}return"unknown"}function D(t,e,r,n=new Set){if(typeof e=="string"){let o=tr(e);return o?[{path:t,type:o,optional:!1,idlType:e}]:[{path:t,type:"string",optional:!1,idlType:e}]}if(typeof e=="object"&&e!==null){let o=e;if("option"in o)return D(t,o.option,r,n).map(s=>({...s,optional:!0}));if("vec"in o){let i=$(o.vec);return[{path:t,type:"string",optional:!1,idlType:`vec<${i}>`}]}if("array"in o){let i=o.array,s=$(i[0]);return[{path:t,type:"string",optional:!1,idlType:`array<${s}>`}]}if("defined"in o){let i=z(o.defined);if(!i)return[{path:t,type:"string",optional:!1,idlType:String(o.defined)}];if(n.has(i))return[{path:t,type:"string",optional:!1,idlType:`circular:${i}`}];let s=r[i];if(!s)return[{path:t,type:"string",optional:!1,idlType:i}];if(s.kind==="enum")return[{path:t,type:"string",optional:!1,idlType:i}];if(s.kind==="struct"){let c=new Set(n);c.add(i);let a=[];for(let u=0;u<s.fields.length;u++){let l=s.fields[u];if(typeof l=="object"&&l!==null&&"name"in l&&"type"in l){let d=l,m=D(`${t}.${d.name}`,d.type,r,c);a.push(...m)}else{let d=D(t,l,r,c);a.push(...d)}}return a}}}return[{path:t,type:"string",optional:!1,idlType:String(e)}]}var x=class{canParse(e){if(typeof e!="object"||e===null)return!1;let r=e;return Array.isArray(r.instructions)?typeof r.name=="string"||typeof r.metadata=="object"&&r.metadata!==null&&typeof r.metadata.name=="string":!1}parse(e){if(typeof e!="object"||e===null)throw new Error("IDL must be a JSON object");let r=e;if(!Array.isArray(r.instructions))throw new Error("IDL is missing 'instructions' array - this may not be a valid Anchor IDL");let n;if(typeof r.name=="string")n=r.name;else if(typeof r.metadata=="object"&&r.metadata!==null&&typeof r.metadata.name=="string")n=r.metadata.name;else throw new Error("IDL is missing 'name' field (checked top-level and metadata.name)");let o=this.buildTypeLookup(r.types),i=r.instructions.map(c=>this.parseInstruction(c,o)),s=typeof r.address=="string"?r.address:null;return{name:n,address:s,instructions:i,types:o}}buildTypeLookup(e){let r={};if(!Array.isArray(e))return r;for(let n of e)if(typeof n=="object"&&n!==null&&typeof n.name=="string"&&typeof n.type=="object"){let o=n;r[o.name]=o.type}return r}parseInstruction(e,r){let n=typeof e.name=="string"?e.name:"unknown",i=(Array.isArray(e.args)?e.args:[]).flatMap(c=>{let a=typeof c.name=="string"?c.name:"unknown";return D(a,c.type,r)}),s=this.parseAccounts(e.accounts);return{name:n,fields:i,accounts:s}}parseAccounts(e){return Array.isArray(e)?e.flatMap(r=>{let n=typeof r.name=="string"?r.name:"unknown";return Array.isArray(r.accounts)?this.parseAccounts(r.accounts):"isMut"in r||"isSigner"in r?[{name:n,writable:r.isMut===!0,signer:r.isSigner===!0}]:[{name:n,writable:r.writable===!0,signer:r.signer===!0}]}):[]}};var rr={number:15,string:15,boolean:10,accounts:30};function O(t){let e={number:0,string:0,boolean:0,accounts:t.accounts.length};for(let n of t.fields)n.type in e&&e[n.type]++;let r=[];for(let[n,o]of Object.entries(e)){let i=rr[n];o>i&&r.push({type:n,count:o,max:i})}return{name:t.name,counts:e,withinLimits:r.length===0,exceeded:r}}function J(t,e){let r=e.map(O),n=[`Program: "${t}" (${e.length} instruction${e.length===1?"":"s"})`,""];for(let o of r){let i=o.withinLimits?"\u2705":"\u26A0\uFE0F",s=`${o.counts.number} numbers, ${o.counts.string} strings, ${o.counts.boolean} booleans, ${o.counts.accounts} accounts`,c=`${i} ${o.name} \u2014 ${s}`;if(!o.withinLimits){let a=o.exceeded.map(u=>`${u.type} by ${u.count-u.max}`).join(", ");c+=` (exceeds: ${a})`}n.push(c)}return n.push(""),n.push("Select which instructions to track."),n.join(`
139
- `)}var Ve={name:"parse_idl",description:"Parse a Solana Anchor IDL file and return the program name, on-chain address, and available instructions with their fields and accounts. Use this before `create_idl` to show the user which instructions are available for tracking. Provide the IDL via `filePath` (preferred for large files) or inline `idl` JSON. Instructions are limited to 15 numbers, 15 strings, 10 booleans, and 30 accounts \u2014 instructions exceeding limits are flagged. After the user selects instructions, pass their choices to `create_idl` to upload. IMPORTANT: Always display the full parsed instruction summary to the user so they can review and select which instructions to track before proceeding.",inputSchema:{filePath:U.string().optional().describe("Absolute path to an Anchor IDL JSON file on disk. Preferred over inline `idl` for large files. Provide either `filePath` or `idl`, not both."),idl:U.record(U.string(),U.any()).optional().describe("Full Anchor IDL JSON content. For large IDLs, use `filePath` instead.")},handler:async t=>{let e=t.filePath,r=t.idl;if(e&&r)return{content:[{type:"text",text:"\u274C Provide either `filePath` or `idl`, not both."}],isError:!0};let n;if(e)try{let a=await nr.readFile(e,"utf-8");n=JSON.parse(a)}catch(a){let u=a instanceof Error?a.message:String(a);return{content:[{type:"text",text:`\u274C ${u.includes("ENOENT")?`File not found: ${e}`:`Failed to read IDL file: ${u}`}`}],isError:!0}}else if(r)n=r;else return{content:[{type:"text",text:"\u274C No IDL provided. Supply either `filePath` (path to IDL JSON file) or `idl` (inline JSON)."}],isError:!0};let o=new x;if(!o.canParse(n))return{content:[{type:"text",text:"\u274C Invalid IDL format. The provided JSON does not appear to be a valid Anchor IDL. It must have an `instructions` array and a `name` field (or `metadata.name` for newer formats)."}],isError:!0};let i;try{i=o.parse(n)}catch(a){return{content:[{type:"text",text:`\u274C Failed to parse IDL: ${a instanceof Error?a.message:String(a)}`}],isError:!0}}if(!i.name)return{content:[{type:"text",text:"\u274C The IDL does not contain a program name. Expected a `name` field (Anchor v0) or `metadata.name` (Anchor v0.30+)."}],isError:!0};let s=J(i.name,i.instructions);return{content:[{type:"text",text:`${i.address?`Program address: ${i.address}`:"\u26A0\uFE0F No `address` field found in IDL \u2014 you will need to provide the program address to `create_idl`."}
138
+ `+m},{type:"text",text:JSON.stringify({queryId:u,totalRows:d,duration:f,preview:p,startDate:c,endDate:a})}]}}};import{z as M}from"zod";import cr from"fs/promises";var ir=new Set(["u8","u16","u32","u64","u128","i8","i16","i32","i64","i128","f32","f64"]),or=new Set(["string","publicKey","pubkey","bytes"]);function sr(t){if(ir.has(t))return"number";if(or.has(t))return"string";if(t==="bool")return"boolean"}function J(t){if(typeof t=="string")return t;if(typeof t=="object"&&t!==null){let e=t.name;if(typeof e=="string")return e}}function $(t){if(typeof t=="string")return t;if(typeof t=="object"&&t!==null){let e=t;if("defined"in e)return J(e.defined)??"unknown";if("option"in e)return`option<${$(e.option)}>`;if("vec"in e)return`vec<${$(e.vec)}>`;if("array"in e){let r=e.array;return`array<${$(r[0])}>`}}return"unknown"}function S(t,e,r,n=new Set){if(typeof e=="string"){let i=sr(e);return i?[{path:t,type:i,optional:!1,idlType:e}]:[{path:t,type:"string",optional:!1,idlType:e}]}if(typeof e=="object"&&e!==null){let i=e;if("option"in i)return S(t,i.option,r,n).map(s=>({...s,optional:!0}));if("vec"in i){let o=$(i.vec);return[{path:t,type:"string",optional:!1,idlType:`vec<${o}>`}]}if("array"in i){let o=i.array,s=$(o[0]);return[{path:t,type:"string",optional:!1,idlType:`array<${s}>`}]}if("defined"in i){let o=J(i.defined);if(!o)return[{path:t,type:"string",optional:!1,idlType:String(i.defined)}];if(n.has(o))return[{path:t,type:"string",optional:!1,idlType:`circular:${o}`}];let s=r[o];if(!s)return[{path:t,type:"string",optional:!1,idlType:o}];if(s.kind==="enum")return[{path:t,type:"string",optional:!1,idlType:o}];if(s.kind==="struct"){let c=new Set(n);c.add(o);let a=[];for(let u=0;u<s.fields.length;u++){let l=s.fields[u];if(typeof l=="object"&&l!==null&&"name"in l&&"type"in l){let d=l,f=S(`${t}.${d.name}`,d.type,r,c);a.push(...f)}else{let d=S(t,l,r,c);a.push(...d)}}return a}}}return[{path:t,type:"string",optional:!1,idlType:String(e)}]}var P=class{canParse(e){if(typeof e!="object"||e===null)return!1;let r=e;return Array.isArray(r.instructions)?typeof r.name=="string"||typeof r.metadata=="object"&&r.metadata!==null&&typeof r.metadata.name=="string":!1}parse(e){if(typeof e!="object"||e===null)throw new Error("IDL must be a JSON object");let r=e;if(!Array.isArray(r.instructions))throw new Error("IDL is missing 'instructions' array - this may not be a valid Anchor IDL");let n;if(typeof r.name=="string")n=r.name;else if(typeof r.metadata=="object"&&r.metadata!==null&&typeof r.metadata.name=="string")n=r.metadata.name;else throw new Error("IDL is missing 'name' field (checked top-level and metadata.name)");let i=this.buildTypeLookup(r.types),o=r.instructions.map(c=>this.parseInstruction(c,i)),s=typeof r.address=="string"?r.address:null;return{name:n,address:s,instructions:o,types:i}}buildTypeLookup(e){let r={};if(!Array.isArray(e))return r;for(let n of e)if(typeof n=="object"&&n!==null&&typeof n.name=="string"&&typeof n.type=="object"){let i=n;r[i.name]=i.type}return r}parseInstruction(e,r){let n=typeof e.name=="string"?e.name:"unknown",o=(Array.isArray(e.args)?e.args:[]).flatMap(c=>{let a=typeof c.name=="string"?c.name:"unknown";return S(a,c.type,r)}),s=this.parseAccounts(e.accounts);return{name:n,fields:o,accounts:s}}parseAccounts(e){return Array.isArray(e)?e.flatMap(r=>{let n=typeof r.name=="string"?r.name:"unknown";return Array.isArray(r.accounts)?this.parseAccounts(r.accounts):"isMut"in r||"isSigner"in r?[{name:n,writable:r.isMut===!0,signer:r.isSigner===!0}]:[{name:n,writable:r.writable===!0,signer:r.signer===!0}]}):[]}};var ar={number:15,string:15,boolean:10,accounts:30};function U(t){let e={number:0,string:0,boolean:0,accounts:t.accounts.length};for(let n of t.fields)n.type in e&&e[n.type]++;let r=[];for(let[n,i]of Object.entries(e)){let o=ar[n];i>o&&r.push({type:n,count:i,max:o})}return{name:t.name,counts:e,withinLimits:r.length===0,exceeded:r}}function X(t,e){let r=e.map(U),n=[`Program: "${t}" (${e.length} instruction${e.length===1?"":"s"})`,""];for(let i of r){let o=i.withinLimits?"\u2705":"\u26A0\uFE0F",s=`${i.counts.number} numbers, ${i.counts.string} strings, ${i.counts.boolean} booleans, ${i.counts.accounts} accounts`,c=`${o} ${i.name} \u2014 ${s}`;if(!i.withinLimits){let a=i.exceeded.map(u=>`${u.type} by ${u.count-u.max}`).join(", ");c+=` (exceeds: ${a})`}n.push(c)}return n.push(""),n.push("Select which instructions to track."),n.join(`
139
+ `)}var ze={name:"parse_idl",description:"Parse a Solana Anchor IDL file and return the program name, on-chain address, and available instructions with their fields and accounts. Use this before `create_idl` to show the user which instructions are available for tracking. Provide the IDL via `filePath` (preferred for large files) or inline `idl` JSON. Instructions are limited to 15 numbers, 15 strings, 10 booleans, and 30 accounts \u2014 instructions exceeding limits are flagged. After the user selects instructions, pass their choices to `create_idl` to upload. IMPORTANT: Always display the full parsed instruction summary to the user so they can review and select which instructions to track before proceeding.",inputSchema:{filePath:M.string().optional().describe("Absolute path to an Anchor IDL JSON file on disk. Preferred over inline `idl` for large files. Provide either `filePath` or `idl`, not both."),idl:M.record(M.string(),M.any()).optional().describe("Full Anchor IDL JSON content. For large IDLs, use `filePath` instead.")},handler:async t=>{let e=t.filePath,r=t.idl;if(e&&r)return{content:[{type:"text",text:"\u274C Provide either `filePath` or `idl`, not both."}],isError:!0};let n;if(e)try{let a=await cr.readFile(e,"utf-8");n=JSON.parse(a)}catch(a){let u=a instanceof Error?a.message:String(a);return{content:[{type:"text",text:`\u274C ${u.includes("ENOENT")?`File not found: ${e}`:`Failed to read IDL file: ${u}`}`}],isError:!0}}else if(r)n=r;else return{content:[{type:"text",text:"\u274C No IDL provided. Supply either `filePath` (path to IDL JSON file) or `idl` (inline JSON)."}],isError:!0};let i=new P;if(!i.canParse(n))return{content:[{type:"text",text:"\u274C Invalid IDL format. The provided JSON does not appear to be a valid Anchor IDL. It must have an `instructions` array and a `name` field (or `metadata.name` for newer formats)."}],isError:!0};let o;try{o=i.parse(n)}catch(a){return{content:[{type:"text",text:`\u274C Failed to parse IDL: ${a instanceof Error?a.message:String(a)}`}],isError:!0}}if(!o.name)return{content:[{type:"text",text:"\u274C The IDL does not contain a program name. Expected a `name` field (Anchor v0) or `metadata.name` (Anchor v0.30+)."}],isError:!0};let s=X(o.name,o.instructions);return{content:[{type:"text",text:`${o.address?`Program address: ${o.address}`:"\u26A0\uFE0F No `address` field found in IDL \u2014 you will need to provide the program address to `create_idl`."}
140
140
 
141
- ${s}`},{type:"text",text:JSON.stringify({programName:i.name,programAddress:i.address,instructions:i.instructions.map(a=>({name:a.name,analysis:O(a),fields:a.fields,accounts:a.accounts}))})}]}}};import{z as A}from"zod";import or from"fs/promises";function ir(t){return t.replace(/_/g," ").replace(/\b\w/g,e=>e.toUpperCase())}var ze={name:"create_idl",description:"Upload a Solana Anchor IDL and create instruction tracking in a single call. Provide the IDL via `filePath` (preferred for large files) or inline `idl` JSON. The program address is extracted from the IDL's `address` field automatically \u2014 only provide `programAddress` to override it. IMPORTANT: Always use `parse_idl` first to show the user available instructions and let them select which to track before calling this tool. The IDL JSON is stored exactly as provided \u2014 it is never modified.",requires:["auth","activeProject"],inputSchema:{filePath:A.string().optional().describe("Absolute path to an Anchor IDL JSON file on disk. Preferred over inline `idl` for large files. Provide either `filePath` or `idl`, not both."),idl:A.record(A.string(),A.any()).optional().describe("Full Anchor IDL JSON content. For large IDLs, use `filePath` instead."),programAddress:A.string().optional().describe("On-chain Solana program address. If omitted, extracted from the IDL's `address` field."),displayName:A.string().optional().describe("User-friendly display name for the program"),description:A.string().optional().describe("Description of the program"),selectedInstructions:A.array(A.object({instructionName:A.string().describe("Name of the instruction from the IDL"),fields:A.array(A.object({fieldName:A.string().describe("Dot-path field name (e.g. 'amount' or 'config.feeRate')"),type:A.enum(["string","number","boolean"]).describe("Field data type"),label:A.string().nullish().describe("Display label"),description:A.string().nullish().describe("Field description")})).describe("Selected fields to track for this instruction"),accounts:A.array(A.string()).describe("Selected account names to track for this instruction")})).optional().describe("Instructions to track. Use `parse_idl` first to discover available instructions.")},handler:async(t,e)=>{let r=h(e);if(r)return r;let n=E(e);if(n)return n;let o=t.filePath,i=t.idl;if(o&&i)return{content:[{type:"text",text:"\u274C Provide either `filePath` or `idl`, not both."}],isError:!0};let s;if(o)try{let p=await or.readFile(o,"utf-8");s=JSON.parse(p)}catch(p){let y=p instanceof Error?p.message:String(p);return{content:[{type:"text",text:`\u274C ${y.includes("ENOENT")?`File not found: ${o}`:`Failed to read IDL file: ${y}`}`}],isError:!0}}else if(i)s=i;else return{content:[{type:"text",text:"\u274C No IDL provided. Supply either `filePath` (path to IDL JSON file) or `idl` (inline JSON)."}],isError:!0};let c=new x;if(!c.canParse(s))return{content:[{type:"text",text:"\u274C Invalid IDL format. The provided JSON does not appear to be a valid Anchor IDL. It must have an `instructions` array and a `name` field (or `metadata.name` for newer formats)."}],isError:!0};let a;try{a=c.parse(s)}catch(p){return{content:[{type:"text",text:`\u274C Failed to parse IDL: ${p instanceof Error?p.message:String(p)}`}],isError:!0}}if(!a.name)return{content:[{type:"text",text:"\u274C The IDL does not contain a program name. Expected a `name` field (Anchor v0) or `metadata.name` (Anchor v0.30+)."}],isError:!0};let u=t.programAddress??a.address;if(!u)return{content:[{type:"text",text:"\u274C No program address found. The IDL does not contain an `address` field and no `programAddress` was provided. Please supply the on-chain program address."}],isError:!0};let l=t.selectedInstructions,d=e.activeProject.id,m={programAddress:u,name:a.name,displayName:t.displayName??null,description:t.description??null,idl:s,types:s.types??null};l&&l.length>0&&(m.instructions=l.map(p=>({instructionName:p.instructionName,label:ir(p.instructionName),fields:p.fields,accounts:p.accounts})));try{let p=await e.apiRequest(`/projects/${d}/idl/create`,{method:"POST",body:m}),y=p.data,T=y?.instructionIds??[],v=[`\u2705 IDL uploaded successfully for program "${a.name}" (${u})`];return T.length>0&&v.push(`\u{1F4C1} ${T.length} instruction${T.length===1?"":"s"} configured for tracking.`),{content:[{type:"text",text:v.join(`
142
- `)},{type:"text",text:JSON.stringify({idlId:y?.id??p.id,instructionIds:T})}]}}catch(p){let y=p instanceof Error?p.message:String(p);return y.includes("already exists")?{content:[{type:"text",text:`\u274C An IDL for program address ${u} already exists in this project.`}],isError:!0}:{content:[{type:"text",text:`\u274C Failed to upload IDL: ${y}`}],isError:!0}}}};import{z as _}from"zod";function sr(t){return t.replace(/_/g," ").replace(/\b\w/g,e=>e.toUpperCase())}var Je={name:"create_instruction",description:'Add an instruction to an existing IDL that is already uploaded to the project. Use this when the IDL exists but the specific instruction hasn\'t been set up for tracking yet. Use `parse_idl` first to inspect available instructions and their fields/accounts, then `list_idls` to find the IDL ID. Pipeline: parse_idl \u2192 list_idls \u2192 create_instruction \u2192 generate_incentive_query (source: "idl_instruction") \u2192 create_recurring_incentive.',requires:["auth","activeProject"],inputSchema:{idlId:_.string().describe("ID of the existing IDL to add the instruction to. Use `list_idls` to find this."),instructionName:_.string().describe("Name of the instruction from the IDL (e.g. 'buy_exact_in')."),label:_.string().optional().describe("Display label for the instruction. Defaults to title-cased instruction name."),fields:_.array(_.object({fieldName:_.string().describe("Dot-path field name (e.g. 'amount' or 'config.feeRate')"),type:_.enum(["string","number","boolean"]).describe("Field data type"),label:_.string().nullish().describe("Display label"),description:_.string().nullish().describe("Field description")})).describe("Fields to track for this instruction. Use `parse_idl` to discover available fields."),accounts:_.array(_.string()).describe("Account names to track for this instruction. Use `parse_idl` to discover available accounts.")},handler:async(t,e)=>{let r=h(e);if(r)return r;let n=E(e);if(n)return n;let o=e.activeProject.id,i=t.idlId,s=t.instructionName,c=t.label??sr(s),a=t.fields,u=t.accounts;try{let l=await e.apiRequest(`/projects/${o}/instruction/create`,{method:"POST",body:{idlId:i,instructionName:s,label:c,fields:a,accounts:u}}),m=l.data?.id??l.id;return{content:[{type:"text",text:`\u2705 Instruction "${s}" created successfully on the IDL.
143
- \u{1F4C1} Instruction ID: \`${m}\`
141
+ ${s}`},{type:"text",text:JSON.stringify({programName:o.name,programAddress:o.address,instructions:o.instructions.map(a=>({name:a.name,analysis:U(a),fields:a.fields,accounts:a.accounts}))})}]}}};import{z as A}from"zod";import ur from"fs/promises";function lr(t){return t.replace(/_/g," ").replace(/\b\w/g,e=>e.toUpperCase())}var Je={name:"create_idl",description:"Upload a Solana Anchor IDL and create instruction tracking in a single call. Provide the IDL via `filePath` (preferred for large files) or inline `idl` JSON. The program address is extracted from the IDL's `address` field automatically \u2014 only provide `programAddress` to override it. IMPORTANT: Always use `parse_idl` first to show the user available instructions and let them select which to track before calling this tool. The IDL JSON is stored exactly as provided \u2014 it is never modified.",requires:["auth","activeProject"],inputSchema:{filePath:A.string().optional().describe("Absolute path to an Anchor IDL JSON file on disk. Preferred over inline `idl` for large files. Provide either `filePath` or `idl`, not both."),idl:A.record(A.string(),A.any()).optional().describe("Full Anchor IDL JSON content. For large IDLs, use `filePath` instead."),programAddress:A.string().optional().describe("On-chain Solana program address. If omitted, extracted from the IDL's `address` field."),displayName:A.string().optional().describe("User-friendly display name for the program"),description:A.string().optional().describe("Description of the program"),selectedInstructions:A.array(A.object({instructionName:A.string().describe("Name of the instruction from the IDL"),fields:A.array(A.object({fieldName:A.string().describe("Dot-path field name (e.g. 'amount' or 'config.feeRate')"),type:A.enum(["string","number","boolean"]).describe("Field data type"),label:A.string().nullish().describe("Display label"),description:A.string().nullish().describe("Field description")})).describe("Selected fields to track for this instruction"),accounts:A.array(A.string()).describe("Selected account names to track for this instruction")})).optional().describe("Instructions to track. Use `parse_idl` first to discover available instructions.")},handler:async(t,e)=>{let r=h(e);if(r)return r;let n=w(e);if(n)return n;let i=t.filePath,o=t.idl;if(i&&o)return{content:[{type:"text",text:"\u274C Provide either `filePath` or `idl`, not both."}],isError:!0};let s;if(i)try{let p=await ur.readFile(i,"utf-8");s=JSON.parse(p)}catch(p){let m=p instanceof Error?p.message:String(p);return{content:[{type:"text",text:`\u274C ${m.includes("ENOENT")?`File not found: ${i}`:`Failed to read IDL file: ${m}`}`}],isError:!0}}else if(o)s=o;else return{content:[{type:"text",text:"\u274C No IDL provided. Supply either `filePath` (path to IDL JSON file) or `idl` (inline JSON)."}],isError:!0};let c=new P;if(!c.canParse(s))return{content:[{type:"text",text:"\u274C Invalid IDL format. The provided JSON does not appear to be a valid Anchor IDL. It must have an `instructions` array and a `name` field (or `metadata.name` for newer formats)."}],isError:!0};let a;try{a=c.parse(s)}catch(p){return{content:[{type:"text",text:`\u274C Failed to parse IDL: ${p instanceof Error?p.message:String(p)}`}],isError:!0}}if(!a.name)return{content:[{type:"text",text:"\u274C The IDL does not contain a program name. Expected a `name` field (Anchor v0) or `metadata.name` (Anchor v0.30+)."}],isError:!0};let u=t.programAddress??a.address;if(!u)return{content:[{type:"text",text:"\u274C No program address found. The IDL does not contain an `address` field and no `programAddress` was provided. Please supply the on-chain program address."}],isError:!0};let l=t.selectedInstructions,d=e.activeProject.id,f={programAddress:u,name:a.name,displayName:t.displayName??null,description:t.description??null,idl:s,types:s.types??null};l&&l.length>0&&(f.instructions=l.map(p=>({instructionName:p.instructionName,label:lr(p.instructionName),fields:p.fields,accounts:p.accounts})));try{let p=await e.apiRequest(`/projects/${d}/idl/create`,{method:"POST",body:f}),m=p.data,T=m?.instructionIds??[],b=[`\u2705 IDL uploaded successfully for program "${a.name}" (${u})`];return T.length>0&&b.push(`\u{1F4C1} ${T.length} instruction${T.length===1?"":"s"} configured for tracking.`),{content:[{type:"text",text:b.join(`
142
+ `)},{type:"text",text:JSON.stringify({idlId:m?.id??p.id,instructionIds:T})}]}}catch(p){let m=p instanceof Error?p.message:String(p);return m.includes("already exists")?{content:[{type:"text",text:`\u274C An IDL for program address ${u} already exists in this project.`}],isError:!0}:{content:[{type:"text",text:`\u274C Failed to upload IDL: ${m}`}],isError:!0}}}};import{z as _}from"zod";function dr(t){return t.replace(/_/g," ").replace(/\b\w/g,e=>e.toUpperCase())}var Xe={name:"create_instruction",description:'Add an instruction to an existing IDL that is already uploaded to the project. Use this when the IDL exists but the specific instruction hasn\'t been set up for tracking yet. Use `parse_idl` first to inspect available instructions and their fields/accounts, then `list_idls` to find the IDL ID. Pipeline: parse_idl \u2192 list_idls \u2192 create_instruction \u2192 generate_incentive_query (source: "idl_instruction") \u2192 create_recurring_incentive.',requires:["auth","activeProject"],inputSchema:{idlId:_.string().describe("ID of the existing IDL to add the instruction to. Use `list_idls` to find this."),instructionName:_.string().describe("Name of the instruction from the IDL (e.g. 'buy_exact_in')."),label:_.string().optional().describe("Display label for the instruction. Defaults to title-cased instruction name."),fields:_.array(_.object({fieldName:_.string().describe("Dot-path field name (e.g. 'amount' or 'config.feeRate')"),type:_.enum(["string","number","boolean"]).describe("Field data type"),label:_.string().nullish().describe("Display label"),description:_.string().nullish().describe("Field description")})).describe("Fields to track for this instruction. Use `parse_idl` to discover available fields."),accounts:_.array(_.string()).describe("Account names to track for this instruction. Use `parse_idl` to discover available accounts.")},handler:async(t,e)=>{let r=h(e);if(r)return r;let n=w(e);if(n)return n;let i=e.activeProject.id,o=t.idlId,s=t.instructionName,c=t.label??dr(s),a=t.fields,u=t.accounts;try{let l=await e.apiRequest(`/projects/${i}/instruction/create`,{method:"POST",body:{idlId:o,instructionName:s,label:c,fields:a,accounts:u}}),f=l.data?.id??l.id;return{content:[{type:"text",text:`\u2705 Instruction "${s}" created successfully on the IDL.
143
+ \u{1F4C1} Instruction ID: \`${f}\`
144
144
 
145
- You can now use \`generate_incentive_query\` with \`source: "idl_instruction"\` and this instruction ID to build a query for an incentive.`},{type:"text",text:JSON.stringify({instructionId:m,instructionName:s,idlId:i})}]}}catch(l){let d=l instanceof Error?l.message:String(l);return d.includes("already exists")?{content:[{type:"text",text:`\u274C An instruction named "${s}" already exists on this IDL.`}],isError:!0}:{content:[{type:"text",text:`\u274C Failed to create instruction: ${d}`}],isError:!0}}}};var Xe={name:"list_idls",description:'List all uploaded IDLs (Anchor programs) and their tracked instructions for the active project. Shows program name, address, status, and instruction details including field/account counts. Use this to find instruction IDs for `generate_incentive_query` (with `source: "idl_instruction"`). IMPORTANT: Always display the full list to the user in your response so they can see their uploaded programs.',requires:["auth","activeProject"],inputSchema:{},handler:async(t,e)=>{let r=h(e);if(r)return r;let n=E(e);if(n)return n;let o=e.activeProject.id;try{let i=await e.apiRequest(`/projects/${o}/idl`),s=Array.isArray(i)?i:i.data??[];if(s.length===0)return{content:[{type:"text",text:["\u{1F4CB} **No IDLs uploaded to this project**","","Use `parse_idl` to inspect an Anchor IDL file, then `create_idl` to upload it."].join(`
146
- `)}]};let c=[`\u{1F4CB} **IDLs for "${e.activeProject.name}"** (${s.length})`,""];for(let a of s){let u=a.active?"\u2705 Active":"\u23F8\uFE0F Inactive",l=a.displayName?` \u2014 ${a.displayName}`:"";c.push(`**${a.name}** (\`${a.programAddress}\`)${l} ${u}`);let d=a.instructions??[];if(d.length===0)c.push(" No instructions tracked");else{c.push(` Instructions (${d.length}):`);for(let m of d){let p=m.instruction?.length??0,y=Object.keys(m.accountMapping??{}).length;c.push(` \u2022 ${m.instructionName} \u2014 ${p} field${p!==1?"s":""}, ${y} account${y!==1?"s":""}`)}}c.push("")}return{content:[{type:"text",text:c.join(`
147
- `)},{type:"text",text:JSON.stringify(s)}]}}catch(i){return{content:[{type:"text",text:`\u274C Failed to list IDLs: ${i instanceof Error?i.message:String(i)}`}],isError:!0}}}};var Ze={name:"get_ai_context",requires:["auth","activeProject"],description:"Fetch context about the active project \u2014 documentation, program and token addresses, and domain knowledge provided by the project owner. AUTOMATICALLY call this after setting an active project and before building any incentives, custom events, or queries. Also call when the user asks for insights, analytics interpretation, or general information about their project. Results are cached per project for the session \u2014 subsequent calls are free.",inputSchema:{},handler:async(t,e)=>{let r=h(e);if(r)return r;let n=E(e);if(n)return n;let o=e.activeProject.id;if(e.aiContextCache?.projectId===o)return e.aiContextCache.data;try{let i=await e.apiRequest(`/ai-context/${o}`),s=[],c=i.addresses;(c?.programAddress||c?.tokenAddress)&&(s.push("## Addresses"),c.programAddress&&s.push(`- **Program:** \`${c.programAddress}\``),c.tokenAddress&&s.push(`- **Token:** \`${c.tokenAddress}\``),s.push(""));let a=i.files;if(a&&Object.keys(a).length>0)for(let[l,d]of Object.entries(a))s.push(`## ${l}`),s.push(d),s.push("");let u;return s.length===0?u={content:[{type:"text",text:`\u{1F4CB} No AI context available for project "${e.activeProject.name}".`}]}:u={content:[{type:"text",text:`\u{1F4CB} **AI Context for "${e.activeProject.name}"**
145
+ You can now use \`generate_incentive_query\` with \`source: "idl_instruction"\` and this instruction ID to build a query for an incentive.`},{type:"text",text:JSON.stringify({instructionId:f,instructionName:s,idlId:o})}]}}catch(l){let d=l instanceof Error?l.message:String(l);return d.includes("already exists")?{content:[{type:"text",text:`\u274C An instruction named "${s}" already exists on this IDL.`}],isError:!0}:{content:[{type:"text",text:`\u274C Failed to create instruction: ${d}`}],isError:!0}}}};var Ze={name:"list_idls",description:'List all uploaded IDLs (Anchor programs) and their tracked instructions for the active project. Shows program name, address, status, and instruction details including field/account counts. Use this to find instruction IDs for `generate_incentive_query` (with `source: "idl_instruction"`). IMPORTANT: Always display the full list to the user in your response so they can see their uploaded programs.',requires:["auth","activeProject"],inputSchema:{},handler:async(t,e)=>{let r=h(e);if(r)return r;let n=w(e);if(n)return n;let i=e.activeProject.id;try{let o=await e.apiRequest(`/projects/${i}/idl`),s=Array.isArray(o)?o:o.data??[];if(s.length===0)return{content:[{type:"text",text:["\u{1F4CB} **No IDLs uploaded to this project**","","Use `parse_idl` to inspect an Anchor IDL file, then `create_idl` to upload it."].join(`
146
+ `)}]};let c=[`\u{1F4CB} **IDLs for "${e.activeProject.name}"** (${s.length})`,""];for(let a of s){let u=a.active?"\u2705 Active":"\u23F8\uFE0F Inactive",l=a.displayName?` \u2014 ${a.displayName}`:"";c.push(`**${a.name}** (\`${a.programAddress}\`)${l} ${u}`);let d=a.instructions??[];if(d.length===0)c.push(" No instructions tracked");else{c.push(` Instructions (${d.length}):`);for(let f of d){let p=f.instruction?.length??0,m=Object.keys(f.accountMapping??{}).length;c.push(` \u2022 ${f.instructionName} \u2014 ${p} field${p!==1?"s":""}, ${m} account${m!==1?"s":""}`)}}c.push("")}return{content:[{type:"text",text:c.join(`
147
+ `)},{type:"text",text:JSON.stringify(s)}]}}catch(o){return{content:[{type:"text",text:`\u274C Failed to list IDLs: ${o instanceof Error?o.message:String(o)}`}],isError:!0}}}};var et={name:"get_ai_context",requires:["auth","activeProject"],description:"Fetch context about the active project \u2014 documentation, program and token addresses, and domain knowledge provided by the project owner. AUTOMATICALLY call this after setting an active project and before building any incentives, custom events, or queries. Also call when the user asks for insights, analytics interpretation, or general information about their project. Results are cached per project for the session \u2014 subsequent calls are free.",inputSchema:{},handler:async(t,e)=>{let r=h(e);if(r)return r;let n=w(e);if(n)return n;let i=e.activeProject.id;if(e.aiContextCache?.projectId===i)return e.aiContextCache.data;try{let o=await e.apiRequest(`/ai-context/${i}`),s=[],c=o.addresses;(c?.programAddress||c?.tokenAddress)&&(s.push("## Addresses"),c.programAddress&&s.push(`- **Program:** \`${c.programAddress}\``),c.tokenAddress&&s.push(`- **Token:** \`${c.tokenAddress}\``),s.push(""));let a=o.files;if(a&&Object.keys(a).length>0)for(let[l,d]of Object.entries(a))s.push(`## ${l}`),s.push(d),s.push("");let u;return s.length===0?u={content:[{type:"text",text:`\u{1F4CB} No AI context available for project "${e.activeProject.name}".`}]}:u={content:[{type:"text",text:`\u{1F4CB} **AI Context for "${e.activeProject.name}"**
148
148
 
149
149
  ${s.join(`
150
- `)}`}]},e.aiContextCache={projectId:o,data:u},u}catch(i){return{content:[{type:"text",text:`Failed to fetch AI context: ${i instanceof Error?i.message:String(i)}`}],isError:!0}}}};var M={name:"create-incentive",config:{title:"Create Incentive",description:"Guided workflow to create any type of recurring incentive \u2014 token-based (swap, bonding curve, hold), custom event, or custom instruction."},handler:async()=>({messages:[{role:"user",content:{type:"text",text:"I want to create a new incentive program."}},{role:"assistant",content:{type:"text",text:`Great! I'll guide you through creating an incentive step by step. I'll ask one question at a time \u2014 you don't need to know all the details upfront.
150
+ `)}`}]},e.aiContextCache={projectId:i,data:u},u}catch(o){return{content:[{type:"text",text:`Failed to fetch AI context: ${o instanceof Error?o.message:String(o)}`}],isError:!0}}}};import{z as C}from"zod";var tt={name:"get_incentive_results",requires:["auth","activeProject"],description:`Fetch results, rankings, or recipient payouts for a recurring incentive. Use this when the user asks about current standings, past results, winners, payouts, or wants to download data for analysis. Do NOT use \`preview_incentive_query\` for this \u2014 that tool is for testing queries during incentive CREATION only.
151
+
152
+ Three modes:
153
+ - "preview" (default): Evaluation scores \u2014 who is leading and their metric values. Returns the latest epoch by default, or a specific epoch if epochConfigId is provided.
154
+ - "recipients": Allocation and payout data \u2014 who was allocated rewards, how much, and distribution status (DISTRIBUTED/PENDING/FAILED). Requires epochConfigId, or auto-selects the latest CLAIMING/COMPLETED epoch.
155
+ - "download": Full CSV download URL for deep analysis with all columns. Returns a signed S3 URL (expires in 5 minutes).
156
+
157
+ To find epoch IDs and statuses, call \`get_recurring_incentive\` first \u2014 it shows all epochs with their IDs, statuses, and time windows. Use \`list_recurring_incentives\` to find the incentive ID.`,inputSchema:{recurringOfferId:C.string().describe("The recurring incentive ID. Use `list_recurring_incentives` to find it."),mode:C.enum(["preview","recipients","download"]).optional().describe('"preview" (default): evaluation scores/rankings. "recipients": allocations, payouts, and distribution status per wallet. "download": signed URL for full CSV download.'),epochConfigId:C.string().optional().describe("Target a specific epoch. If omitted: preview uses latest results, recipients auto-selects the latest CLAIMING or COMPLETED epoch, download uses the latest epoch's query. Use `get_recurring_incentive` to see available epochs and their IDs."),limit:C.number().optional().describe("Max rows to return (default: 50). Applies to preview and recipients modes."),offset:C.number().optional().describe("Pagination offset (default: 0). Applies to preview and recipients modes.")},handler:async(t,e)=>{let r=h(e);if(r)return r;let n=w(e);if(n)return n;let i=e.activeProject.id,o=t.recurringOfferId,s=t.mode??"preview",c=t.epochConfigId,a=t.limit??50,u=t.offset??0;try{return s==="download"?await mr(e,i,o,c):s==="recipients"?await fr(e,i,o,c,a,u):await pr(e,i,o,c,a,u)}catch(l){return{content:[{type:"text",text:`\u274C Failed to get incentive results: ${l instanceof Error?l.message:String(l)}`}],isError:!0}}}};async function pr(t,e,r,n,i,o){let s=n?`/project/${e}/recurring-offer/${r}/epoch/${n}/eval-results?limit=${i}&offset=${o}`:`/project/${e}/recurring-offer/${r}/latest-eval-results?limit=${i}&offset=${o}`,c=await t.apiRequest(s),a=c.data??c,u=a.results??[];if(u.length===0)return{content:[{type:"text",text:n?`\u{1F4CA} No evaluation results found for epoch \`${n}\`. The epoch may not have been evaluated yet.`:"\u{1F4CA} No evaluation results available yet. The incentive may not have completed its first evaluation period."}]};let d=[n?`\u{1F4CA} **Evaluation Results** (epoch \`${n}\`)`:"\u{1F4CA} **Latest Evaluation Results**","",`Showing ${u.length} of ${a.total} results${a.lastUpdated?` (last updated: ${a.lastUpdated})`:""}`,"","| Rank | Wallet | Metric Value |","|------|--------|-------------|"];return u.forEach((f,p)=>{d.push(`| ${o+p+1} | \`${f.walletAddress}\` | ${f.metricValue} |`)}),a.total>o+u.length&&(d.push(""),d.push(`_${a.total-o-u.length} more results available. Use \`offset: ${o+i}\` to see the next page, or \`mode: "download"\` to get the full CSV._`)),{content:[{type:"text",text:d.join(`
158
+ `)}]}}async function fr(t,e,r,n,i,o){let s=n;if(!s){let p=[...(await t.apiRequest(`/project/${e}/recurring-offer/${r}`)).configs??[]].reverse().find(m=>m.status==="CLAIMING"||m.status==="COMPLETED");if(!p)return{content:[{type:"text",text:"\u{1F4CB} No epochs with recipients found. Recipients are only available for epochs in CLAIMING or COMPLETED status. Use `get_recurring_incentive` to check epoch statuses."}]};s=p.id}let c=await t.apiRequest(`/project/${e}/recurring-offer/${r}/epoch/${s}/recipients?limit=${i}&offset=${o}`),a=c.data??c,u=a.recipients??[];if(u.length===0)return{content:[{type:"text",text:`\u{1F4CB} No recipients found for epoch \`${s}\`. Allocations may not have been created yet.`}]};let l=[`\u{1F4CB} **Recipients** (epoch \`${s}\`)`,"",`Showing ${u.length} of ${a.total} recipients`,"","| Rank | Wallet | Allocated | Distributed | Status |","|------|--------|-----------|-------------|--------|"];return u.forEach((d,f)=>{let p=d.distributedAmount!==null?String(d.distributedAmount):"\u2014",m=d.status==="DISTRIBUTED"?"\u2705":d.status==="FAILED"?"\u274C":"\u23F3";l.push(`| ${o+f+1} | \`${d.walletAddress}\` | ${d.allocatedAmount} | ${p} | ${m} ${d.status} |`)}),a.total>o+u.length&&(l.push(""),l.push(`_${a.total-o-u.length} more recipients. Use \`offset: ${o+i}\` to see the next page._`)),{content:[{type:"text",text:l.join(`
159
+ `)}]}}async function mr(t,e,r,n){let o=(await t.apiRequest(`/project/${e}/recurring-offer/${r}`)).configs??[];if(o.length===0)return{content:[{type:"text",text:"\u274C No epoch configs found for this incentive. It may not have been evaluated yet."}],isError:!0};let s=n?o.find(u=>u.id===n):o[o.length-1];if(!s)return{content:[{type:"text",text:`\u274C Epoch config \`${n}\` not found. Use \`get_recurring_incentive\` to see available epochs.`}],isError:!0};if(!s.sqlQueryId)return{content:[{type:"text",text:"\u274C No query ID found on the epoch config. The incentive may not have a query attached."}],isError:!0};let c=await t.apiRequest(`/project/${e}/query/${s.sqlQueryId}/download`),a=c.data?.url??c.url;return a?{content:[{type:"text",text:["\u{1F4E5} **CSV Download Ready**","",`**Epoch:** ${s.epochNumber} (\`${s.id??"latest"}\`)`,`**URL:** ${a}`,"","This is a signed URL that expires in 5 minutes. Download it to get the full results CSV with all columns for analysis."].join(`
160
+ `)}]}:{content:[{type:"text",text:"\u274C Download URL not available. The query results may not be ready yet."}],isError:!0}}import{z as rt}from"zod";function hr(t){let e=[];if(e.push(`### ${t.metadata?.title||t.title||"Untitled Offer"}`),e.push(`**ID:** ${t.id}`),e.push(`**Status:** ${t.status}`),e.push(`**Period:** ${t.startTime} - ${t.endTime}`),t.totalRewards&&t.totalRewards.length>0){let n=t.totalRewards.map(i=>`${i.amount} ${i.token}`).join(", ");e.push(`**Rewards:** ${n}`)}else e.push("**Rewards:** None configured");let r=t.metadata?.description||t.description;return r&&e.push(`**Description:** ${r}`),e.join(`
161
+ `)}var nt={name:"get_offer_details",requires:["auth","activeProject"],description:"Fetch active offer summaries for the current project \u2014 reward pools, timeframes, and metadata. This is the builder's view of what end users can see and claim. Use this to review published offers, check reward totals, or verify offer status.",inputSchema:{offerStatus:rt.enum(["ACTIVE","CLOSED"]).optional().describe('Filter by offer status (default: "ACTIVE")'),offerId:rt.string().optional().describe("Filter to a specific offer by ID")},handler:async(t,e)=>{let r=h(e);if(r)return r;let n=w(e);if(n)return n;let i=e.activeProject.id,o=t.offerStatus??"ACTIVE",s=`/claim/details/byOffer?projectId=${i}&offerStatus=${o}`;t.offerId&&(s+=`&offerId=${t.offerId}`);try{let c=await e.apiRequest(s),a=c.data??c,u=Array.isArray(a)?a:[];if(u.length===0)return{content:[{type:"text",text:`\u{1F4CB} No offers found with status "${o}" for this project.`}]};let l=`\u{1F4CB} **Offer Details** (${u.length} offer${u.length!==1?"s":""} found)
162
+ `,d=u.map(hr).join(`
163
+
164
+ ---
165
+
166
+ `);return{content:[{type:"text",text:l+`
167
+ `+d}]}}catch(c){return{content:[{type:"text",text:`\u274C Failed to fetch offer details: ${c instanceof Error?c.message:String(c)}`}],isError:!0}}}};var F={name:"create-incentive",config:{title:"Create Incentive",description:"Guided workflow to create any type of recurring incentive \u2014 token-based (swap, bonding curve, hold), custom event, or custom instruction."},handler:async()=>({messages:[{role:"user",content:{type:"text",text:"I want to create a new incentive program."}},{role:"assistant",content:{type:"text",text:`Great! I'll guide you through creating an incentive step by step. I'll ask one question at a time \u2014 you don't need to know all the details upfront.
151
168
 
152
169
  **Before we start:** I need to check a few things. Let me verify your authentication, make sure you have a project selected, and fetch your project's context so I can make relevant suggestions.
153
170
 
@@ -270,7 +287,7 @@ Call \`create_recurring_incentive\` with \`confirmed: false\` first to preview.
270
287
  - **Auth expired mid-flow:** Call \`check_auth_status\` then \`authenticate\` again before retrying the failed step.
271
288
  - **Query returns no data:** Verify the token mint address, event ID, or instruction ID is correct. For custom events, check that data has been ingested at least once via \`list_project_events\`.
272
289
  - **Preview looks wrong:** Go back to Step 2 and adjust the query parameters (filters, measure, direction).
273
- - **Incentive creation fails:** Check the error message \u2014 common causes are missing required fields for the chosen type (e.g., \`rebatePercentage\` for rebate, \`raffleBuckets\` for raffle).`}}]})};var ar='# Torque MCP Workflows\n\n## Creating an Incentive (Unified Flow)\n\nUse the `create-incentive` prompt to start the guided workflow. The flow:\n\n### Path A: Token Incentive (zero setup)\nFor incentivizing on-chain token activity. No custom event or IDL setup required.\n\n1. **Select project** \u2192 `list_projects` + `set_active_project` (if no projects exist, create one with `create_project` \u2014 ask if they are a token team, protocol, or both, and collect the appropriate address)\n2. **Choose token action:**\n - **Swap** \u2192 `generate_incentive_query` with `source: "swap"` (token mint, direction, min USD, DEX protocol)\n - **Bonding Curve Buy** \u2192 `generate_incentive_query` with `source: "bonding_curve"` (launchpad, pool/genesis account)\n - **Hold** \u2192 `generate_incentive_query` with `source: "hold"` (token mint, min balance > 0)\n3. **(Optional) Preview** \u2192 `preview_incentive_query` to validate results with real data\n4. **Configure recipe** \u2192 `create_recurring_incentive` (primitive, emission, rewards)\n5. **Preview & confirm**\n\n### Path B: Custom Event Incentive\nFor off-chain activity (social, content, etc.).\n\n1. **Select project** \u2192 `list_projects` + `set_active_project`\n2. **Set up event** \u2192 `list_project_events` (use existing or `create_custom_event` + `attach_custom_event`)\n3. **Generate query** \u2192 `generate_incentive_query` with `source: "custom_event"`\n4. **(Optional) Preview** \u2192 `preview_incentive_query` to validate results with real data\n5. **Configure recipe** \u2192 `create_recurring_incentive`\n6. **Preview & confirm**\n\n### Path C: Custom Instruction Incentive\nFor on-chain program interactions not covered by token actions.\n\n1. **Select project** \u2192 `list_projects` + `set_active_project`\n2. **Set up instruction** \u2192 `list_idls` (use existing or `parse_idl` + `create_idl` / `create_instruction`)\n3. **Generate query** \u2192 `generate_incentive_query` with `source: "idl_instruction"`\n4. **(Optional) Preview** \u2192 `preview_incentive_query` to validate results with real data\n5. **Configure recipe** \u2192 `create_recurring_incentive`\n6. **Preview & confirm**\n\n## Reviewing Existing Incentives\n\n1. `list_recurring_incentives` \u2192 see all incentives for active project\n2. `get_recurring_incentive` \u2192 full details of a specific incentive\n3. `get_recurring_incentive_analytics` \u2192 performance metrics\n\n## Terminology\n\n- **Primitive** \u2014 Incentive mechanic: Leaderboard, Rebate, Raffle, or Direct (fixed wallet allocations)\n- **Action** \u2014 What users do to qualify: Swap, Bonding Curve Buy, Hold, Custom Event, Custom Instruction\n- **Measure** \u2014 How actions are scored: Volume, Count, Duration, Balance\n- **Recipe** \u2014 Primitive + Action + Measure = a complete incentive configuration\n- **Evaluation period** \u2014 Time window for measuring activity (daily, weekly, monthly)\n\n## Tips\n\n- Token incentives are the fastest path \u2014 data is already indexed, no setup needed\n- For custom events, the event must be ingested at least once before query generation works (columnMapping must be non-null)\n- Use `get_ai_context` to fetch project-specific documentation and domain knowledge\n- Always preview before confirming: `create_recurring_incentive` with `confirmed: false`\n- **For leaderboards:** Always ask how the user wants rewards distributed (fixed prizes per rank, tiered ranges, equal splits, etc.) and build a `customFormula` \u2014 the default `N` pays raw metric values which is rarely intended\n- **For raffles:** Default weighting is WEIGHTED_BY_METRIC (query metric = ticket count). Use `raffleWeighting: "EQUAL_CHANCES"` if the user wants equal odds for all participants.\n';function et(t){t.registerResource("workflows-guide","torque://workflows",{description:"Guide to common Torque workflows including custom event incentives, IDL instruction incentives, and simple incentives. Read this to understand the tool pipeline and available guided prompts.",mimeType:"text/markdown"},async()=>({contents:[{uri:"torque://workflows",mimeType:"text/markdown",text:ar}]}))}var cr=`# Torque MCP \u2014 Capabilities
290
+ - **Incentive creation fails:** Check the error message \u2014 common causes are missing required fields for the chosen type (e.g., \`rebatePercentage\` for rebate, \`raffleBuckets\` for raffle).`}}]})};var gr='# Torque MCP Workflows\n\n## Creating an Incentive (Unified Flow)\n\nUse the `create-incentive` prompt to start the guided workflow. The flow:\n\n### Path A: Token Incentive (zero setup)\nFor incentivizing on-chain token activity. No custom event or IDL setup required.\n\n1. **Select project** \u2192 `list_projects` + `set_active_project` (if no projects exist, create one with `create_project` \u2014 ask if they are a token team, protocol, or both, and collect the appropriate address)\n2. **Choose token action:**\n - **Swap** \u2192 `generate_incentive_query` with `source: "swap"` (token mint, direction, min USD, DEX protocol)\n - **Bonding Curve Buy** \u2192 `generate_incentive_query` with `source: "bonding_curve"` (launchpad, pool/genesis account)\n - **Hold** \u2192 `generate_incentive_query` with `source: "hold"` (token mint, min balance > 0)\n3. **(Optional) Preview** \u2192 `preview_incentive_query` to validate results with real data\n4. **Configure recipe** \u2192 `create_recurring_incentive` (primitive, emission, rewards)\n5. **Preview & confirm**\n\n### Path B: Custom Event Incentive\nFor off-chain activity (social, content, etc.).\n\n1. **Select project** \u2192 `list_projects` + `set_active_project`\n2. **Set up event** \u2192 `list_project_events` (use existing or `create_custom_event` + `attach_custom_event`)\n3. **Generate query** \u2192 `generate_incentive_query` with `source: "custom_event"`\n4. **(Optional) Preview** \u2192 `preview_incentive_query` to validate results with real data\n5. **Configure recipe** \u2192 `create_recurring_incentive`\n6. **Preview & confirm**\n\n### Path C: Custom Instruction Incentive\nFor on-chain program interactions not covered by token actions.\n\n1. **Select project** \u2192 `list_projects` + `set_active_project`\n2. **Set up instruction** \u2192 `list_idls` (use existing or `parse_idl` + `create_idl` / `create_instruction`)\n3. **Generate query** \u2192 `generate_incentive_query` with `source: "idl_instruction"`\n4. **(Optional) Preview** \u2192 `preview_incentive_query` to validate results with real data\n5. **Configure recipe** \u2192 `create_recurring_incentive`\n6. **Preview & confirm**\n\n## Reviewing Existing Incentives\n\n1. `list_recurring_incentives` \u2192 see all incentives for active project\n2. `get_recurring_incentive` \u2192 full details of a specific incentive\n3. `get_recurring_incentive_analytics` \u2192 performance metrics\n\n## Viewing Results & Payouts\n\n1. `get_incentive_results` with `mode: "preview"` \u2192 evaluation scores (ranked wallets + metric values)\n2. `get_incentive_results` with `mode: "recipients"` \u2192 allocations, payout amounts, and distribution status (DISTRIBUTED/PENDING/FAILED)\n3. `get_incentive_results` with `mode: "download"` \u2192 full CSV download URL\n4. `get_offer_details` \u2192 active offers with reward pools and timeframes\n\n**Do NOT use `preview_incentive_query` to check live results** \u2014 it runs a new query against raw data. Use `get_incentive_results` for existing incentive results.\n\n## Building Landing Pages\n\nRead the `torque://building-landing-pages` resource for a complete guide to building user-facing pages with leaderboards, claims, and reward status.\n\n## Terminology\n\n- **Primitive** \u2014 Incentive mechanic: Leaderboard, Rebate, Raffle, or Direct (fixed wallet allocations)\n- **Action** \u2014 What users do to qualify: Swap, Bonding Curve Buy, Hold, Custom Event, Custom Instruction\n- **Measure** \u2014 How actions are scored: Volume, Count, Duration, Balance\n- **Recipe** \u2014 Primitive + Action + Measure = a complete incentive configuration\n- **Evaluation period** \u2014 Time window for measuring activity (daily, weekly, monthly)\n\n## Tips\n\n- Token incentives are the fastest path \u2014 data is already indexed, no setup needed\n- For custom events, the event must be ingested at least once before query generation works (columnMapping must be non-null)\n- Use `get_ai_context` to fetch project-specific documentation and domain knowledge\n- Always preview before confirming: `create_recurring_incentive` with `confirmed: false`\n- **For leaderboards:** Always ask how the user wants rewards distributed (fixed prizes per rank, tiered ranges, equal splits, etc.) and build a `customFormula` \u2014 the default `N` pays raw metric values which is rarely intended\n- **For raffles:** Default weighting is WEIGHTED_BY_METRIC (query metric = ticket count). Use `raffleWeighting: "EQUAL_CHANCES"` if the user wants equal odds for all participants.\n';function it(t){t.registerResource("workflows-guide","torque://workflows",{description:"Guide to common Torque workflows including custom event incentives, IDL instruction incentives, and simple incentives. Read this to understand the tool pipeline and available guided prompts.",mimeType:"text/markdown"},async()=>({contents:[{uri:"torque://workflows",mimeType:"text/markdown",text:gr}]}))}var yr=`# Torque MCP \u2014 Capabilities
274
291
 
275
292
  ## What is Torque MCP?
276
293
 
@@ -324,7 +341,7 @@ Most tools also require an **active project** \u2014 call \`list_projects\` then
324
341
  | Tool | Description | Requires |
325
342
  |------|-------------|----------|
326
343
  | \`generate_incentive_query\` | Generate SQL for any incentive \u2014 swap, bonding curve, hold, custom event, or IDL instruction | auth, project |
327
- | \`preview_incentive_query\` | Preview query results against real indexed data before creating an incentive | auth, project |
344
+ | \`preview_incentive_query\` | Preview query results during incentive creation (NOT for live results \u2014 use \`get_incentive_results\`) | auth, project |
328
345
 
329
346
  ### Incentives
330
347
  | Tool | Description | Requires |
@@ -334,6 +351,12 @@ Most tools also require an **active project** \u2014 call \`list_projects\` then
334
351
  | \`get_recurring_incentive\` | Get details of a specific incentive | auth, project |
335
352
  | \`get_recurring_incentive_analytics\` | View performance analytics for an incentive | auth, project |
336
353
 
354
+ ### Results & Monitoring
355
+ | Tool | Description | Requires |
356
+ |------|-------------|----------|
357
+ | \`get_incentive_results\` | Fetch results for a live incentive \u2014 preview (evaluation scores), recipients (allocations & payout status), or download (full CSV) | auth, project |
358
+ | \`get_offer_details\` | Get active offer summaries with reward pools and timeframes | auth, project |
359
+
337
360
  ### Context
338
361
  | Tool | Description | Requires |
339
362
  |------|-------------|----------|
@@ -365,4 +388,191 @@ Most tools also require an **active project** \u2014 call \`list_projects\` then
365
388
  | See what events a project has | \`list_project_events\` |
366
389
  | See what on-chain programs are tracked | \`list_idls\` |
367
390
  | Manage API keys for event ingestion | \`list_api_keys\` or \`create_api_key\` |
368
- `;function tt(t){t.registerResource("capabilities-guide","torque://capabilities",{description:"Complete guide to all Torque MCP tools, prompts, and capabilities. Read this when the user asks what they can do, how to use Torque, or needs help getting started.",mimeType:"text/markdown"},async()=>({contents:[{uri:"torque://capabilities",mimeType:"text/markdown",text:cr}]}))}var ur='# Torque \u2014 Incentive Types & Formulas\n\n## What Are Incentive Types?\n\nIncentive types determine the **reward distribution shape** \u2014 how rewards are calculated and distributed to qualifying users. Every recurring incentive has a type that controls what additional inputs are needed and how the reward formula works.\n\nAny query (custom event-based or otherwise) can use any type.\n\n## Available Types\n\n### Leaderboard (\\`leaderboard\\`)\nRank users by a metric and reward based on position. Uses \\`distributionType: "FORMULA"\\` under the hood.\n\n**IMPORTANT:** Always ask the user how they want rewards distributed before creating a leaderboard. The fallback formula \\`N\\` pays each user their raw metric value (e.g., a trader with $10,000 volume would receive 10,000 tokens \u2014 essentially doubling their money). This is rarely the desired behavior. Build a \\`customFormula\\` from the user\'s stated payout intent.\n\n**Common formula patterns:**\n- **Fixed rank prizes:** \\`RANK == 1 ? 10000 : RANK == 2 ? 5000 : RANK == 3 ? 2000 : 0\\` \u2014 set \\`totalFundAmount\\` >= sum of all prizes\n- **Tiered ranges:** \\`RANK <= 3 ? 10000 : RANK <= 10 ? 5000 : RANK <= 50 ? 1000 : 0\\` \u2014 top 3 get 10k, 4-10 get 5k, 11-50 get 1k\n- **Equal split:** \\`TOTAL_REWARD_POOL / TOTAL_PARTICIPANTS\\` \u2014 equal share for everyone who qualifies\n- **Diminishing by rank:** \\`TOTAL_REWARD_POOL / pow(RANK, 0.5)\\` \u2014 top performers get more, lower ranks get reduced amounts as pool depletes\n- **Capped payouts:** \\`min(N * 0.1, 500)\\` \u2014 10% of metric value, capped at 500 per user\n\n**Required inputs:** \\`customFormula\\` (strongly recommended \u2014 ask the user)\n\n### Rebate (\\`rebate\\`)\nReturn a percentage of a tracked value to users. Uses \\`distributionType: "FORMULA"\\` under the hood \u2014 the MCP constructs the formula from \\`rebatePercentage\\`.\n\n**Formula:** \\`VALUE * (rebatePercentage / 100)\\` \u2014 e.g., 5% rebate becomes \\`VALUE * 0.05\\`. The MCP builds this formula automatically from the \\`rebatePercentage\\` parameter.\n\n**Custom formula examples:**\n- Tiered: \\`VALUE > 1000 ? VALUE * 0.1 : VALUE * 0.05\\`\n- Capped: \\`min(VALUE * 0.05, 500)\\`\n\n**Required inputs:** \\`rebatePercentage\\`\n\n### Raffle (\\`raffle\\`)\nRandomly select winners from qualifying users. **Always ask the user how they want raffle tickets distributed** before creating.\n\n**No formula** \u2014 winners are selected randomly based on ticket weight. Prize distribution is defined by \\`raffleBuckets\\`.\n\n**Weighting modes (\\`raffleWeighting\\`) \u2014 ask the user which they want:**\n- **\\`WEIGHTED_BY_METRIC\\`** (default) \u2014 ticket count = user\'s query metric value. A user with 100 volume gets 10x more chances than a user with 10 volume. The query results must be sorted by value (highest first) for this to work correctly.\n- **\\`EQUAL_CHANCES\\`** \u2014 every qualifying user gets exactly 1 ticket. All qualifying users have equal odds regardless of their metric value \u2014 the query value only determines whether they meet the threshold to enter.\n\n**Required inputs:** \\`raffleBuckets\\` (array of {amount, count})\n**Optional inputs:** \\`raffleWeighting\\` (default: WEIGHTED_BY_METRIC)\n\n### Direct (\\`direct\\`)\nSend specific amounts to specific wallet addresses.\n\nProvide a list of wallet addresses and amounts. The MCP constructs an inline SQL query from the allocations and uses \\`distributionType: "FORMULA"\\` with formula \\`N\\` internally, so each wallet receives exactly the specified amount. Direct incentives are created as recurring offers and appear in \\`list_recurring_incentives\\`.\n\n**Required inputs:** \\`allocations\\` (CSV string or array of {address, amount})\n\n## How Formulas Work\n\nFormulas produce **direct payout amounts** \u2014 each user receives exactly what the formula evaluates to, subject to the reward pool cap.\n\n**Processing order:** Users are processed in SQL query result order. The MCP\'s query builders sort by \\`value DESC\\`, so RANK 1 = highest metric value.\n\n**Pool exhaustion:** \\`totalFundAmount\\` is a sequential cap. Each user gets their formula result if the pool has enough remaining. When remaining pool < formula result, the user receives only what\'s left. Remaining users receive nothing. Negative formula results are converted to 0.\n\n## Formula Variables\n\nWhen writing a \\`customFormula\\`, these variables are available:\n\n| Variable | Description |\n|----------|-------------|\n| \\`N\\` / \\`VALUE\\` | The metric value from the SQL query for each user (e.g., total spend, score, event count) |\n| \\`RANK\\` | 1-based position in query results (1 = first/highest row). Depends on SQL ORDER BY. |\n| \\`INDEX\\` | 0-based position (RANK - 1) |\n| \\`TOTAL_PARTICIPANTS\\` | Number of qualifying users in this epoch |\n| \\`TOTAL_REWARD_POOL\\` | The \\`totalFundAmount\\` for this epoch \u2014 caps total payouts sequentially |\n\n## Formula Functions\n\n\\`sqrt\\`, \\`pow\\`, \\`abs\\`, \\`floor\\`, \\`ceil\\`, \\`round\\`, \\`min\\`, \\`max\\`, \\`log\\`, \\`exp\\`\n\n## How Types Connect to Queries\n\n- **Type** = how to distribute rewards (the formula shape)\n- **Query** = who qualifies and what\'s measured (the SQL)\n\nAny query can use any type. For example:\n- A "total purchases" query + leaderboard type = reward top spenders by rank\n- The same query + rebate type = give everyone back a % of what they spent\n- A "game score" query + raffle type = enter everyone who played into a prize draw\n\n## Intent-to-Formula Examples\n\n| User says... | Formula |\n|-------------|---------|\n| "1st place gets 10000, 2nd gets 5000, 3rd gets 2000" | \\`RANK == 1 ? 10000 : RANK == 2 ? 5000 : RANK == 3 ? 2000 : 0\\` |\n| "Top 10 get 5000 each, ranks 11-50 get 1000" | \\`RANK <= 10 ? 5000 : RANK <= 50 ? 1000 : 0\\` |\n| "Split the reward pool equally among everyone" | \\`TOTAL_REWARD_POOL / TOTAL_PARTICIPANTS\\` |\n| "Top players get more, with diminishing returns" | \\`TOTAL_REWARD_POOL / pow(RANK, 0.5)\\` |\n| "Give them back 5% of what they spent" | \\`N * 0.05\\` (or use rebate type with rebatePercentage: 5) |\n| "Reward based on how much they bought but cap it at 500" | \\`min(N * 0.1, 500)\\` |\n| "Bonus tokens for every 10 games completed" | \\`floor(N / 10) * 5\\` |\n| "Flat reward for everyone who qualifies" | Use \\`valueExpression: "1"\\` in the query instead |\n';function rt(t){t.registerResource("incentive-types-guide","torque://incentive-types",{description:"Guide to Torque incentive types, formulas, and how they connect to queries. Read this when configuring reward distribution for an incentive.",mimeType:"text/markdown"},async()=>({contents:[{uri:"torque://incentive-types",mimeType:"text/markdown",text:ur}]}))}function pr(t){if(!t.requires?.length)return t.description;let e=[];return t.requires.includes("auth")&&e.push("requires authentication"),t.requires.includes("activeProject")&&e.push("requires an active project via `set_active_project`"),`${t.description} [PREREQUISITE: ${e.join("; ")}. Call these first and wait for success.]`}function w(t,e,r){t.registerTool(e.name,{description:pr(e),inputSchema:e.inputSchema},async n=>e.handler(n,r))}async function nt(t){let e=new lr({name:"torque-mcp",version:"0.2.0"}),r=F(t);await r.auth.loadPersistedToken(),w(e,he,r),w(e,ye,r),w(e,ge,r),w(e,ve,r),w(e,we,r),w(e,be,r),w(e,Te,r),w(e,Ee,r),w(e,Ie,r),w(e,xe,r),w(e,Pe,r),w(e,Se,r),w(e,De,r),w(e,$e,r),w(e,je,r),w(e,Ce,r),w(e,Ve,r),w(e,ze,r),w(e,Je,r),w(e,Xe,r),w(e,Ge,r),w(e,Ye,r),w(e,Ze,r),e.registerPrompt(M.name,M.config,M.handler),et(e),tt(e),rt(e);let n=new dr;await e.connect(n)}var{action:ot,params:mr}=Z(process.argv.slice(2));ot==="help"&&(ee(),process.exit(0));ot==="version"&&(te(),process.exit(0));var fr=re(mr);nt(fr).catch(t=>{console.error("Failed to start Torque MCP server:",t),process.exit(1)});
391
+ | View current results/payouts | \`get_incentive_results\` (preview, recipients, or download mode) |
392
+ | See what offers are active | \`get_offer_details\` |
393
+ | Build a landing page | Read \`torque://building-landing-pages\` resource |
394
+ `;function ot(t){t.registerResource("capabilities-guide","torque://capabilities",{description:"Complete guide to all Torque MCP tools, prompts, and capabilities. Read this when the user asks what they can do, how to use Torque, or needs help getting started.",mimeType:"text/markdown"},async()=>({contents:[{uri:"torque://capabilities",mimeType:"text/markdown",text:yr}]}))}var vr='# Torque \u2014 Incentive Types & Formulas\n\n## What Are Incentive Types?\n\nIncentive types determine the **reward distribution shape** \u2014 how rewards are calculated and distributed to qualifying users. Every recurring incentive has a type that controls what additional inputs are needed and how the reward formula works.\n\nAny query (custom event-based or otherwise) can use any type.\n\n## Available Types\n\n### Leaderboard (\\`leaderboard\\`)\nRank users by a metric and reward based on position. Uses \\`distributionType: "FORMULA"\\` under the hood.\n\n**IMPORTANT:** Always ask the user how they want rewards distributed before creating a leaderboard. The fallback formula \\`N\\` pays each user their raw metric value (e.g., a trader with $10,000 volume would receive 10,000 tokens \u2014 essentially doubling their money). This is rarely the desired behavior. Build a \\`customFormula\\` from the user\'s stated payout intent.\n\n**Common formula patterns:**\n- **Fixed rank prizes:** \\`RANK == 1 ? 10000 : RANK == 2 ? 5000 : RANK == 3 ? 2000 : 0\\` \u2014 set \\`totalFundAmount\\` >= sum of all prizes\n- **Tiered ranges:** \\`RANK <= 3 ? 10000 : RANK <= 10 ? 5000 : RANK <= 50 ? 1000 : 0\\` \u2014 top 3 get 10k, 4-10 get 5k, 11-50 get 1k\n- **Equal split:** \\`TOTAL_REWARD_POOL / TOTAL_PARTICIPANTS\\` \u2014 equal share for everyone who qualifies\n- **Diminishing by rank:** \\`TOTAL_REWARD_POOL / pow(RANK, 0.5)\\` \u2014 top performers get more, lower ranks get reduced amounts as pool depletes\n- **Capped payouts:** \\`min(N * 0.1, 500)\\` \u2014 10% of metric value, capped at 500 per user\n\n**Required inputs:** \\`customFormula\\` (strongly recommended \u2014 ask the user)\n\n### Rebate (\\`rebate\\`)\nReturn a percentage of a tracked value to users. Uses \\`distributionType: "FORMULA"\\` under the hood \u2014 the MCP constructs the formula from \\`rebatePercentage\\`.\n\n**Formula:** \\`VALUE * (rebatePercentage / 100)\\` \u2014 e.g., 5% rebate becomes \\`VALUE * 0.05\\`. The MCP builds this formula automatically from the \\`rebatePercentage\\` parameter.\n\n**Custom formula examples:**\n- Tiered: \\`VALUE > 1000 ? VALUE * 0.1 : VALUE * 0.05\\`\n- Capped: \\`min(VALUE * 0.05, 500)\\`\n\n**Required inputs:** \\`rebatePercentage\\`\n\n### Raffle (\\`raffle\\`)\nRandomly select winners from qualifying users. **Always ask the user how they want raffle tickets distributed** before creating.\n\n**No formula** \u2014 winners are selected randomly based on ticket weight. Prize distribution is defined by \\`raffleBuckets\\`.\n\n**Weighting modes (\\`raffleWeighting\\`) \u2014 ask the user which they want:**\n- **\\`WEIGHTED_BY_METRIC\\`** (default) \u2014 ticket count = user\'s query metric value. A user with 100 volume gets 10x more chances than a user with 10 volume. The query results must be sorted by value (highest first) for this to work correctly.\n- **\\`EQUAL_CHANCES\\`** \u2014 every qualifying user gets exactly 1 ticket. All qualifying users have equal odds regardless of their metric value \u2014 the query value only determines whether they meet the threshold to enter.\n\n**Required inputs:** \\`raffleBuckets\\` (array of {amount, count})\n**Optional inputs:** \\`raffleWeighting\\` (default: WEIGHTED_BY_METRIC)\n\n### Direct (\\`direct\\`)\nSend specific amounts to specific wallet addresses.\n\nProvide a list of wallet addresses and amounts. The MCP constructs an inline SQL query from the allocations and uses \\`distributionType: "FORMULA"\\` with formula \\`N\\` internally, so each wallet receives exactly the specified amount. Direct incentives are created as recurring offers and appear in \\`list_recurring_incentives\\`.\n\n**Required inputs:** \\`allocations\\` (CSV string or array of {address, amount})\n\n## How Formulas Work\n\nFormulas produce **direct payout amounts** \u2014 each user receives exactly what the formula evaluates to, subject to the reward pool cap.\n\n**Processing order:** Users are processed in SQL query result order. The MCP\'s query builders sort by \\`value DESC\\`, so RANK 1 = highest metric value.\n\n**Pool exhaustion:** \\`totalFundAmount\\` is a sequential cap. Each user gets their formula result if the pool has enough remaining. When remaining pool < formula result, the user receives only what\'s left. Remaining users receive nothing. Negative formula results are converted to 0.\n\n## Formula Variables\n\nWhen writing a \\`customFormula\\`, these variables are available:\n\n| Variable | Description |\n|----------|-------------|\n| \\`N\\` / \\`VALUE\\` | The metric value from the SQL query for each user (e.g., total spend, score, event count) |\n| \\`RANK\\` | 1-based position in query results (1 = first/highest row). Depends on SQL ORDER BY. |\n| \\`INDEX\\` | 0-based position (RANK - 1) |\n| \\`TOTAL_PARTICIPANTS\\` | Number of qualifying users in this epoch |\n| \\`TOTAL_REWARD_POOL\\` | The \\`totalFundAmount\\` for this epoch \u2014 caps total payouts sequentially |\n\n## Formula Functions\n\n\\`sqrt\\`, \\`pow\\`, \\`abs\\`, \\`floor\\`, \\`ceil\\`, \\`round\\`, \\`min\\`, \\`max\\`, \\`log\\`, \\`exp\\`\n\n## How Types Connect to Queries\n\n- **Type** = how to distribute rewards (the formula shape)\n- **Query** = who qualifies and what\'s measured (the SQL)\n\nAny query can use any type. For example:\n- A "total purchases" query + leaderboard type = reward top spenders by rank\n- The same query + rebate type = give everyone back a % of what they spent\n- A "game score" query + raffle type = enter everyone who played into a prize draw\n\n## Intent-to-Formula Examples\n\n| User says... | Formula |\n|-------------|---------|\n| "1st place gets 10000, 2nd gets 5000, 3rd gets 2000" | \\`RANK == 1 ? 10000 : RANK == 2 ? 5000 : RANK == 3 ? 2000 : 0\\` |\n| "Top 10 get 5000 each, ranks 11-50 get 1000" | \\`RANK <= 10 ? 5000 : RANK <= 50 ? 1000 : 0\\` |\n| "Split the reward pool equally among everyone" | \\`TOTAL_REWARD_POOL / TOTAL_PARTICIPANTS\\` |\n| "Top players get more, with diminishing returns" | \\`TOTAL_REWARD_POOL / pow(RANK, 0.5)\\` |\n| "Give them back 5% of what they spent" | \\`N * 0.05\\` (or use rebate type with rebatePercentage: 5) |\n| "Reward based on how much they bought but cap it at 500" | \\`min(N * 0.1, 500)\\` |\n| "Bonus tokens for every 10 games completed" | \\`floor(N / 10) * 5\\` |\n| "Flat reward for everyone who qualifies" | Use \\`valueExpression: "1"\\` in the query instead |\n';function st(t){t.registerResource("incentive-types-guide","torque://incentive-types",{description:"Guide to Torque incentive types, formulas, and how they connect to queries. Read this when configuring reward distribution for an incentive.",mimeType:"text/markdown"},async()=>({contents:[{uri:"torque://incentive-types",mimeType:"text/markdown",text:vr}]}))}var wr=`# Torque \u2014 Building Landing Pages
395
+
396
+ How leaderboards, claims, epochs, and results connect, and how to build a landing page that displays incentive data and enables users to claim rewards.
397
+
398
+ ---
399
+
400
+ ## 1. Domain Model Overview
401
+
402
+ \`\`\`
403
+ Project
404
+ \u2514\u2500\u2500 Recurring Incentive (leaderboard | rebate | raffle | direct)
405
+ \u2514\u2500\u2500 Epoch (evaluation period)
406
+ \u251C\u2500\u2500 Status lifecycle: PENDING \u2192 EVALUATING \u2192 CLAIMING \u2192 COMPLETED
407
+ \u251C\u2500\u2500 Query \u2192 Results (ranked wallets + metric values)
408
+ \u2514\u2500\u2500 Offer (on-chain reward distribution)
409
+ \`\`\`
410
+
411
+ Key concepts:
412
+
413
+ - A **project** has one or more **recurring incentives**
414
+ - Each incentive runs in **epochs** \u2014 time-bounded evaluation periods
415
+ - **Epoch status lifecycle:** PENDING \u2192 EVALUATING \u2192 CLAIMING \u2192 COMPLETED
416
+ - During **EVALUATING**, the query runs and produces **results** \u2014 ranked wallets with metric values (this is the leaderboard data)
417
+ - During **CLAIMING**, eligible users can **claim rewards** via an API call with their wallet pubkey
418
+ - Each epoch has an associated **offer** that handles on-chain reward distribution
419
+ - Use \`get_recurring_incentive\` to inspect epoch details: status, eval/claim windows, offer IDs, query IDs
420
+
421
+ ---
422
+
423
+ ## 2. Displaying Results & Scores
424
+
425
+ ### Via MCP Tools (authenticated, for builders)
426
+
427
+ | Tool | Mode | Use Case |
428
+ |------|------|----------|
429
+ | \`get_incentive_results\` | \`preview\` | Evaluation scores \u2014 ranked wallets and raw metric values from the query |
430
+ | \`get_incentive_results\` | \`recipients\` | Allocations and payouts \u2014 wallet addresses, amounts, and distribution status (DISTRIBUTED/PENDING/FAILED) |
431
+ | \`get_incentive_results\` | \`download\` | Full CSV download URL for the complete result set |
432
+
433
+ ### Via REST API (public, for frontends)
434
+
435
+ **Latest evaluation results** (live scores):
436
+ \`\`\`
437
+ GET /recurring-offer/:id/latest-eval-results?limit=10&offset=0
438
+ \`\`\`
439
+ - Fully public, no auth required
440
+ - Returns ranked wallets with metric values from the most recent evaluation
441
+ - Use this for live leaderboard displays
442
+
443
+ **Note:** Recipients data (allocations and payout status) requires project authentication and should be fetched from your backend using the MCP \`get_incentive_results\` tool with \`mode: "recipients"\`.
444
+
445
+ **Offer stats** (aggregate participation):
446
+ \`\`\`
447
+ GET /offer/:offerId/journey/stats
448
+ \`\`\`
449
+ - Fully public, no auth required
450
+ - Returns total started and total converted counts
451
+
452
+ ---
453
+
454
+ ## 3. Displaying Offer Info
455
+
456
+ ### Via MCP Tools (authenticated)
457
+
458
+ Use \`get_offer_details\` to fetch active offers for a project \u2014 reward pools, timeframes, and metadata.
459
+
460
+ ### Via REST API (for frontends)
461
+
462
+ \`\`\`
463
+ GET /claim/details/byOffer?projectId=X&offerStatus=ACTIVE
464
+ \`\`\`
465
+ - Works **without auth** for non-gated offers
466
+ - Add \`&wallet=<userPubkey>\` to also get:
467
+ - \`isEligible\` \u2014 whether the wallet qualifies
468
+ - \`eligibleAmounts\` \u2014 reward amounts the wallet can claim
469
+
470
+ ---
471
+
472
+ ## 4. Displaying Claim Status (Per User)
473
+
474
+ \`\`\`
475
+ GET /claim/details/byAllocation?projectId=X&wallet=<userPubkey>
476
+ \`\`\`
477
+ - Works **without auth** for non-gated offers
478
+ - Returns per-allocation details:
479
+
480
+ | Field | Description |
481
+ |-------|-------------|
482
+ | \`offerId\` | The offer this allocation belongs to |
483
+ | \`amount\` | Reward amount |
484
+ | \`token\` | Reward token address |
485
+ | \`availableAt\` | When the claim becomes available |
486
+ | \`crankStatus\` | Processing status (see below) |
487
+
488
+ **Crank status lifecycle:**
489
+ \`\`\`
490
+ STAGED \u2192 STARTED \u2192 PENDING \u2192 DONE (or FAILED)
491
+ \`\`\`
492
+ - **STAGED** \u2014 Claim queued for processing
493
+ - **STARTED** \u2014 Processing has begun
494
+ - **PENDING** \u2014 Transaction submitted, waiting for confirmation
495
+ - **DONE** \u2014 Complete; includes \`transactionSignature\` \u2014 link to Solana explorer
496
+ - **FAILED** \u2014 Processing failed; may be retried
497
+
498
+ Poll this endpoint for real-time progress updates on the user's claims.
499
+
500
+ ---
501
+
502
+ ## 5. Enabling Claims
503
+
504
+ Claims are **fully server-side** \u2014 no transaction signing is required from the user's wallet.
505
+
506
+ **Steps:**
507
+
508
+ 1. **Connect wallet** \u2014 use a wallet adapter (e.g. \`@solana/wallet-adapter-react\`) to get the user's pubkey
509
+ 2. **Trigger claims:**
510
+ \`\`\`
511
+ GET /claim?projectId=X&wallet=<userPubkey>
512
+ \`\`\`
513
+ This queues **all eligible claims** for the wallet. Multiple claims may be available if the user qualifies for more than one allocation.
514
+ 3. **Poll for status:**
515
+ \`\`\`
516
+ GET /claim/details/byAllocation?projectId=X&wallet=<userPubkey>
517
+ \`\`\`
518
+ Watch \`crankStatus\` progress from STAGED through DONE.
519
+
520
+ ---
521
+
522
+ ## 6. Auth Considerations
523
+
524
+ | Endpoint Category | Auth Required | Safe For |
525
+ |-------------------|---------------|----------|
526
+ | Latest eval results (\`/recurring-offer/:id/latest-eval-results\`) | None | Client-side, fully public |
527
+ | Offer stats (\`/offer/:id/journey/stats\`) | None | Client-side, fully public |
528
+ | Claim trigger (\`/claim\`) | Optional API key | Client-side for non-gated |
529
+ | Claim status (\`/claim/details/byAllocation\`) | Optional API key | Client-side for non-gated |
530
+ | Offer details (\`/claim/details/byOffer\`) | Optional API key | Client-side for non-gated |
531
+ | Incentive results (\`get_incentive_results\`) | Project auth | Backend only |
532
+ | Recipients / payouts (\`get_incentive_results\` recipients mode) | Project auth | Backend only |
533
+ | Epoch data (\`get_recurring_incentive\`) | Project auth | Backend only |
534
+ | Query downloads | Project auth | Backend only |
535
+
536
+ **Rule of thumb:** If it is user-facing display data (eval results, stats, claim status), it is public. If it is project management data (epoch configs, recipient allocations, raw query downloads), it requires project authentication and should only be called from your backend.
537
+
538
+ ---
539
+
540
+ ## 7. Recommended Architecture
541
+
542
+ \`\`\`
543
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
544
+ \u2502 FRONTEND \u2502
545
+ \u2502 \u2502
546
+ \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
547
+ \u2502 \u2502 Wallet \u2502 \u2502 Public API Calls \u2502 \u2502
548
+ \u2502 \u2502 Connect \u2502 \u2502 \u2502 \u2502
549
+ \u2502 \u2502 (get pubkey) \u2502 \u2502 \u2022 GET /recurring-offer/ \u2502 \u2502
550
+ \u2502 \u2502 \u2502 \u2502 :id/latest-eval- \u2502 \u2502
551
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502 results \u2502 \u2502
552
+ \u2502 \u2502 \u2502 \u2022 GET /stats \u2502 \u2502
553
+ \u2502 \u2502 \u2502 \u2022 GET /claim \u2502 \u2502
554
+ \u2502 \u2502 \u2502 \u2022 GET /claim/details/... \u2502 \u2502
555
+ \u2502 \u2502 \u2502 \u2022 GET /claim/details/ \u2502 \u2502
556
+ \u2502 \u2502 \u2502 byOffer \u2502 \u2502
557
+ \u2502 \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
558
+ \u2502 \u2502 \u2502
559
+ \u2502 \u2514\u2500\u2500\u2500 pubkey used for claim + status \u2500\u2500\u25BA \u2502
560
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
561
+
562
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
563
+ \u2502 BACKEND \u2502
564
+ \u2502 \u2502
565
+ \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\u2502
566
+ \u2502 \u2502 Authenticated Calls (project auth / API key) \u2502\u2502
567
+ \u2502 \u2502 \u2502\u2502
568
+ \u2502 \u2502 \u2022 get_recurring_incentive (epoch details) \u2502\u2502
569
+ \u2502 \u2502 \u2022 get_incentive_results (eval results, CSV) \u2502\u2502
570
+ \u2502 \u2502 \u2022 Gated offer details \u2502\u2502
571
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\u2502
572
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
573
+ \`\`\`
574
+
575
+ **Frontend** handles wallet connection and all public endpoints \u2014 eval results display, offer info, claim triggering, and claim status polling. No secrets needed.
576
+
577
+ **Backend** handles authenticated calls for epoch management, raw evaluation results, and any API-key-gated offer queries. Keep project auth tokens and API keys server-side only.
578
+ `;function at(t){t.registerResource("building-landing-pages-guide","torque://building-landing-pages",{description:"Guide to building landing pages that display leaderboards, offer info, and claim status. Covers the domain model (projects, incentives, epochs, results), public REST endpoints for frontends, claim flow, auth requirements, and recommended frontend/backend architecture. Read this when helping a builder create a frontend for their incentive program.",mimeType:"text/markdown"},async()=>({contents:[{uri:"torque://building-landing-pages",mimeType:"text/markdown",text:wr}]}))}function Er(t){if(!t.requires?.length)return t.description;let e=[];return t.requires.includes("auth")&&e.push("requires authentication"),t.requires.includes("activeProject")&&e.push("requires an active project via `set_active_project`"),`${t.description} [PREREQUISITE: ${e.join("; ")}. Call these first and wait for success.]`}function v(t,e,r){t.registerTool(e.name,{description:Er(e),inputSchema:e.inputSchema},async n=>e.handler(n,r))}async function ct(t){let e=new br({name:"torque-mcp",version:"0.3.0"}),r=B(t);await r.auth.loadPersistedToken(),v(e,ge,r),v(e,ye,r),v(e,ve,r),v(e,we,r),v(e,be,r),v(e,Te,r),v(e,Ee,r),v(e,Ae,r),v(e,Pe,r),v(e,xe,r),v(e,De,r),v(e,Se,r),v(e,tt,r),v(e,nt,r),v(e,$e,r),v(e,Ce,r),v(e,je,r),v(e,Ne,r),v(e,ze,r),v(e,Je,r),v(e,Xe,r),v(e,Ze,r),v(e,Ve,r),v(e,Ye,r),v(e,et,r),e.registerPrompt(F.name,F.config,F.handler),it(e),ot(e),st(e),at(e);let n=new Tr;await e.connect(n)}var{action:ut,params:Ar}=ee(process.argv.slice(2));ut==="help"&&(te(),process.exit(0));ut==="version"&&(re(),process.exit(0));var _r=ne(Ar);ct(_r).catch(t=>{console.error("Failed to start Torque MCP server:",t),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@torque-labs/mcp",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "MCP server for the Torque incentive platform on Solana — create and manage token reward programs through AI assistants",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",