@torque-labs/mcp 0.3.1 → 0.3.5

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 +0 -1
  2. package/dist/index.js +233 -101
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -163,7 +163,6 @@ You can provide either an auth token or a private key, or authenticate at runtim
163
163
  | Tool | Description |
164
164
  | ------------------------ | ------------------------------------------------------------------------------------------ |
165
165
  | `get_incentive_results` | View leaderboard scores/rankings, check recipient payout status, or download/export full CSV — three modes: preview, recipients, download |
166
- | `get_offer_details` | View active incentive/campaign offers — reward pools, timeframes, and metadata |
167
166
 
168
167
  ### Context
169
168
 
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
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=`
2
+ function ue(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 le(){let t=`
3
3
  Torque MCP Server \u2014 connect AI assistants to the Torque incentive platform on Solana.
4
4
 
5
5
  Usage:
@@ -25,146 +25,139 @@ 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 re(){process.stderr.write(`0.3.1
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 N from"fs/promises";import j from"path";var q=class{token=null;authToken=null;baseUrl;cacheDir;constructor(e,r){this.baseUrl=e,this.cacheDir=r||j.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=j.join(this.cacheDir,"token.json");try{await N.unlink(e)}catch{}}async loadPersistedToken(){let e=j.join(this.cacheDir,"token.json");try{let r=await N.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 N.mkdir(this.cacheDir,{recursive:!0});let n=j.join(this.cacheDir,"token.json"),i={token:e,publicKey:r,timestamp:Date.now()};await N.writeFile(n,JSON.stringify(i,null,2))}};function Q(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
- `)}function oe(t,e){if(t.length===0)return["\u{1F4C1} **No projects found**","","Use `create_project` to create one."].join(`
28
+ `.trimStart();process.stderr.write(t)}function de(){process.stderr.write(`0.3.5
29
+ `)}var Pt="https://server.torque.so",Dt="https://platform.torque.so",Ct="https://ingest.torque.so",$t="Sign in to Torque";function pe(t){let e=t.apiUrl??process.env.TORQUE_API_URL??Pt,r=t.platformUrl??process.env.TORQUE_PLATFORM_URL??Dt,n=t.ingesterUrl??process.env.TORQUE_INGESTER_URL??Ct,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:$t,privateKey:i,apiToken:o}}import{McpServer as en}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as tn}from"@modelcontextprotocol/sdk/server/stdio.js";import{createKeyPairSignerFromBytes as Nt,getBase58Encoder as jt,getTransactionDecoder as Lt,getTransactionEncoder as qt,partiallySignTransaction as Ot,signBytes as Ut}from"@solana/kit";async function me(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=jt().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 Nt(r);return{address:n.address,async signMessage(i){let o=await Ut(n.keyPair.privateKey,i);return new Uint8Array(o)},async signTransaction(i){let o=Lt(),s=qt(),u=o.decode(i),c=await Ot([n.keyPair],u);return new Uint8Array(s.encode(c))}}}import F from"fs/promises";import Q from"path";var B=class{token=null;authToken=null;baseUrl;cacheDir;constructor(e,r){this.baseUrl=e,this.cacheDir=r||Q.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=Q.join(this.cacheDir,"token.json");try{await F.unlink(e)}catch{}}async loadPersistedToken(){let e=Q.join(this.cacheDir,"token.json");try{let r=await F.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 F.mkdir(this.cacheDir,{recursive:!0});let n=Q.join(this.cacheDir,"token.json"),i={token:e,publicKey:r,timestamp:Date.now()};await F.writeFile(n,JSON.stringify(i,null,2))}};function Y(t){let e=new B(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 me(t.privateKey),r},auth:{login:(o,s)=>e.login(o,s),loginWithStatement:(o,s,u)=>e.loginWithStatement(o,s,u),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 u=e.getAuthHeader();if(!u)throw new Error("Not authenticated. Use the authenticate tool first or set TORQUE_API_TOKEN.");let c=`${t.apiBaseUrl}${o}`,l=await fetch(c,{method:s?.method??"GET",headers:{...u,...s?.body?{"Content-Type":"application/json"}:{}},...s?.body?{body:JSON.stringify(s.body)}:{}});if(!l.ok){let p=await l.text();throw new Error(`API request failed (${l.status}): ${p}`)}return await l.json()},config:t}}import{z as tr}from"zod";import{getBase58Decoder as rr}from"@solana/kit";import{z as a}from"zod";var yn=a.enum(["SUCCESS","BAD_REQUEST","NOT_AUTHORIZED","FORBIDDEN","NOT_FOUND","INTERNAL_ERROR"]);function v(t,e){let r=a.discriminatedUnion("status",[a.object({status:a.literal("SUCCESS"),data:t}),a.object({status:a.enum(["BAD_REQUEST","NOT_AUTHORIZED","FORBIDDEN","NOT_FOUND","INTERNAL_ERROR"]),message:a.string()})]).parse(e);if(r.status!=="SUCCESS")throw new Error(r.message);return r.data}function I(t,e){return v(a.array(t),e)}var vn=a.enum(["TOKENS","SOL","POINTS"]),bn=a.enum(["DIRECT","MANUAL","CSV","FORMULA","RAFFLE"]),Mt=a.enum(["AIRDROP","CLAIM"]),Ft=a.enum(["EQUAL_CHANCES","WEIGHTED_BY_METRIC"]),fe=a.enum(["ALLOCATION","PERCENTAGE"]),Qt=a.enum(["UPCOMING","EVALUATING","CLAIMING","COMPLETED","FAILED"]),Bt=a.enum(["DISTRIBUTED","PENDING","FAILED"]),Wt=a.discriminatedUnion("type",[a.object({type:a.literal("REBATE"),maxPerParticipant:a.number().nullish()}),a.object({type:a.literal("RAFFLE"),maxPerParticipant:a.number().nullish()}),a.object({type:a.literal("COMPETITION")}),a.object({type:a.literal("DIRECT")})]),Gt=a.discriminatedUnion("distributionType",[a.object({distributionType:a.literal("DIRECT"),audienceId:a.string().nullish(),csvFile:a.string().nullish()}),a.object({distributionType:a.literal("MANUAL"),valueType:fe,manualRanks:a.array(a.union([a.object({rank:a.number(),value:a.number()}),a.object({rankFrom:a.number(),rankTo:a.number(),value:a.number()})]))}),a.object({distributionType:a.literal("CSV"),valueType:fe.optional(),csvFile:a.string()}),a.object({distributionType:a.literal("FORMULA"),customFormula:a.string()}),a.object({distributionType:a.literal("RAFFLE"),selectionLogic:Ft,prizeBuckets:a.array(a.object({amount:a.number(),count:a.number()})),allowDuplicateWinners:a.boolean().optional()})]),Ht=a.discriminatedUnion("emissionType",[a.object({emissionType:a.literal("TOKENS"),tokenAddress:a.string(),tokenDecimals:a.number()}),a.object({emissionType:a.literal("SOL")}),a.object({emissionType:a.literal("POINTS"),pointId:a.string()})]),Vt=a.object({totalFundAmount:a.number(),maxPerParticipant:a.number().nullish(),distributionMethod:Mt,claimWindowStart:a.coerce.date().optional(),claimWindowDuration:a.number(),timezoneOffset:a.number().optional()}),Kt=Gt.and(Ht).and(Vt),zt=a.object({title:a.string(),description:a.string().optional(),image:a.string().optional(),primitive:Wt.nullish()}),Yt=a.object({id:a.string(),recurringOfferId:a.string().nullable(),epochNumber:a.number(),status:Qt,evalStart:a.coerce.date(),evalEnd:a.coerce.date(),claimStart:a.coerce.date(),claimEnd:a.coerce.date(),sqlQueryVersionId:a.string(),evalQueryFrequency:a.number(),distributionConfig:Kt,offerMetadata:zt,sqlQueryId:a.string().nullable(),addressColumn:a.string().nullable(),metricColumn:a.string(),failureReason:a.string().nullish(),offerId:a.string().nullish(),createdAt:a.coerce.date(),updatedAt:a.coerce.date()}),Jt=Yt.extend({sqlQuery:a.object({id:a.string(),name:a.string()}).nullish()}),P=a.object({id:a.string(),name:a.string(),description:a.string().nullish(),type:a.string().nullish(),isActive:a.boolean(),maxIterations:a.number().nullish(),startDate:a.coerce.date().nullish(),timezone:a.string().optional(),createdAt:a.coerce.date(),updatedAt:a.coerce.date(),deleted:a.boolean(),userId:a.string(),projectId:a.string().nullish(),campaignId:a.string().nullish(),configs:a.array(Jt).optional()}),Xt=a.object({walletAddress:a.string(),metricValue:a.number()}),he=a.object({results:a.array(Xt),total:a.number(),offset:a.number(),limit:a.number(),lastUpdated:a.coerce.date().nullable()}),Zt=a.object({walletAddress:a.string(),allocatedAmount:a.number(),distributedAmount:a.number().nullable(),status:Bt}),ge=a.object({recipients:a.array(Zt),total:a.number(),offset:a.number(),limit:a.number()}),$=a.object({id:a.string(),name:a.string(),description:a.string().nullish(),image:a.string().nullish(),defaultTokenAddress:a.string().nullish(),defaultProgramAddress:a.string().nullish(),createdAt:a.coerce.date(),updatedAt:a.coerce.date()}),ye=a.object({id:a.string(),name:a.string().nullable(),key:a.string(),isActive:a.boolean(),lastUsedAt:a.coerce.date().nullable(),createdAt:a.coerce.date()}),ve=a.object({id:a.string(),name:a.string().nullable(),key:a.string()}),be=a.object({id:a.string(),name:a.string().optional()}),we=a.object({queryId:a.string()}),Te=a.object({status:a.string(),preview:a.string().nullish(),error:a.string().nullish(),totalRows:a.number().nullish(),duration:a.number().nullish()}),Ee=a.object({url:a.string()}),N=a.object({id:a.string(),name:a.string(),eventName:a.string(),active:a.boolean(),deleted:a.boolean(),creatorId:a.string().nullable(),fields:a.array(a.object({fieldName:a.string(),type:a.enum(["string","number","boolean"]),label:a.string().optional(),description:a.string().optional()})),columnMapping:a.record(a.string(),a.string()).nullable(),createdAt:a.coerce.date(),updatedAt:a.coerce.date()}),Ae=N.extend({projects:a.array(a.object({id:a.string(),projectId:a.string(),customEventId:a.string(),createdAt:a.coerce.date()}))}),er=a.object({id:a.string(),instructionName:a.string(),label:a.string(),idlId:a.string(),projectId:a.string(),instruction:a.array(a.object({fieldName:a.string(),type:a.enum(["string","number","boolean"]),label:a.string().optional(),description:a.string().optional()})),fieldMapping:a.record(a.string(),a.string()),accountMapping:a.record(a.string(),a.string()),active:a.boolean().optional(),deleted:a.boolean().optional(),createdAt:a.coerce.date(),updatedAt:a.coerce.date()}),W=a.object({id:a.string(),projectId:a.string(),programAddress:a.string(),name:a.string(),displayName:a.string().nullable(),description:a.string().nullable(),idl:a.unknown(),types:a.unknown().nullable(),active:a.boolean(),deleted:a.boolean(),instructions:a.array(er).nullish(),createdAt:a.coerce.date(),updatedAt:a.coerce.date()}),Re=a.object({id:a.string(),instructionIds:a.array(a.string()).optional()}),_e=a.object({id:a.string()}),Ie=a.object({addresses:a.object({programAddress:a.string().optional(),tokenAddress:a.string().optional()}).optional(),files:a.record(a.string(),a.string()).optional()});function J(t){if(t.length!==0)return t.reduce((e,r)=>r.epochNumber>e.epochNumber?r:e)}var j="\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 L(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
+ `)}function ke(t,e){if(t.length===0)return["\u{1F4C1} **No projects found**","","Use `create_project` to create one."].join(`
31
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(`
32
+ `)}function Se(t){let e=["\u{1F4CB} **New Project Preview**",j,"",` 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("",j,"\u26A0\uFE0F Confirm to proceed."),e.join(`
33
+ `)}function xe(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
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 current Torque authentication status by verifying the stored token. ALWAYS call at session start before 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 API keys for the Torque event ingestion pipeline (used in `x-api-key` header for custom events). Keys shown masked \u2014 manage or revoke via developer dashboard. IMPORTANT: Display the full list and include the dashboard link for key management and 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. Key shown once \u2014 cannot be retrieved later. Used in `x-api-key` header for ingester requests. IMPORTANT: Include the developer dashboard link for key management and 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 Torque project (workspace for incentives, events, analytics). Automatically becomes active. IMPORTANT: Ask the user's project type before creating: (1) Token team \u2014 incentivize token activity (swaps, holds, bonding curve). Collect token mint \u2192 `defaultTokenAddress`. (2) Protocol/product \u2014 incentivize on-chain program interactions. Collect program address \u2192 `defaultProgramAddress`; ask if they also have a token. These defaults avoid re-asking for addresses on every incentive.",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 Torque projects and which is currently active. AUTOMATICALLY call after authentication succeeds. Also use when switching projects or finding an ID for `set_active_project`. If no project is active, suggest selecting one \u2014 most tools require it. IMPORTANT: Always display the full project list.",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 \u2014 all project-scoped tools (events, incentives, analytics, context) operate on it. IMPORTANT: MUST call `list_projects` first and show the full list \u2014 never ask 'which project?' without displaying options.",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}
35
+ `)}function Pe(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 u=e?.get(s.projectId);return u?`**${u}**`:`\`${s.projectId}\``}).join(", "),r.push(`| **${n.name}** \`${n.eventName}\` | \`${n.id}\` | ${i} | ${o} |`)}return r.join(`
37
+ `)}function De(t){let e=t.fields,r=["\u{1F4CB} **New Custom Event Preview**",j,"",` 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.","",j,"\u26A0\uFE0F Confirm to proceed."),r.join(`
38
+ `)}function Ce(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 $e(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",u=new Date(n.createdAt).toLocaleDateString();r.push(`| ${i} | \`${n.key}\` | ${o} | ${s} | ${u} |`)}return r.push(""),r.push(`Manage API keys and view ingestion logs at [${e}/developer](${e}/developer).`),r.join(`
41
+ `)}function Ne(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 je(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="\u2014",o=J(r.configs??[]);if(o){let s=Math.round((o.evalEnd.getTime()-o.evalStart.getTime())/864e5);s>0&&(i=`${s}-day epochs`)}e.push(`| **${r.name}** | \`${r.id}\` | ${r.type??"\u2014"} | ${i} | ${n} |`)}return e.join(`
44
+ `)}function Le(t){let e=t.isActive?"\u2705 active":"\u23F8 inactive",r=[`\u{1F4CA} **${t.name||"Incentive Detail"}**`,"","| | |","|---|---|",`| **Status** | ${e} |`,`| **ID** | \`${t.id}\` |`];if(t.type&&r.push(`| **Type** | ${t.type} |`),t.maxIterations&&r.push(`| **Max Epochs** | ${t.maxIterations} |`),t.startDate){let o=t.startDate instanceof Date?t.startDate.toISOString().split("T")[0]:String(t.startDate);r.push(`| **Start** | ${o} |`)}let n=J(t.configs??[]);if(n){let o=Math.round((n.evalEnd.getTime()-n.evalStart.getTime())/864e5);o>0&&r.push(`| **Eval Period** | ${o} day(s) |`);let s=n.distributionConfig;s.distributionType&&r.push(`| **Distribution** | ${s.distributionType} |`),s.emissionType&&r.push(`| **Emission** | ${s.emissionType} |`),"tokenAddress"in s&&s.tokenAddress&&r.push(`| **Token** | \`${s.tokenAddress}\`${" tokenDecimals"in s?` (decimals: ${s.tokenDecimals})`:""} |`),s.totalFundAmount!==void 0&&r.push(`| **Total Fund** | ${s.totalFundAmount} |`),"customFormula"in s&&s.customFormula&&r.push(`| **Formula** | \`${s.customFormula}\` |`),s.maxPerParticipant&&r.push(`| **Max/Participant** | ${s.maxPerParticipant} |`);let u=Math.round(s.claimWindowDuration/86400);if(u>0&&r.push(`| **Claim Window** | ${u} day(s) |`),"prizeBuckets"in s&&s.prizeBuckets){r.push(""),r.push("### Prize Tiers"),r.push(""),r.push("| Prize | Winners |"),r.push("|-------|---------|");for(let l of s.prizeBuckets)r.push(`| ${l.amount} | ${l.count} |`);if("selectionLogic"in s){let l=s.selectionLogic==="EQUAL_CHANCES"?"Equal chances (1 ticket per user)":"Weighted by metric (more activity = more tickets)";r.push(""),r.push(`**Selection:** ${l}`)}}let c=n.offerMetadata;c.description&&(r.push(""),r.push(`${c.description}`))}let i=t.configs??[];if(i.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 o of i){let s=o.epochNumber??"\u2014",u=o.status??"\u2014",c=o.evalStart?new Date(o.evalStart).toISOString().split("T")[0]:"\u2014",l=o.evalEnd?new Date(o.evalEnd).toISOString().split("T")[0]:"\u2014",d=o.claimStart?new Date(o.claimStart).toISOString().split("T")[0]:"\u2014",p=o.claimEnd?new Date(o.claimEnd).toISOString().split("T")[0]:"\u2014",f=o.sqlQueryId?`\`${o.sqlQueryId}\``:"\u2014",m=o.offerId?`\`${o.offerId}\``:"\u2014";r.push(`| ${s} | ${u} | ${c} \u2192 ${l} | ${d} \u2192 ${p} | ${m} | ${f} |`)}}return r.join(`
45
+ `)}function qe(t){let e={leaderboard:"Leaderboard Competition",rebate:"Rebate",raffle:"Raffle",direct:"Direct Distribution"},r=t.type,n=["\u{1F4CB} **New Incentive Preview**",j,"",` 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,u)=>s+u.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,u)=>s+u.amount*u.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("",j,"\u26A0\uFE0F Confirm to proceed."),n.join(`
46
+ `)}var Oe={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:tr.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=rr().decode(o),u=await e.auth.loginWithStatement(n.address,e.config.signStatement,s);return await e.auth.setToken(u,n.address),{content:[{type:"text",text:`\u2705 Successfully authenticated with wallet \`${n.address}\`.`}]}}return{content:[{type:"text",text:L(e.config.platformUrl)}],isError:!0}}catch(r){return{content:[{type:"text",text:`Authentication failed: ${r instanceof Error?r.message:String(r)}`}],isError:!0}}}},Ue={name:"check_auth_status",description:"Check current Torque authentication status by verifying the stored token. ALWAYS call at session start before 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
+ `+L(e.config.platformUrl)}]}:{content:[{type:"text",text:L(e.config.platformUrl)}]}},Me={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 nr}from"zod";function y(t){return t.auth.getToken()?null:{content:[{type:"text",text:L(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 Fe={name:"list_api_keys",description:"List API keys for the Torque event ingestion pipeline (used in `x-api-key` header for custom events). Keys shown masked \u2014 manage or revoke via developer dashboard. IMPORTANT: Display the full list and include the dashboard link for key management and ingestion logs.",requires:["auth"],inputSchema:{},handler:async(t,e)=>{let r=y(e);if(r)return r;try{let n=await e.apiRequest("/api-keys"),i=I(ye,n);return{content:[{type:"text",text:$e(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}}}},Qe={name:"create_api_key",description:"Create a new API key for sending custom events to the Torque ingestion pipeline. Key shown once \u2014 cannot be retrieved later. Used in `x-api-key` header for ingester requests. IMPORTANT: Include the developer dashboard link for key management and ingestion logs.",requires:["auth"],inputSchema:{name:nr.string().optional().describe("Optional display name for the API key. If omitted, the API generates a default name.")},handler:async(t,e)=>{let r=y(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}),o=v(ve,i);return{content:[{type:"text",text:Ne(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 x}from"zod";var or=.05,Be={name:"create_project",description:"Create a new Torque project (workspace for incentives, events, analytics). Automatically becomes active. IMPORTANT: Ask the user's project type before creating: (1) Token team \u2014 incentivize token activity (swaps, holds, bonding curve). Collect token mint \u2192 `defaultTokenAddress`. (2) Protocol/product \u2014 incentivize on-chain program interactions. Collect program address \u2192 `defaultProgramAddress`; ask if they also have a token. These defaults avoid re-asking for addresses on every incentive.",requires:["auth"],inputSchema:{name:x.string().describe("Project name"),description:x.string().optional().describe("Project description"),image:x.string().optional().describe("Project image URL"),defaultTokenAddress:x.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:x.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:x.array(x.string()).optional().describe("Wallet addresses to add as project admins"),confirmed:x.boolean().optional().describe("Set to true to confirm and execute the creation. Omit to preview first.")},handler:async(t,e)=>{let r=y(e);if(r)return r;if(!t.confirmed)return{content:[{type:"text",text:Se(t)}]};try{let n={name:t.name,protocolFee:or};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 s=t.adminWallets;n.roles=s.map(u=>({userPubKey:u,role:"ADMIN"}))}let i=await e.apiRequest("/projects",{method:"POST",body:n}),o=v($,i);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}}}},We={name:"list_projects",description:"List all Torque projects and which is currently active. AUTOMATICALLY call after authentication succeeds. Also use when switching projects or finding an ID for `set_active_project`. If no project is active, suggest selecting one \u2014 most tools require it. IMPORTANT: Always display the full project list.",requires:["auth"],inputSchema:{},handler:async(t,e)=>{let r=y(e);if(r)return r;try{let n=await e.apiRequest("/projects"),i=I($,n);return{content:[{type:"text",text:ke(i,e.activeProject?.id)}]}}catch(n){return{content:[{type:"text",text:`Failed to list projects: ${n instanceof Error?n.message:String(n)}`}],isError:!0}}}},Ge={name:"set_active_project",description:"Set the active project \u2014 all project-scoped tools (events, incentives, analytics, context) operate on it. IMPORTANT: MUST call `list_projects` first and show the full list \u2014 never ask 'which project?' without displaying options.",requires:["auth"],inputSchema:{projectId:x.string().describe("The project ID to set as active")},handler:async(t,e)=>{let r=y(e);if(r)return r;try{let n=t.projectId,i=await e.apiRequest(`/projects/${n}`),o=v($,i);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 g}from"zod";var He="tokenswap_partitioned",Ve="launchlabtrade_partitioned",Ke="mplxgenesislaunchpooldepositwithdraw_partitioned",G="tokenbalance_partitioned",ir=[/'/,/"/,/;/,/--/,/\/\*/,/\\/];function D(t,e){for(let r of ir)if(r.test(t))throw new Error(`Invalid ${e}: contains forbidden characters. Value must not contain quotes, semicolons, comments, or backslashes.`);return t}function H(t){return{content:[{type:"text",text:`${t.description}
49
49
 
50
50
  \`\`\`sql
51
51
  ${t.sql}
52
52
  \`\`\`
53
53
 
54
- Use this query with \`create_recurring_incentive\` to set up the incentive.`},{type:"text",text:JSON.stringify({sql:t.sql,...t.metadata})}]}}function B(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(),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=B(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. Call `get_ai_context` first for project domain context. Three layers: (1) SQL query from `generate_incentive_query` (source: swap, bonding_curve, hold, custom_event, idl_instruction) or raw SQL via `sqlQuery`, (2) type: leaderboard (rank-based), rebate (%-back), raffle (random winners), or direct (fixed allocations), (3) rewards (tokens/SOL, amounts, frequency). IMPORTANT for leaderboard: Ask how rewards should be distributed before creating. Patterns: fixed rank prizes (1st=10000, 2nd=5000), tiered ranges (top 50 split pool), proportional splits. Build `customFormula` from their intent \u2014 never default to `N` (pays raw metric values). IMPORTANT for raffle: Ask about ticket distribution \u2014 WEIGHTED_BY_METRIC (default, more activity = more entries) vs EQUAL_CHANCES (1 ticket per user). Results must be sorted highest-first for weighted raffles. See `torque://incentive-types` for formula variables, type details, and examples. Pipelines \u2014 custom events: list_project_events \u2192 generate_incentive_query (source: "custom_event") \u2192 create_recurring_incentive. IDL instructions: 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:B(t.allocations)}),{content:[{type:"text",text:he(u)}]}}let c=G[i],a=e.activeProject.id;try{if(i==="direct"&&t.allocations){let T=B(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 \u2014 name, type, status, epoch duration. Use when the user asks about incentives, rewards, campaigns, leaderboards, or wants to see what they have. Provides IDs needed for `get_recurring_incentive` and `get_recurring_incentive_analytics`. IMPORTANT: Always display the full list.",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 when the user asks about performance, stats, or wants to evaluate results. Call `get_ai_context` for domain context when interpreting. Use `list_recurring_incentives` for 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). Does NOT require an active project \u2014 events are project-independent, attachable via `attach_custom_event`. If called during an existing workflow (e.g. incentive creation), continue that workflow after. RULES: (1) Never include wallet/pubkey as a field \u2014 `userPubkey` is a top-level property, not a schema field. (2) Confirm `eventName` with user before creating \u2014 suggest snake_case of display name (e.g. 'Sign Up' \u2192 'sign_up'). (3) After creation: offer to attach to active project if exists, otherwise ask \u2014 use `list_projects` \u2192 `set_active_project` \u2192 `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?`
54
+ Use this query with \`create_recurring_incentive\` to set up the incentive.`},{type:"text",text:JSON.stringify({sql:t.sql,...t.metadata})}]}}function X(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(),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 Z={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 sr(t,e){if(!(t in Z))return`Unknown type "${t}". Valid types: leaderboard, rebate, raffle, direct. See the torque://incentive-types resource for details.`;let r=Z[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 ar(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 u=e.rebatePercentage;o.customFormula=s||`VALUE * ${u/100}`}else if(t.name==="raffle"){let u=e.raffleBuckets;o.prizeBuckets=u,o.totalFundAmount=u.reduce((l,d)=>l+d.amount*d.count,0);let c=e.raffleWeighting;o.selectionLogic=c==="EQUAL_CHANCES"?"EQUAL_CHANCES":"WEIGHTED_BY_METRIC",o.allowDuplicateWinners=!1}else if(t.name==="direct"){let u=X(e.allocations),c=e.customFormula;o.customFormula=c||"N",o.totalFundAmount=u.reduce((l,d)=>l+d.amount,0)}return o}var ze={DAILY:1,WEEKLY:7,MONTHLY:30};function cr(t){if(t.evalDurationDays)return t.evalDurationDays;let e=t.interval;return e&&e in ze?ze[e]:null}var Ye={name:"create_recurring_incentive",requires:["auth","activeProject"],description:'Create a recurring incentive (reward program) for the active project. Call `get_ai_context` first for project domain context. Three layers: (1) SQL query from `generate_incentive_query` (source: swap, bonding_curve, hold, custom_event, idl_instruction) or raw SQL via `sqlQuery`, (2) type: leaderboard (rank-based), rebate (%-back), raffle (random winners), or direct (fixed allocations), (3) rewards (tokens/SOL, amounts, frequency). IMPORTANT for leaderboard: Ask how rewards should be distributed before creating. Patterns: fixed rank prizes (1st=10000, 2nd=5000), tiered ranges (top 50 split pool), proportional splits. Build `customFormula` from their intent \u2014 never default to `N` (pays raw metric values). IMPORTANT for raffle: Ask about ticket distribution \u2014 WEIGHTED_BY_METRIC (default, more activity = more entries) vs EQUAL_CHANCES (1 ticket per user). Results must be sorted highest-first for weighted raffles. See `torque://incentive-types` for formula variables, type details, and examples. Pipelines \u2014 custom events: list_project_events \u2192 generate_incentive_query (source: "custom_event") \u2192 create_recurring_incentive. IDL instructions: 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=y(e);if(r)return r;let n=E(e);if(n)return n;let i=t.type,o=sr(i,t);if(o)return{content:[{type:"text",text:o}],isError:!0};let s=cr(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 l={...t};return i==="direct"&&t.allocations&&(l={...l,allocations:X(t.allocations)}),{content:[{type:"text",text:qe(l)}]}}let u=Z[i],c=e.activeProject.id;try{if(i==="direct"&&t.allocations){let M=X(t.allocations).map(ce=>`('${D(ce.address,"address")}', ${ce.amount})`).join(", ");t.sqlQuery=`SELECT address, value FROM (VALUES ${M}) AS t(address, value)`}let l=await e.apiRequest(`/project/${c}/query`,{method:"POST",body:{name:`${t.name} - Query - ${Date.now()}`,query:t.sqlQuery||"SELECT 1",paramMap:{startDate:"DATE",endDate:"DATE"}}}),d=v(be,l),p=ar(u,t),f={title:t.name,primitive:u.primitive};t.description&&(f.description=t.description);let m={name:t.name,type:u.type,evalDurationDays:s,startDate:t.startDate,sqlQueryId:d.id,distributionConfig:p,offerMetadata:f,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 h=await e.apiRequest(`/project/${c}/recurring-offer`,{method:"POST",body:m}),w=v(P,h);return{content:[{type:"text",text:`Recurring incentive "${w.name||t.name}" created successfully (ID: ${w.id}). Fund it here: ${e.config.platformUrl}/${c}/incentives`}]}}catch(l){return{content:[{type:"text",text:`Failed to create recurring incentive: ${l instanceof Error?l.message:String(l)}`}],isError:!0}}}},Je={name:"list_recurring_incentives",requires:["auth","activeProject"],description:"List all recurring incentives (reward programs) for the active project \u2014 name, type, status, epoch duration. Use when the user asks about incentives, rewards, campaigns, leaderboards, or wants to see what they have. Provides IDs needed for `get_recurring_incentive` and `get_recurring_incentive_analytics`. IMPORTANT: Always display the full list.",inputSchema:{},handler:async(t,e)=>{let r=y(e);if(r)return r;let n=E(e);if(n)return n;let i=e.activeProject.id;try{let o=await e.apiRequest(`/project/${i}/recurring-offer`),s=I(P,o);return{content:[{type:"text",text:je(s)}]}}catch(o){return{content:[{type:"text",text:`Failed to list recurring incentives: ${o instanceof Error?o.message:String(o)}`}],isError:!0}}}},Xe={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=y(e);if(r)return r;let n=E(e);if(n)return n;let i=e.activeProject.id,o=t.recurringOfferId;try{let s=await e.apiRequest(`/project/${i}/recurring-offer/${o}`),u=v(P,s);return{content:[{type:"text",text:Le(u)}]}}catch(s){return{content:[{type:"text",text:`Failed to get recurring incentive: ${s instanceof Error?s.message:String(s)}`}],isError:!0}}}},Ze={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 when the user asks about performance, stats, or wants to evaluate results. Call `get_ai_context` for domain context when interpreting. Use `list_recurring_incentives` for the incentive ID.",inputSchema:{recurringOfferId:g.string().describe("The recurring incentive ID")},handler:async(t,e)=>{let r=y(e);if(r)return r;let n=E(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`),u=v(g.record(g.unknown()),s);return{content:[{type:"text",text:JSON.stringify(u,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 S}from"zod";var et={name:"create_custom_event",description:"Create a custom event schema for tracking off-chain user activity (e.g. purchases, game actions, signups). Does NOT require an active project \u2014 events are project-independent, attachable via `attach_custom_event`. If called during an existing workflow (e.g. incentive creation), continue that workflow after. RULES: (1) Never include wallet/pubkey as a field \u2014 `userPubkey` is a top-level property, not a schema field. (2) Confirm `eventName` with user before creating \u2014 suggest snake_case of display name (e.g. 'Sign Up' \u2192 'sign_up'). (3) After creation: offer to attach to active project if exists, otherwise ask \u2014 use `list_projects` \u2192 `set_active_project` \u2192 `attach_custom_event`.",requires:["auth"],inputSchema:{eventName:S.string().describe("Identifier for the custom event"),name:S.string().describe("Display name for the custom event"),confirmed:S.boolean().optional().describe("Set to true to confirm and execute the creation. Omit to preview first."),fields:S.array(S.object({fieldName:S.string().describe("Field name"),type:S.enum(["string","boolean","number"]).describe("Field data type"),label:S.string().optional().describe("Display label for the field"),description:S.string().optional().describe("Description of what this field represents")})).describe("Fields that define the event schema")},handler:async(t,e)=>{let r=y(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:De(t)}]};try{let i=await e.apiRequest("/events",{method:"POST",body:{eventName:t.eventName,name:t.name,fields:n}}),o=v(N,i),s=Ce(e.config.ingesterUrl,t.eventName,n,e.config.platformUrl),u=e.activeProject,c=u?`
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: ${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 on the active project (includes events from all admins). Shows schemas, fields, and query-readiness (events need ingestion before use in queries). Use to find events for `generate_incentive_query` (source: "custom_event"). For your events across all projects, use `list_custom_events`. IMPORTANT: Display the full event list (preferably as table) so the user can select.',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}}}},Ne={name:"list_custom_events",description:"List all your custom events across all projects. Shows schemas, fields, and attached projects. Use to find events for attaching via `attach_custom_event`. For events on a specific project (including other admins'), use `list_project_events`. IMPORTANT: Display the full event list (preferably as table) so the user can select.",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}}}},je={name:"attach_custom_event",description:'Attach a custom event to the active project for use in incentives. Event must exist \u2014 use `list_custom_events` to find one or `create_custom_event` to make one. After attaching, event needs ingestion data before it\'s query-ready for `generate_incentive_query` (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
- ${d.join(`,
57
+ \u{1F4A1} Would you like to attach this event to project "${u.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
+ ${s}${c}`},{type:"text",text:JSON.stringify({id:o.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}}}},tt={name:"list_project_events",description:'List custom events on the active project (includes events from all admins). Shows schemas, fields, and query-readiness (events need ingestion before use in queries). Use to find events for `generate_incentive_query` (source: "custom_event"). For your events across all projects, use `list_custom_events`. IMPORTANT: Display the full event list (preferably as table) so the user can select.',requires:["auth","activeProject"],inputSchema:{},handler:async(t,e)=>{let r=y(e);if(r)return r;let n=E(e);if(n)return n;let i=e.activeProject.id;try{let o=await e.apiRequest(`/projects/${i}/events`),s=I(N,o);return{content:[{type:"text",text:xe(s)}]}}catch(o){return{content:[{type:"text",text:`Failed to list project events: ${o instanceof Error?o.message:String(o)}`}],isError:!0}}}},rt={name:"list_custom_events",description:"List all your custom events across all projects. Shows schemas, fields, and attached projects. Use to find events for attaching via `attach_custom_event`. For events on a specific project (including other admins'), use `list_project_events`. IMPORTANT: Display the full event list (preferably as table) so the user can select.",requires:["auth"],inputSchema:{},handler:async(t,e)=>{let r=y(e);if(r)return r;try{let[n,i]=await Promise.all([e.apiRequest("/events"),e.apiRequest("/projects").catch(()=>({status:"SUCCESS",data:[]}))]),o=I(Ae,n),s=I($,i),u=new Map;for(let c of s)u.set(c.id,c.name);return{content:[{type:"text",text:Pe(o,u)}]}}catch(n){return{content:[{type:"text",text:`Failed to list custom events: ${n instanceof Error?n.message:String(n)}`}],isError:!0}}}},nt={name:"attach_custom_event",description:'Attach a custom event to the active project for use in incentives. Event must exist \u2014 use `list_custom_events` to find one or `create_custom_event` to make one. After attaching, event needs ingestion data before it\'s query-ready for `generate_incentive_query` (source: "custom_event").',requires:["auth","activeProject"],inputSchema:{customEventId:S.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=y(e);if(r)return r;let n=E(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 u=s instanceof Error?s.message:String(s);return u.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: ${u}`}],isError:!0}}}};import{z as A}from"zod";var ee="customevent_partitioned",ur=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 te(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function lr(t){return t.replace(/\\/g,"\\\\").replace(/'/g,"''")}function ot(t,e){let r=[],n=new Map;for(let s of e.fields){let u=e.columnMapping[s.fieldName];u&&n.set(s.fieldName,{column:u,type:s.type})}let i=Array.from(n.keys()).sort((s,u)=>u.length-s.length),o=t;for(let s of i)if(new RegExp(`\\b${te(s)}\\b`,"g").test(o)){let{column:c,type:l}=n.get(s);r.push({fieldName:s,column:c,type:l}),o=o.replace(new RegExp(`\\b${te(s)}\\b`,"g"),`"${c}"`)}return{resolved:o,referencedFields:r}}var dr=new Set(["SUM","AVG"]);function it(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 u of e.fields)new RegExp(`\\b${te(u.fieldName)}\\b`).test(s)&&dr.has(o)&&u.type!=="number"&&r.push(`Cannot use ${o}() on "${u.fieldName}" \u2014 it is a ${u.type} field, not a NUMBER.`)}return r}function st(t,e){let r=[],n=[],i=it(e.valueExpression,t);r.push(...i);let{resolved:o}=ot(e.valueExpression,t),s=/^\s*[\d.]+\s*$/.test(e.valueExpression),u=/\bCOUNT\s*\(\s*\*\s*\)/i.test(e.valueExpression);!s&&!u&&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(w=>w.fieldName).join(", ")}`));let c=[];if(e.filters)for(let h of e.filters){let w=it(h,t);r.push(...w);let{resolved:R}=ot(h,t);c.push(R)}if(r.length>0)return{errors:r};let l=e.groupByPubkey!==!1,d=e.orderBy||"DESC",p=[' "userPubkey" AS address',` ${o} AS value`],f=[`"eventId" = '${lr(t.id)}'`,'"receivedAt" BETWEEN {{startDate}} AND {{endDate}}',...c],m=`SELECT
59
+ ${p.join(`,
60
60
  `)}
61
- FROM ${W}
61
+ FROM ${ee}
62
62
  WHERE ${f.join(`
63
- AND `)}`;return u&&(p+=`
64
- GROUP BY "userPubkey"`),p+=`
65
- ORDER BY value ${l}`,e.limit!==void 0&&e.limit>0&&(p+=`
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 Nt(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(Nt(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",jt=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 Qe(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 Be(t,e){let r=[],n=[],i=Qe(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=Qe(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
- ${d.join(`,
63
+ AND `)}`;return l&&(m+=`
64
+ GROUP BY "userPubkey"`),m+=`
65
+ ORDER BY value ${d}`,e.limit!==void 0&&e.limit>0&&(m+=`
66
+ LIMIT ${e.limit}`),{sql:m,warnings:n}}var pr=/\b(INSERT|UPDATE|DELETE|DROP|ALTER|TRUNCATE|CREATE)\b/i;function at(t,e){let r=[],n=[];/^\s*SELECT\b/i.test(t)||r.push("Query must be a SELECT statement."),pr.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(ee)||r.push(`Query must reference the "${ee}" 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;)ur.has(o[1])||r.push(`Referenced column "${o[1]}" does not exist in the table schema.`);let s=new Map;for(let c of e.fields){let l=e.columnMapping[c.fieldName];l&&s.set(l,c.type)}let u=/\b(SUM|AVG)\s*\(\s*"?([\w]+)"?\s*\)/gi;for(;(o=u.exec(t))!==null;){let c=o[1].toUpperCase(),l=o[2],d=s.get(l);d&&d!=="number"&&r.push(`Cannot use ${c}() on column "${l}" \u2014 it maps to a ${d} field.`)}return{valid:r.length===0,errors:r,warnings:n}}function mr(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 u=i[1],c=i[2];c?n.push(`Calculates ${c}x the total "${u}" per user from "${t}" events`):n.push(`Calculates the total "${u}" 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 u=e.filters.map(c=>`"${c}"`);n.push(`Only includes events where ${u.join(" and ")}`)}if(e.limit){let u=e.orderBy==="ASC"?"bottom":"top";n.push(`Returns the ${u} ${e.limit} users`)}return n.join(". ")+"."}function fr(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 hr(t,e){let r=t.name.toLowerCase(),n=fr(t),i=e?.valueExpression??"",o=i.match(/^SUM\((\w+)\)/i),s=i.match(/^COUNT\(\*\)$/i),u;s?u=`number of ${r} events`:o?u=`total ${o[1]}`:i==="1"?u="participation":u="calculated value";let c=[`Each user's **${u}** 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 l=o[1];n==="commerce"?(c.push(`- "Give shoppers cashback \u2014 5% of their total ${l}"`),c.push('- "Leaderboard: top 50 spenders share the reward pool"'),c.push(`- "Loyalty reward proportional to ${l}, capped at 500 tokens per customer"`),c.push('- "Everyone who spent over $10 gets an equal share of the pool"')):n==="defi"?(c.push(`- "Rebate based on trading ${l} \u2014 give back 2% of their volume"`),c.push(`- "Top 100 traders by ${l} share the reward pool"`),c.push(`- "Proportional to ${l} but cap rewards at 10,000 tokens per wallet"`),c.push(`- "Liquidity mining: split the pool based on each user's share of total ${l}"`)):n==="gaming"?(c.push(`- "Top players by ${l} win prizes \u2014 leaderboard style"`),c.push(`- "Reward proportional to ${l}, with diminishing returns for top ranks"`),c.push(`- "Bonus for every 100 ${l} earned"`),c.push(`- "All players with ${l} above 0 split the pool equally"`)):n==="social"?(c.push(`- "Reward based on engagement \u2014 proportional to their ${l}"`),c.push(`- "Top 100 contributors by ${l} share the pool"`),c.push(`- "Everyone who contributed gets a share, weighted by ${l}"`)):(c.push(`- "Give each user 5% of their ${l} back as a reward"`),c.push(`- "Top 50 users by ${l} share the reward pool"`),c.push(`- "Reward proportional to ${l}, but cap at 1000 tokens per user"`),c.push(`- "Split the pool equally among everyone with ${l} above 0"`))}else s?n==="commerce"?(c.push('- "Reward repeat customers \u2014 10 tokens per purchase"'),c.push('- "Top 100 most frequent shoppers share the pool"'),c.push('- "Everyone who made at least 3 purchases gets an equal share"')):n==="defi"?(c.push('- "Reward active traders \u2014 bonus for each transaction"'),c.push('- "Top 100 most active wallets share the pool"'),c.push('- "Everyone who traded at least once gets an equal share"')):n==="gaming"?(c.push('- "Play-to-earn: reward for every match completed"'),c.push('- "Most active players share the prize pool"'),c.push('- "Everyone who played at least 5 matches enters a raffle"')):n==="social"?(c.push('- "Reward for every post or share \u2014 more activity, more reward"'),c.push('- "Top referrers share the pool"'),c.push('- "Everyone who invited at least one friend gets tokens"')):(c.push(`- "Reward based on how many ${r} events they triggered"`),c.push(`- "5 tokens for every ${r} event"`),c.push('- "Top 100 most active users share the pool"')):i==="1"?n==="commerce"?(c.push('- "Everyone who made a purchase gets an equal reward"'),c.push('- "Raffle among all customers \u2014 random winners get prizes"')):n==="defi"?(c.push('- "Airdrop to everyone who interacted with the protocol"'),c.push('- "Equal distribution among all active wallets"')):n==="gaming"?(c.push('- "Participation reward \u2014 everyone who played gets tokens"'),c.push('- "Raffle among all players for bonus prizes"')):n==="social"?(c.push('- "Reward everyone who participated equally"'),c.push('- "Raffle among all engaged users"')):(c.push('- "Split the pool equally among all participants"'),c.push('- "Raffle among all qualifying users"')):(c.push(`- "Reward proportional to their ${u}"`),c.push('- "Split the pool equally among qualifying users"'),c.push('- "Top performers get more, with a cap per user"'));return c.join(`
67
+ `)}function ct(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:** ${mr(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(hr(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 re="mapped_custom_instruction",gr=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 ne(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function yr(t){return t.replace(/\\/g,"\\\\").replace(/'/g,"''")}function ut(t,e){let r=[],n=new Map;for(let[s,u]of Object.entries(e.accountMapping))n.set(s,{column:u,type:"string"});for(let s of e.instruction){let u=e.fieldMapping[s.fieldName];u&&n.set(s.fieldName,{column:u,type:s.type})}let i=Array.from(n.keys()).sort((s,u)=>u.length-s.length),o=t;for(let s of i)if(new RegExp(`\\b${ne(s)}\\b`,"g").test(o)){let{column:c,type:l}=n.get(s);r.push({fieldName:s,column:c,type:l}),o=o.replace(new RegExp(`\\b${ne(s)}\\b`,"g"),`"${c}"`)}return{resolved:o,referencedFields:r}}var vr=new Set(["SUM","AVG"]);function lt(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 u of e)new RegExp(`\\b${ne(u.fieldName)}\\b`).test(s)&&vr.has(o)&&u.type!=="number"&&r.push(`Cannot use ${o}() on "${u.fieldName}" \u2014 it is a ${u.type} field, not a NUMBER.`)}return r}function dt(t,e){let r=[],n=[],i=lt(e.valueExpression,t.instruction);r.push(...i);let{resolved:o}=ut(e.valueExpression,t),s=/^\s*[\d.]+\s*$/.test(e.valueExpression),u=/\bCOUNT\s*\(\s*\*\s*\)/i.test(e.valueExpression);!s&&!u&&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(w=>w.fieldName).join(", ")}`));let c=[];if(e.filters)for(let h of e.filters){let w=lt(h,t.instruction);r.push(...w);let{resolved:R}=ut(h,t);c.push(R)}if(r.length>0)return{errors:r};let l=e.groupByFeePayer!==!1,d=e.orderBy||"DESC",p=[' "feePayer" AS address',` ${o} AS value`],f=[`"instructionId" = '${yr(t.id)}'`,'"receivedAt" BETWEEN {{startDate}} AND {{endDate}}',...c],m=`SELECT
69
+ ${p.join(`,
70
70
  `)}
71
- FROM ${V}
71
+ FROM ${re}
72
72
  WHERE ${f.join(`
73
- AND `)}`;return u&&(p+=`
74
- GROUP BY "feePayer"`),p+=`
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;)jt.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 Qt(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 Bt(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:Qt,count:Bt},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(`
73
+ AND `)}`;return l&&(m+=`
74
+ GROUP BY "feePayer"`),m+=`
75
+ ORDER BY value ${d}`,{sql:m,warnings:n}}var br=/\b(INSERT|UPDATE|DELETE|DROP|ALTER|TRUNCATE|CREATE)\b/i;function pt(t,e){let r=[],n=[];/^\s*SELECT\b/i.test(t)||r.push("Query must be a SELECT statement."),br.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(re)||r.push(`Query must reference the "${re}" 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;)gr.has(o[1])||r.push(`Referenced column "${o[1]}" does not exist in the table schema.`);let s=new Map;for(let c of e.instruction){let l=e.fieldMapping[c.fieldName];l&&s.set(l,c.type)}let u=/\b(SUM|AVG)\s*\(\s*"?([\w]+)"?\s*\)/gi;for(;(o=u.exec(t))!==null;){let c=o[1].toUpperCase(),l=o[2],d=s.get(l);d&&d!=="number"&&r.push(`Cannot use ${c}() on column "${l}" \u2014 it maps to a ${d} field.`)}return{valid:r.length===0,errors:r,warnings:n}}function wr(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 u=i[1],c=i[2];c?n.push(`Calculates ${c}x the total "${u}" per user from "${t}" instructions`):n.push(`Calculates the total "${u}" 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 u=e.filters.map(c=>`"${c}"`);n.push(`Only includes instructions where ${u.join(" and ")}`)}return n.join(". ")+"."}function mt(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:** ${wr(e.instructionName,i)}`)),s.push("","```sql",t,"```","","Field Mapping:");for(let c of e.instruction){let l=e.fieldMapping[c.fieldName];s.push(` ${c.fieldName} (${c.type}) \u2192 ${l}`)}let u=Object.entries(e.accountMapping);if(u.length>0){s.push(""),s.push("Account Mapping:");for(let[c,l]of u)s.push(` ${c} \u2192 ${l}`)}if(n.length>0){s.push(""),s.push("\u26A0\uFE0F Warnings:");for(let c of n)s.push(` - ${c}`)}return s.push(""),s.push("**Next step: How should rewards be calculated?**"),s.push(""),s.push(Tr(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 Tr(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 u=[`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 c=i[1];u.push(`- "Rebate based on ${c} \u2014 give back 2% of their volume"`),u.push(`- "Top 100 users by ${c} share the reward pool"`),u.push(`- "Proportional to ${c} but cap rewards at 10,000 tokens per wallet"`),u.push(`- "Split the pool based on each user's share of total ${c}"`)}else o?(u.push(`- "Reward active users \u2014 bonus for each ${r} executed"`),u.push('- "Top 100 most active wallets share the pool"'),u.push(`- "Everyone who executed at least one ${r} gets an equal share"`)):n==="1"?(u.push('- "Airdrop to everyone who interacted with the program"'),u.push('- "Equal distribution among all active wallets"')):(u.push(`- "Reward proportional to their ${s}"`),u.push('- "Split the pool equally among qualifying users"'),u.push('- "Top performers get more, with a cap per user"'));return u.join(`
77
+ `)}var ft={raydium_launchlab:{table:Ve,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 Er(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 ${G}`,` WHERE "mint" = '${t}'`,` AND "amount" >= ${e}`,' AND "createdAt" BETWEEN {{startDate}} AND {{endDate}}',") sub","WHERE rn = 1","ORDER BY value DESC"].join(`
78
+ `)}function Ar(t,e){return['SELECT "owner" AS address, COUNT(DISTINCT DATE("createdAt")) AS value',`FROM ${G}`,`WHERE "mint" = '${t}'`,` AND "amount" >= ${e}`,' AND "createdAt" BETWEEN {{startDate}} AND {{endDate}}','GROUP BY "owner"',"ORDER BY value DESC"].join(`
79
+ `)}function Rr(t,e){return['SELECT "owner" AS address, COUNT(*) AS value',`FROM ${G}`,`WHERE "mint" = '${t}'`,` AND "amount" >= ${e}`,' AND "createdAt" BETWEEN {{startDate}} AND {{endDate}}','GROUP BY "owner"',"ORDER BY value DESC"].join(`
80
+ `)}var _r={balance:Er,duration:Ar,count:Rr},Ir={balance:"latest qualifying token balance per wallet",duration:"number of distinct days with qualifying balance",count:"number of qualifying balance records"},oe={swap:["volume","count"],bonding_curve:["volume","count"],hold:["balance","duration","count"]};function b(t){return{isError:!0,content:[{type:"text",text:`\u274C ${t}`}]}}function kr(t){let e=t.tokenMint;if(!e)return b("tokenMint is required for swap source.");let r=t.direction??"buy",n=t.minUsdAmount,i=t.protocol,o=t.measure;try{D(e,"tokenMint"),i&&D(i,"protocol")}catch(p){return b(p.message)}let s;r==="buy"?s=`"tokenOut" = '${e}'`:r==="sell"?s=`"tokenIn" = '${e}'`:s=`("tokenIn" = '${e}' OR "tokenOut" = '${e}')`;let u=o==="volume"?'SUM("usdAmount") AS value':"COUNT(*) AS value",c=[s,'"receivedAt" BETWEEN {{startDate}} AND {{endDate}}'];n!==void 0&&c.push(`"usdAmount" >= ${n}`),i&&c.push(`"protocol" = '${i}'`);let l=[`SELECT "feePayer" AS address, ${u}`,`FROM ${He}`,`WHERE ${c.join(`
81
81
  AND `)}`,'GROUP BY "feePayer"',"ORDER BY value DESC"].join(`
82
- `),l=`\u2705 Generated swap query for token \`${e}\`
82
+ `),d=`\u2705 Generated swap query for token \`${e}\`
83
83
 
84
84
  **Direction:** ${r}
85
85
  **Measure:** ${o}${o==="volume"?" (USD)":" (transactions)"}
86
86
  `+(n!==void 0?`**Min USD per swap:** ${n}
87
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(`
88
+ `:"**Protocol:** all");return H({description:d,sql:l,metadata:{source:"swap",tokenMint:e,direction:r,measure:o,minUsdAmount:n,protocol:i}})}function Sr(t){let e=t.launchpad;if(!e||!(e in ft))return b("launchpad is required for bonding_curve source. Use 'raydium_launchlab' or 'metaplex_genesis'.");let r=ft[e],n=e==="raydium_launchlab"?t.poolState:t.genesisAccount;if(!n)return b(`${r.identifierParam} is required for ${r.label}. This identifies the specific token's bonding curve.`);try{D(n,r.identifierParam)}catch(p){return b(p.message)}let i=t.direction??"buy",o=t.minAmountIn,s=t.measure,u=s==="volume"?`${r.volumeExpression} AS value`:"COUNT(*) AS value",c=[`"${r.identifierColumn}" = '${n}'`,'"receivedAt" BETWEEN {{startDate}} AND {{endDate}}'];if(i!=="both"){let p=r.directionMap[i];c.push(`"direction" = '${p}'`)}o!==void 0&&c.push(`"${r.amountColumn}" >= ${o}`);let l=[`SELECT "feePayer" AS address, ${u}`,`FROM ${r.table}`,`WHERE ${c.join(`
89
89
  AND `)}`,'GROUP BY "feePayer"',"ORDER BY value DESC"].join(`
90
- `),l=`\u2705 Generated bonding curve query for ${r.label}
90
+ `),d=`\u2705 Generated bonding curve query for ${r.label}
91
91
 
92
92
  **${r.identifierParam}:** \`${n}\`
93
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}\`
94
+ **Measure:** ${s}`;return H({description:d,sql:l,metadata:{source:"bonding_curve",launchpad:e,[r.identifierParam]:n,direction:i,measure:s,minAmountIn:o}})}function xr(t){let e=t.tokenMint;if(!e)return b("tokenMint is required for hold source.");let r=t.minBalance;if(r==null)return b("minBalance is required for hold source.");if(r<=0)return b("minBalance must be greater than 0. A hold incentive without a minimum balance threshold would include all wallets.");let n=t.measure;try{D(e,"tokenMint")}catch(s){return b(s.message)}let i=_r[n](e,r),o=`\u2705 Generated hold query for token \`${e}\`
95
95
 
96
96
  **Min balance:** ${r} (raw amount)
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).
97
+ **Measure:** ${n} \u2014 ${Ir[n]}`;return H({description:o,sql:i,metadata:{source:"hold",tokenMint:e,minBalance:r,measure:n}})}async function Pr(t,e){let r=t.customEventId;if(!r)return b("customEventId is required for custom_event source.");let n=e.activeProject.id,i;try{let d=await e.apiRequest(`/projects/${n}/events`),f=I(N,d).find(m=>m.id===r);if(!f)return b("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 b("This event does not have column mappings yet. Events need to be ingested at least once before queries can be generated.");i=f}catch(d){return b(`Failed to fetch project events: ${d instanceof Error?d.message:String(d)}`)}let o=t.rawQuery,s=t.valueExpression;if(o&&s)return b('Provide either "valueExpression" or "rawQuery", not both.');if(!o&&!s)return b(`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(o){let l=Ue(o,i);if(!l.valid)return y(`SQL query validation failed:
105
- `+l.errors.map(d=>` - ${d}`).join(`
106
- `)+(l.warnings.length>0?`
104
+ **Raw SQL mode:** Provide a full SELECT query with {{startDate}} and {{endDate}} parameters.`);let u,c;if(o){let d=at(o,i);if(!d.valid)return b(`SQL query validation failed:
105
+ `+d.errors.map(p=>` - ${p}`).join(`
106
+ `)+(d.warnings.length>0?`
107
107
 
108
108
  \u26A0\uFE0F Warnings:
109
- `+l.warnings.map(d=>` - ${d}`).join(`
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
- `+l.errors.map(d=>` - ${d}`).join(`
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).
109
+ `+d.warnings.map(p=>` - ${p}`).join(`
110
+ `):""));u=o,c=d.warnings}else{let d=st(i,{valueExpression:s,filters:t.filters,groupByPubkey:t.groupByPubkey,orderBy:t.orderBy,limit:t.limit});if("errors"in d)return b(`Query build failed:
111
+ `+d.errors.map(p=>` - ${p}`).join(`
112
+ `));u=d.sql,c=d.warnings}let l=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"+u+"\n```\n\nPass this query as the `sqlQuery` parameter when creating a recurring incentive with `create_recurring_incentive`."},{type:"text",text:JSON.stringify({sql:u,eventId:i.id,eventName:i.eventName})}]}:{content:[{type:"text",text:ct(u,i,c,l)}]}}async function Dr(t,e){let r=t.instructionId;if(!r)return b("instructionId is required for idl_instruction source.");let n=e.activeProject.id,i,o;try{let p=await e.apiRequest(`/projects/${n}/idl`),f=I(W,p),m,h;for(let w of f){if(!w.instructions)continue;let R=w.instructions.find(M=>M.id===r);if(R){m=R,h=w;break}}if(!m||!h)return b("Instruction not found on this project. Use `list_idls` to see available IDLs and their instructions.");i=m,o=h}catch(p){return b(`Failed to fetch project IDLs: ${p instanceof Error?p.message:String(p)}`)}let s=t.rawQuery,u=t.valueExpression;if(s&&u)return b('Provide either "valueExpression" or "rawQuery", not both.');if(!s&&!u)return b(`Provide either "valueExpression" (structured mode) or "rawQuery" (raw SQL mode).
113
113
 
114
114
  **Structured mode examples:**
115
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=Ge(s,i);if(!d.valid)return y(`SQL query validation failed:
120
- `+d.errors.map(f=>` - ${f}`).join(`
121
- `)+(d.warnings.length>0?`
119
+ **Raw SQL mode:** Provide a full SELECT query with {{startDate}} and {{endDate}} parameters.`);let c,l;if(s){let p=pt(s,i);if(!p.valid)return b(`SQL query validation failed:
120
+ `+p.errors.map(f=>` - ${f}`).join(`
121
+ `)+(p.warnings.length>0?`
122
122
 
123
123
  \u26A0\uFE0F Warnings:
124
- `+d.warnings.map(f=>` - ${f}`).join(`
125
- `):""));a=s,u=d.warnings}else{let d=Be(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. First step in creating any incentive (leaderboard, rebate, raffle, direct, campaign) \u2014 defines what user activity is tracked. Token sources (swap, bonding_curve, hold) require no setup \u2014 already indexed. Custom event and IDL instruction sources require prior setup. Pass result to `preview_incentive_query` to validate, then to `create_recurring_incentive` as `sqlQuery`.",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:"CREATION-TIME ONLY \u2014 Test/validate a SQL query against real indexed data before creating an incentive. Verify that a `generate_incentive_query` result produces expected data before committing. Do NOT use for live/active incentive results \u2014 use `get_incentive_results` instead. Optional step \u2014 most useful for tokens/events with existing indexed data; newly tracked sources may return empty results (normal, user can skip). Runs async \u2014 polls up to 60s. IMPORTANT: Tell the user you're submitting and will wait for results (may be queued before execution). Defaults to last 3 days of data. Accepts queries from `generate_incentive_query` or valid 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.
124
+ `+p.warnings.map(f=>` - ${f}`).join(`
125
+ `):""));c=s,l=p.warnings}else{let p=dt(i,{valueExpression:u,filters:t.filters,groupByFeePayer:t.groupByFeePayer,orderBy:t.orderBy});if("errors"in p)return b(`Query build failed:
126
+ `+p.errors.map(f=>` - ${f}`).join(`
127
+ `));c=p.sql,l=p.warnings}let d=s?{isRawQuery:!0}:{valueExpression:u,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"+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,instructionId:i.id,instructionName:i.instructionName,idlId:i.idlId})}]}:{content:[{type:"text",text:mt(c,i,o,l,d)}]}}var ht={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. First step in creating any incentive (leaderboard, rebate, raffle, direct, campaign) \u2014 defines what user activity is tracked. Token sources (swap, bonding_curve, hold) require no setup \u2014 already indexed. Custom event and IDL instruction sources require prior setup. Pass result to `preview_incentive_query` to validate, then to `create_recurring_incentive` as `sqlQuery`.",requires:["auth","activeProject"],inputSchema:{source:A.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:A.string().optional().describe("SPL token mint address (required for swap, hold)"),direction:A.enum(["buy","sell","both"]).optional().describe("Trade direction (swap, bonding_curve). Default: buy"),measure:A.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:A.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:A.string().optional().describe("DEX protocol filter, e.g. Jupiter, Raydium (swap only)"),launchpad:A.enum(["raydium_launchlab","metaplex_genesis"]).optional().describe("Launchpad program (required for bonding_curve)"),poolState:A.string().optional().describe("Pool state address (required for raydium_launchlab)"),genesisAccount:A.string().optional().describe("Genesis account (required for metaplex_genesis)"),minAmountIn:A.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:A.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:A.string().optional().describe("Custom event ID (required for custom_event). Use list_project_events to find events."),valueExpression:A.string().optional().describe('Value expression for structured queries (custom_event, idl_instruction). E.g. "SUM(amount)", "COUNT(*)"'),filters:A.array(A.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:A.boolean().optional().describe("Group by pubkey (custom_event only, default: true)"),orderBy:A.enum(["DESC","ASC"]).optional().describe("Sort order (custom_event, idl_instruction)"),limit:A.number().optional().describe("Max rows (custom_event only)"),rawQuery:A.string().optional().describe("Raw SQL query with {{startDate}}/{{endDate}} params (custom_event, idl_instruction)"),instructionId:A.string().optional().describe("Instruction ID (required for idl_instruction). Use list_idls to find instructions."),groupByFeePayer:A.boolean().optional().describe("Group by fee payer (idl_instruction only, default: true)"),confirmed:A.boolean().optional().describe("Set to true to confirm (custom_event, idl_instruction). Omit to preview.")},handler:async(t,e)=>{let r=y(e);if(r)return r;let n=E(e);if(n)return n;let i=t.source;if(i in oe){let o=t.measure;if(!o)return b(`measure is required for ${i} source.`);if(!oe[i].includes(o))return b(`Invalid measure '${o}' for ${i} source. Allowed: ${oe[i].join(", ")}`)}switch(i){case"swap":return kr(t);case"bonding_curve":return Sr(t);case"hold":return xr(t);case"custom_event":return Pr(t,e);case"idl_instruction":return Dr(t,e);default:return b(`Unknown source: ${i}`)}}};import{z as ie}from"zod";var Cr=5e3,gt=6e4,$r=3,Nr=20;function jr(){let t=new Date,e=new Date(Date.UTC(t.getUTCFullYear(),t.getUTCMonth(),t.getUTCDate())),r=new Date(e);return r.setUTCDate(r.getUTCDate()-$r),{startDate:r.toISOString(),endDate:e.toISOString()}}function Lr(t){return new Promise(e=>setTimeout(e,t))}function qr(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(u=>`| ${u.split(",").join(" | ")} |`)];return r.length-1>e&&s.push(`| _...and ${r.length-1-e} more rows_ |`),s.join(`
129
+ `)}async function Or(t,e,r){let n=Date.now();for(;Date.now()-n<gt;){let i=await t.apiRequest(`/project/${e}/query/${r}/result`),o=v(Te,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 Lr(Cr)}return{status:"TIMEOUT",preview:null,error:null,totalRows:null,duration:null,timedOut:!0}}var yt={name:"preview_incentive_query",description:"CREATION-TIME ONLY \u2014 Test/validate a SQL query against real indexed data before creating an incentive. Verify that a `generate_incentive_query` result produces expected data before committing. Do NOT use for live/active incentive results \u2014 use `get_incentive_results` instead. Optional step \u2014 most useful for tokens/events with existing indexed data; newly tracked sources may return empty results (normal, user can skip). Runs async \u2014 polls up to 60s. IMPORTANT: Tell the user you're submitting and will wait for results (may be queued before execution). Defaults to last 3 days of data. Accepts queries from `generate_incentive_query` or valid SQL with {{startDate}}/{{endDate}} parameters.",requires:["auth","activeProject"],inputSchema:{sqlQuery:ie.string().describe("The SQL query to execute and preview. Typically from generate_incentive_query output."),startDate:ie.string().optional().describe("ISO 8601 start date for the query window. Default: midnight 3 days ago."),endDate:ie.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=y(e);if(r)return r;let n=E(e);if(n)return n;let i=e.activeProject.id,o=t.sqlQuery,s=jr(),u=t.startDate??s.startDate,c=t.endDate??s.endDate,l;try{let R=await e.apiRequest(`/project/${i}/query/request`,{method:"POST",body:{type:"RAW",query:o,paramMap:{startDate:"DATE",endDate:"DATE"},paramValues:{startDate:u,endDate:c},targetDatabase:"INDEXER"}});l=v(we,R).queryId}catch(R){return{isError:!0,content:[{type:"text",text:`\u274C Failed to submit query for preview: ${R instanceof Error?R.message:String(R)}`}]}}let d;try{d=await Or(e,i,l)}catch(R){return{isError:!0,content:[{type:"text",text:`\u274C Failed to poll query results (queryId: ${l}): ${R instanceof Error?R.message:String(R)}`}]}}if(d.timedOut)return{content:[{type:"text",text:`\u23F3 Query is still processing after ${gt/1e3}s.
130
130
 
131
- **Query ID:** \`${u}\`
131
+ **Query ID:** \`${l}\`
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,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
133
+ The query may still complete \u2014 you can check results later. The query was submitted with date range ${u} to ${c}.`},{type:"text",text:JSON.stringify({queryId:l,status:"TIMEOUT",startDate:u,endDate:c})}]};if(d.error||d.status==="FAILED")return{isError:!0,content:[{type:"text",text:`\u274C Query preview failed: ${d.error??"Unknown error"}`}]};let p=d.totalRows??0,f=d.duration,m=d.preview,h;return!m||m.trim()===""?h="_No matching data found for the given date range._":h=qr(m,Nr),{content:[{type:"text",text:`\u2705 Query preview complete
134
134
 
135
- **Results:** ${d} rows`+(f!==null?` (${(f/1e3).toFixed(1)}s)`:"")+`
136
- **Date range:** ${c} \u2192 ${a}
135
+ **Results:** ${p} rows`+(f!==null?` (${(f/1e3).toFixed(1)}s)`:"")+`
136
+ **Date range:** ${u} \u2192 ${c}
137
137
 
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 \u2014 returns program name, address, and available instructions with fields/accounts. Use before `create_idl` to let the user select instructions for tracking. Accepts `filePath` (preferred for large files) or inline `idl` JSON. Limits per instruction: 15 numbers, 15 strings, 10 booleans, 30 accounts \u2014 exceeding limits are flagged. IMPORTANT: Display the full instruction summary so the user can review and select before passing choices to `create_idl`.",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`."}
138
+ `+h},{type:"text",text:JSON.stringify({queryId:l,totalRows:p,duration:f,preview:m,startDate:u,endDate:c})}]}}};import{z as K}from"zod";import Br from"fs/promises";var Ur=new Set(["u8","u16","u32","u64","u128","i8","i16","i32","i64","i128","f32","f64"]),Mr=new Set(["string","publicKey","pubkey","bytes"]);function Fr(t){if(Ur.has(t))return"number";if(Mr.has(t))return"string";if(t==="bool")return"boolean"}function se(t){if(typeof t=="string")return t;if(typeof t=="object"&&t!==null){let e=t.name;if(typeof e=="string")return e}}function O(t){if(typeof t=="string")return t;if(typeof t=="object"&&t!==null){let e=t;if("defined"in e)return se(e.defined)??"unknown";if("option"in e)return`option<${O(e.option)}>`;if("vec"in e)return`vec<${O(e.vec)}>`;if("array"in e){let r=e.array;return`array<${O(r[0])}>`}}return"unknown"}function q(t,e,r,n=new Set){if(typeof e=="string"){let i=Fr(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 q(t,i.option,r,n).map(s=>({...s,optional:!0}));if("vec"in i){let o=O(i.vec);return[{path:t,type:"string",optional:!1,idlType:`vec<${o}>`}]}if("array"in i){let o=i.array,s=O(o[0]);return[{path:t,type:"string",optional:!1,idlType:`array<${s}>`}]}if("defined"in i){let o=se(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 u=new Set(n);u.add(o);let c=[];for(let l=0;l<s.fields.length;l++){let d=s.fields[l];if(typeof d=="object"&&d!==null&&"name"in d&&"type"in d){let p=d,f=q(`${t}.${p.name}`,p.type,r,u);c.push(...f)}else{let p=q(t,d,r,u);c.push(...p)}}return c}}}return[{path:t,type:"string",optional:!1,idlType:String(e)}]}var C=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(u=>this.parseInstruction(u,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(u=>{let c=typeof u.name=="string"?u.name:"unknown";return q(c,u.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 Qr={number:15,string:15,boolean:10,accounts:30};function V(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=Qr[n];i>o&&r.push({type:n,count:i,max:o})}return{name:t.name,counts:e,withinLimits:r.length===0,exceeded:r}}function ae(t,e){let r=e.map(V),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`,u=`${o} ${i.name} \u2014 ${s}`;if(!i.withinLimits){let c=i.exceeded.map(l=>`${l.type} by ${l.count-l.max}`).join(", ");u+=` (exceeds: ${c})`}n.push(u)}return n.push(""),n.push("Select which instructions to track."),n.join(`
139
+ `)}var vt={name:"parse_idl",description:"Parse a Solana Anchor IDL \u2014 returns program name, address, and available instructions with fields/accounts. Use before `create_idl` to let the user select instructions for tracking. Accepts `filePath` (preferred for large files) or inline `idl` JSON. Limits per instruction: 15 numbers, 15 strings, 10 booleans, 30 accounts \u2014 exceeding limits are flagged. IMPORTANT: Display the full instruction summary so the user can review and select before passing choices to `create_idl`.",inputSchema:{filePath:K.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:K.record(K.string(),K.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 c=await Br.readFile(e,"utf-8");n=JSON.parse(c)}catch(c){let l=c instanceof Error?c.message:String(c);return{content:[{type:"text",text:`\u274C ${l.includes("ENOENT")?`File not found: ${e}`:`Failed to read IDL file: ${l}`}`}],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 C;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(c){return{content:[{type:"text",text:`\u274C Failed to parse IDL: ${c instanceof Error?c.message:String(c)}`}],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=ae(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: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. Accepts `filePath` (preferred for large files) or inline `idl` JSON. Program address auto-extracted from IDL \u2014 provide `programAddress` only to override. IMPORTANT: Use `parse_idl` first so the user can select instructions to track before calling this. IDL JSON stored exactly as provided \u2014 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 a tracked instruction to an existing uploaded IDL. Use when the IDL exists but the instruction isn\'t tracked yet. Use `parse_idl` first for fields/accounts, `list_idls` for 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.
141
+ ${s}`},{type:"text",text:JSON.stringify({programName:o.name,programAddress:o.address,instructions:o.instructions.map(c=>({name:c.name,analysis:V(c),fields:c.fields,accounts:c.accounts}))})}]}}};import{z as _}from"zod";import Wr from"fs/promises";function Gr(t){return t.replace(/_/g," ").replace(/\b\w/g,e=>e.toUpperCase())}var bt={name:"create_idl",description:"Upload a Solana Anchor IDL and create instruction tracking in a single call. Accepts `filePath` (preferred for large files) or inline `idl` JSON. Program address auto-extracted from IDL \u2014 provide `programAddress` only to override. IMPORTANT: Use `parse_idl` first so the user can select instructions to track before calling this. IDL JSON stored exactly as provided \u2014 never modified.",requires:["auth","activeProject"],inputSchema:{filePath:_.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:_.record(_.string(),_.any()).optional().describe("Full Anchor IDL JSON content. For large IDLs, use `filePath` instead."),programAddress:_.string().optional().describe("On-chain Solana program address. If omitted, extracted from the IDL's `address` field."),displayName:_.string().optional().describe("User-friendly display name for the program"),description:_.string().optional().describe("Description of the program"),selectedInstructions:_.array(_.object({instructionName:_.string().describe("Name of the instruction from the IDL"),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("Selected fields to track for this instruction"),accounts:_.array(_.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=y(e);if(r)return r;let n=E(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 m=await Wr.readFile(i,"utf-8");s=JSON.parse(m)}catch(m){let h=m instanceof Error?m.message:String(m);return{content:[{type:"text",text:`\u274C ${h.includes("ENOENT")?`File not found: ${i}`:`Failed to read IDL file: ${h}`}`}],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 u=new C;if(!u.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 c;try{c=u.parse(s)}catch(m){return{content:[{type:"text",text:`\u274C Failed to parse IDL: ${m instanceof Error?m.message:String(m)}`}],isError:!0}}if(!c.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 l=t.programAddress??c.address;if(!l)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 d=t.selectedInstructions,p=e.activeProject.id,f={programAddress:l,name:c.name,displayName:t.displayName??null,description:t.description??null,idl:s,types:s.types??null};d&&d.length>0&&(f.instructions=d.map(m=>({instructionName:m.instructionName,label:Gr(m.instructionName),fields:m.fields,accounts:m.accounts})));try{let m=await e.apiRequest(`/projects/${p}/idl/create`,{method:"POST",body:f}),h=v(Re,m),w=h.instructionIds??[],R=[`\u2705 IDL uploaded successfully for program "${c.name}" (${l})`];return w.length>0&&R.push(`\u{1F4C1} ${w.length} instruction${w.length===1?"":"s"} configured for tracking.`),{content:[{type:"text",text:R.join(`
142
+ `)},{type:"text",text:JSON.stringify({idlId:h.id,instructionIds:w})}]}}catch(m){let h=m instanceof Error?m.message:String(m);return h.includes("already exists")?{content:[{type:"text",text:`\u274C An IDL for program address ${l} already exists in this project.`}],isError:!0}:{content:[{type:"text",text:`\u274C Failed to upload IDL: ${h}`}],isError:!0}}}};import{z as k}from"zod";function Hr(t){return t.replace(/_/g," ").replace(/\b\w/g,e=>e.toUpperCase())}var wt={name:"create_instruction",description:'Add a tracked instruction to an existing uploaded IDL. Use when the IDL exists but the instruction isn\'t tracked yet. Use `parse_idl` first for fields/accounts, `list_idls` for 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:k.string().describe("ID of the existing IDL to add the instruction to. Use `list_idls` to find this."),instructionName:k.string().describe("Name of the instruction from the IDL (e.g. 'buy_exact_in')."),label:k.string().optional().describe("Display label for the instruction. Defaults to title-cased instruction name."),fields:k.array(k.object({fieldName:k.string().describe("Dot-path field name (e.g. 'amount' or 'config.feeRate')"),type:k.enum(["string","number","boolean"]).describe("Field data type"),label:k.string().nullish().describe("Display label"),description:k.string().nullish().describe("Field description")})).describe("Fields to track for this instruction. Use `parse_idl` to discover available fields."),accounts:k.array(k.string()).describe("Account names to track for this instruction. Use `parse_idl` to discover available accounts.")},handler:async(t,e)=>{let r=y(e);if(r)return r;let n=E(e);if(n)return n;let i=e.activeProject.id,o=t.idlId,s=t.instructionName,u=t.label??Hr(s),c=t.fields,l=t.accounts;try{let d=await e.apiRequest(`/projects/${i}/instruction/create`,{method:"POST",body:{idlId:o,instructionName:s,label:u,fields:c,accounts:l}}),f=v(_e,d).id;return{content:[{type:"text",text:`\u2705 Instruction "${s}" created successfully on the IDL.
143
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: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 uploaded IDLs (Anchor programs) and tracked instructions for the active project. Shows program name, address, status, and instruction field/account counts. Use to find instruction IDs for `generate_incentive_query` (source: "idl_instruction"). IMPORTANT: Always display the full list.',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 project context \u2014 documentation, program/token addresses, and domain knowledge from the project owner. AUTOMATICALLY call after setting an active project and before building incentives, events, or queries. Also use for analytics interpretation or project information. Cached per project \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}"**
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(d){let p=d instanceof Error?d.message:String(d);return p.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: ${p}`}],isError:!0}}}};var Tt={name:"list_idls",description:'List uploaded IDLs (Anchor programs) and tracked instructions for the active project. Shows program name, address, status, and instruction field/account counts. Use to find instruction IDs for `generate_incentive_query` (source: "idl_instruction"). IMPORTANT: Always display the full list.',requires:["auth","activeProject"],inputSchema:{},handler:async(t,e)=>{let r=y(e);if(r)return r;let n=E(e);if(n)return n;let i=e.activeProject.id;try{let o=await e.apiRequest(`/projects/${i}/idl`),s=I(W,o);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 u=[`\u{1F4CB} **IDLs for "${e.activeProject.name}"** (${s.length})`,""];for(let c of s){let l=c.active?"\u2705 Active":"\u23F8\uFE0F Inactive",d=c.displayName?` \u2014 ${c.displayName}`:"";u.push(`**${c.name}** (\`${c.programAddress}\`)${d} ${l}`);let p=c.instructions??[];if(p.length===0)u.push(" No instructions tracked");else{u.push(` Instructions (${p.length}):`);for(let f of p){let m=f.instruction?.length??0,h=Object.keys(f.accountMapping??{}).length;u.push(` \u2022 ${f.instructionName} \u2014 ${m} field${m!==1?"s":""}, ${h} account${h!==1?"s":""}`)}}u.push("")}return{content:[{type:"text",text:u.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 project context \u2014 documentation, program/token addresses, and domain knowledge from the project owner. AUTOMATICALLY call after setting an active project and before building incentives, events, or queries. Also use for analytics interpretation or project information. Cached per project \u2014 subsequent calls are free.",inputSchema:{},handler:async(t,e)=>{let r=y(e);if(r)return r;let n=E(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=v(Ie,o),u=[],c=s.addresses;(c?.programAddress||c?.tokenAddress)&&(u.push("## Addresses"),c.programAddress&&u.push(`- **Program:** \`${c.programAddress}\``),c.tokenAddress&&u.push(`- **Token:** \`${c.tokenAddress}\``),u.push(""));let l=s.files;if(l&&Object.keys(l).length>0)for(let[p,f]of Object.entries(l))u.push(`## ${p}`),u.push(f),u.push("");let d;return u.length===0?d={content:[{type:"text",text:`\u{1F4CB} No AI context available for project "${e.activeProject.name}".`}]}:d={content:[{type:"text",text:`\u{1F4CB} **AI Context for "${e.activeProject.name}"**
148
148
 
149
- ${s.join(`
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:`View leaderboard scores/rankings, check recipient payout status, or download/export full CSV data for a recurring incentive. Use for standings, results, winners, payouts, distributions, or data export/download. Do NOT use \`preview_incentive_query\` (creation-time query testing only).
149
+ ${u.join(`
150
+ `)}`}]},e.aiContextCache={projectId:i,data:d},d}catch(o){return{content:[{type:"text",text:`Failed to fetch AI context: ${o instanceof Error?o.message:String(o)}`}],isError:!0}}}};import{z as U}from"zod";var At={name:"get_incentive_results",requires:["auth","activeProject"],description:`View leaderboard scores/rankings, check recipient payout status, or download/export full CSV data for a recurring incentive. Use for standings, results, winners, payouts, distributions, or data export/download. Do NOT use \`preview_incentive_query\` (creation-time query testing only).
151
151
 
152
152
  Modes:
153
153
  - "preview" (default): Scores/rankings \u2014 who is leading and metric values; latest epoch or specific epochConfigId.
154
154
  - "recipients": Allocations, payout amounts, distribution status (DISTRIBUTED/PENDING/FAILED); requires epochConfigId or auto-selects latest CLAIMING/COMPLETED epoch.
155
155
  - "download": Signed S3 CSV URL (5 min expiry) for full data export with all columns.
156
156
 
157
- Use \`get_recurring_incentive\` for epoch IDs/statuses, \`list_recurring_incentives\` for incentive IDs.`,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:"View active incentive/campaign offers, reward pools, timeframes, and metadata for the current project. This is the builder's view of what end users can see and claim \u2014 published rewards, campaign status, and offer details. Use this to review live campaigns, 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.
157
+ Use \`get_recurring_incentive\` for epoch IDs/statuses, \`list_recurring_incentives\` for incentive IDs.`,inputSchema:{recurringOfferId:U.string().describe("The recurring incentive ID. Use `list_recurring_incentives` to find it."),mode:U.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:U.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:U.number().optional().describe("Max rows to return (default: 50). Applies to preview and recipients modes."),offset:U.number().optional().describe("Pagination offset (default: 0). Applies to preview and recipients modes.")},handler:async(t,e)=>{let r=y(e);if(r)return r;let n=E(e);if(n)return n;let i=e.activeProject.id,o=t.recurringOfferId,s=t.mode??"preview",u=t.epochConfigId,c=t.limit??50,l=t.offset??0;try{return s==="download"?await zr(e,i,o,u):s==="recipients"?await Kr(e,i,o,u,c,l):await Vr(e,i,o,u,c,l)}catch(d){return{content:[{type:"text",text:`\u274C Failed to get incentive results: ${d instanceof Error?d.message:String(d)}`}],isError:!0}}}};async function Vr(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}`,u=await t.apiRequest(s),c=v(he,u),l=c.results??[];if(l.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 p=[n?`\u{1F4CA} **Evaluation Results** (epoch \`${n}\`)`:"\u{1F4CA} **Latest Evaluation Results**","",`Showing ${l.length} of ${c.total} results${c.lastUpdated?` (last updated: ${c.lastUpdated.toISOString()})`:""}`,"","| Rank | Wallet | Metric Value |","|------|--------|-------------|"];return l.forEach((f,m)=>{p.push(`| ${o+m+1} | \`${f.walletAddress}\` | ${f.metricValue} |`)}),c.total>o+l.length&&(p.push(""),p.push(`_${c.total-o-l.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:p.join(`
158
+ `)}]}}async function Kr(t,e,r,n,i,o){let s=n;if(!s){let p=await t.apiRequest(`/project/${e}/recurring-offer/${r}`),h=[...v(P,p).configs??[]].reverse().find(w=>w.status==="CLAIMING"||w.status==="COMPLETED");if(!h)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=h.id}let u=await t.apiRequest(`/project/${e}/recurring-offer/${r}/epoch/${s}/recipients?limit=${i}&offset=${o}`),c=v(ge,u),l=c.recipients??[];if(l.length===0)return{content:[{type:"text",text:`\u{1F4CB} No recipients found for epoch \`${s}\`. Allocations may not have been created yet.`}]};let d=[`\u{1F4CB} **Recipients** (epoch \`${s}\`)`,"",`Showing ${l.length} of ${c.total} recipients`,"","| Rank | Wallet | Allocated | Distributed | Status |","|------|--------|-----------|-------------|--------|"];return l.forEach((p,f)=>{let m=p.distributedAmount!==null?String(p.distributedAmount):"\u2014",h=p.status==="DISTRIBUTED"?"\u2705":p.status==="FAILED"?"\u274C":"\u23F3";d.push(`| ${o+f+1} | \`${p.walletAddress}\` | ${p.allocatedAmount} | ${m} | ${h} ${p.status} |`)}),c.total>o+l.length&&(d.push(""),d.push(`_${c.total-o-l.length} more recipients. Use \`offset: ${o+i}\` to see the next page._`)),{content:[{type:"text",text:d.join(`
159
+ `)}]}}async function zr(t,e,r,n){let i=await t.apiRequest(`/project/${e}/recurring-offer/${r}`),s=v(P,i).configs??[];if(s.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 u=n?s.find(d=>d.id===n):s[s.length-1];if(!u)return{content:[{type:"text",text:`\u274C Epoch config \`${n}\` not found. Use \`get_recurring_incentive\` to see available epochs.`}],isError:!0};if(!u.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/${u.sqlQueryId}/download`),l=v(Ee,c);return{content:[{type:"text",text:["\u{1F4E5} **CSV Download Ready**","",`**Epoch:** ${u.epochNumber} (\`${u.id}\`)`,`**URL:** ${l.url}`,"","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
+ `)}]}}var z={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.
168
161
 
169
162
  **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.
170
163
 
@@ -287,7 +280,111 @@ Call \`create_recurring_incentive\` with \`confirmed: false\` first to preview.
287
280
  - **Auth expired mid-flow:** Call \`check_auth_status\` then \`authenticate\` again before retrying the failed step.
288
281
  - **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\`.
289
282
  - **Preview looks wrong:** Go back to Step 2 and adjust the query parameters (filters, measure, direction).
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 & Downloads\n\n1. `get_incentive_results` with `mode: "preview"` \u2192 leaderboard scores/rankings (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/export URL (signed S3, expires 5 min)\n4. `get_offer_details` \u2192 active incentive/campaign offers with reward pools and timeframes\n\n**Do NOT use `preview_incentive_query` to check live results** \u2014 that tool is CREATION-TIME ONLY (tests queries before creating an incentive). 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 \u2014 token incentives (swap, hold, bonding curve), custom event incentives, and IDL instruction incentives. Read this to understand the tool pipeline, step-by-step flows, and available guided prompts.",mimeType:"text/markdown"},async()=>({contents:[{uri:"torque://workflows",mimeType:"text/markdown",text:gr}]}))}var yr='# Torque MCP \u2014 Capabilities\n\n## What is Torque MCP?\n\nTorque MCP connects AI assistants to the **Torque incentive platform** on Solana. You can create and manage reward programs, track on-chain and off-chain user activity, and distribute tokens or SOL \u2014 all through natural conversation.\n\n## Prerequisites\n\nMost tools require **authentication** \u2014 call `check_auth_status` first, then `authenticate` if needed. Wait for authentication to succeed before calling any other tool.\n\nMost tools also require an **active project** \u2014 call `list_projects` then `set_active_project` after authenticating. Wait for the project to be set before calling project-scoped tools.\n\n## Available Tools\n\n### Authentication\n| Tool | Description |\n|------|-------------|\n| `authenticate` | Connect to Torque with an auth token or wallet private key |\n| `check_auth_status` | Verify your current authentication |\n| `logout` | Disconnect from Torque |\n\n### API Keys\n| Tool | Description | Requires |\n|------|-------------|----------|\n| `list_api_keys` | List your API keys for event ingestion | auth |\n| `create_api_key` | Create a new API key for event ingestion | auth |\n\n### Projects\n| Tool | Description | Requires |\n|------|-------------|----------|\n| `list_projects` | View all your Torque projects | auth |\n| `create_project` | Create a new project \u2014 ask if they are a token team, protocol, or both, then collect the appropriate address(es) | auth |\n| `set_active_project` | Select a project to work in | auth |\n\n### Custom Events (off-chain tracking)\n| Tool | Description | Requires |\n|------|-------------|----------|\n| `create_custom_event` | Define a new event schema for tracking off-chain activity | auth |\n| `list_project_events` | View events attached to the active project | auth, project |\n| `list_custom_events` | Browse your own custom events across all projects | auth |\n| `attach_custom_event` | Attach an event to the active project | auth, project |\n\n### IDL & On-Chain Programs\n| Tool | Description | Requires |\n|------|-------------|----------|\n| `parse_idl` | Parse a Solana Anchor IDL and show available instructions | \u2014 |\n| `create_idl` | Upload an Anchor IDL and configure instruction tracking | auth, project |\n| `create_instruction` | Add an instruction to an already-uploaded IDL | auth, project |\n| `list_idls` | List uploaded IDLs and their tracked instructions | auth, project |\n\n### Incentive Query Builder\n| Tool | Description | Requires |\n|------|-------------|----------|\n| `generate_incentive_query` | Generate SQL for any incentive \u2014 swap, bonding curve, hold, custom event, or IDL instruction | auth, project |\n| `preview_incentive_query` | CREATION-TIME ONLY \u2014 test/validate a query against real data before creating an incentive (NOT for live results \u2014 use `get_incentive_results`) | auth, project |\n\n### Incentives\n| Tool | Description | Requires |\n|------|-------------|----------|\n| `create_recurring_incentive` | Create a recurring incentive (leaderboard, rebate, raffle, or direct) | auth, project |\n| `list_recurring_incentives` | View all incentives on the active project | auth, project |\n| `get_recurring_incentive` | Get details of a specific incentive | auth, project |\n| `get_recurring_incentive_analytics` | View performance analytics for an incentive | auth, project |\n\n### Results & Monitoring\n| Tool | Description | Requires |\n|------|-------------|----------|\n| `get_incentive_results` | View leaderboard scores/rankings, check recipient payout status, or download/export full CSV \u2014 three modes: preview, recipients, download | auth, project |\n| `get_offer_details` | View active incentive/campaign offers \u2014 reward pools, timeframes, and metadata | auth, project |\n\n### Context\n| Tool | Description | Requires |\n|------|-------------|----------|\n| `get_ai_context` | Fetch project documentation, domain knowledge, and configuration | auth, project |\n\n## Guided Workflows\n\n| Prompt | Description |\n|--------|-------------|\n| `create-incentive` | Unified guide to create any incentive \u2014 token-based, custom event, or custom instruction |\n\n## Quick Start\n\n1. **Authenticate** \u2014 Use `check_auth_status`, then `authenticate` if needed. **Wait for auth to succeed before proceeding.**\n2. **Select a project** \u2014 Use `list_projects` then `set_active_project`. **Wait for project to be set before proceeding.**\n3. **Create an incentive** \u2014 Use the `create-incentive` prompt for a guided walkthrough, or call individual tools directly\n\n## Common Tasks\n\n| I want to... | Do this |\n|---------------|---------|\n| Create an incentive for token activity (swap, hold, bonding curve) | Use the `create-incentive` prompt \u2192 choose Token path |\n| Create an incentive based on off-chain activity | Use the `create-incentive` prompt \u2192 choose Custom Event path |\n| Create an incentive based on on-chain activity | Use the `create-incentive` prompt \u2192 choose Custom Instruction path |\n| See what incentives exist | `list_recurring_incentives` |\n| Check how an incentive is performing | `get_recurring_incentive_analytics` |\n| Track off-chain user activity | `create_custom_event` |\n| Track on-chain program instructions | `parse_idl` \u2192 `create_idl` |\n| See what events a project has | `list_project_events` |\n| See what on-chain programs are tracked | `list_idls` |\n| Manage API keys for event ingestion | `list_api_keys` or `create_api_key` |\n| View leaderboard scores/standings | `get_incentive_results` with mode: "preview" |\n| Check payout/distribution status | `get_incentive_results` with mode: "recipients" |\n| Download/export full results CSV | `get_incentive_results` with mode: "download" |\n| See active incentive offers | `get_offer_details` |\n| Build a landing page | Read `torque://building-landing-pages` resource |\n';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
283
+ - **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 Yr='# 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 & Downloads\n\n1. `get_incentive_results` with `mode: "preview"` \u2192 leaderboard scores/rankings (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/export URL (signed S3, expires 5 min)\n\n**Do NOT use `preview_incentive_query` to check live results** \u2014 that tool is CREATION-TIME ONLY (tests queries before creating an incentive). 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 Rt(t){t.registerResource("workflows-guide","torque://workflows",{description:"Guide to common Torque workflows \u2014 token incentives (swap, hold, bonding curve), custom event incentives, and IDL instruction incentives. Read this to understand the tool pipeline, step-by-step flows, and available guided prompts.",mimeType:"text/markdown"},async()=>({contents:[{uri:"torque://workflows",mimeType:"text/markdown",text:Yr}]}))}var Jr=`# Torque MCP \u2014 Capabilities
284
+
285
+ ## What is Torque MCP?
286
+
287
+ Torque MCP connects AI assistants to the **Torque incentive platform** on Solana. You can create and manage reward programs, track on-chain and off-chain user activity, and distribute tokens or SOL \u2014 all through natural conversation.
288
+
289
+ ## Prerequisites
290
+
291
+ Most tools require **authentication** \u2014 call \`check_auth_status\` first, then \`authenticate\` if needed. Wait for authentication to succeed before calling any other tool.
292
+
293
+ Most tools also require an **active project** \u2014 call \`list_projects\` then \`set_active_project\` after authenticating. Wait for the project to be set before calling project-scoped tools.
294
+
295
+ ## Available Tools
296
+
297
+ ### Authentication
298
+ | Tool | Description |
299
+ |------|-------------|
300
+ | \`authenticate\` | Connect to Torque with an auth token or wallet private key |
301
+ | \`check_auth_status\` | Verify your current authentication |
302
+ | \`logout\` | Disconnect from Torque |
303
+
304
+ ### API Keys
305
+ | Tool | Description | Requires |
306
+ |------|-------------|----------|
307
+ | \`list_api_keys\` | List your API keys for event ingestion | auth |
308
+ | \`create_api_key\` | Create a new API key for event ingestion | auth |
309
+
310
+ ### Projects
311
+ | Tool | Description | Requires |
312
+ |------|-------------|----------|
313
+ | \`list_projects\` | View all your Torque projects | auth |
314
+ | \`create_project\` | Create a new project \u2014 ask if they are a token team, protocol, or both, then collect the appropriate address(es) | auth |
315
+ | \`set_active_project\` | Select a project to work in | auth |
316
+
317
+ ### Custom Events (off-chain tracking)
318
+ | Tool | Description | Requires |
319
+ |------|-------------|----------|
320
+ | \`create_custom_event\` | Define a new event schema for tracking off-chain activity | auth |
321
+ | \`list_project_events\` | View events attached to the active project | auth, project |
322
+ | \`list_custom_events\` | Browse your own custom events across all projects | auth |
323
+ | \`attach_custom_event\` | Attach an event to the active project | auth, project |
324
+
325
+ ### IDL & On-Chain Programs
326
+ | Tool | Description | Requires |
327
+ |------|-------------|----------|
328
+ | \`parse_idl\` | Parse a Solana Anchor IDL and show available instructions | \u2014 |
329
+ | \`create_idl\` | Upload an Anchor IDL and configure instruction tracking | auth, project |
330
+ | \`create_instruction\` | Add an instruction to an already-uploaded IDL | auth, project |
331
+ | \`list_idls\` | List uploaded IDLs and their tracked instructions | auth, project |
332
+
333
+ ### Incentive Query Builder
334
+ | Tool | Description | Requires |
335
+ |------|-------------|----------|
336
+ | \`generate_incentive_query\` | Generate SQL for any incentive \u2014 swap, bonding curve, hold, custom event, or IDL instruction | auth, project |
337
+ | \`preview_incentive_query\` | CREATION-TIME ONLY \u2014 test/validate a query against real data before creating an incentive (NOT for live results \u2014 use \`get_incentive_results\`) | auth, project |
338
+
339
+ ### Incentives
340
+ | Tool | Description | Requires |
341
+ |------|-------------|----------|
342
+ | \`create_recurring_incentive\` | Create a recurring incentive (leaderboard, rebate, raffle, or direct) | auth, project |
343
+ | \`list_recurring_incentives\` | View all incentives on the active project | auth, project |
344
+ | \`get_recurring_incentive\` | Get details of a specific incentive | auth, project |
345
+ | \`get_recurring_incentive_analytics\` | View performance analytics for an incentive | auth, project |
346
+
347
+ ### Results & Monitoring
348
+ | Tool | Description | Requires |
349
+ |------|-------------|----------|
350
+ | \`get_incentive_results\` | View leaderboard scores/rankings, check recipient payout status, or download/export full CSV \u2014 three modes: preview, recipients, download | auth, project |
351
+
352
+ ### Context
353
+ | Tool | Description | Requires |
354
+ |------|-------------|----------|
355
+ | \`get_ai_context\` | Fetch project documentation, domain knowledge, and configuration | auth, project |
356
+
357
+ ## Guided Workflows
358
+
359
+ | Prompt | Description |
360
+ |--------|-------------|
361
+ | \`create-incentive\` | Unified guide to create any incentive \u2014 token-based, custom event, or custom instruction |
362
+
363
+ ## Quick Start
364
+
365
+ 1. **Authenticate** \u2014 Use \`check_auth_status\`, then \`authenticate\` if needed. **Wait for auth to succeed before proceeding.**
366
+ 2. **Select a project** \u2014 Use \`list_projects\` then \`set_active_project\`. **Wait for project to be set before proceeding.**
367
+ 3. **Create an incentive** \u2014 Use the \`create-incentive\` prompt for a guided walkthrough, or call individual tools directly
368
+
369
+ ## Common Tasks
370
+
371
+ | I want to... | Do this |
372
+ |---------------|---------|
373
+ | Create an incentive for token activity (swap, hold, bonding curve) | Use the \`create-incentive\` prompt \u2192 choose Token path |
374
+ | Create an incentive based on off-chain activity | Use the \`create-incentive\` prompt \u2192 choose Custom Event path |
375
+ | Create an incentive based on on-chain activity | Use the \`create-incentive\` prompt \u2192 choose Custom Instruction path |
376
+ | See what incentives exist | \`list_recurring_incentives\` |
377
+ | Check how an incentive is performing | \`get_recurring_incentive_analytics\` |
378
+ | Track off-chain user activity | \`create_custom_event\` |
379
+ | Track on-chain program instructions | \`parse_idl\` \u2192 \`create_idl\` |
380
+ | See what events a project has | \`list_project_events\` |
381
+ | See what on-chain programs are tracked | \`list_idls\` |
382
+ | Manage API keys for event ingestion | \`list_api_keys\` or \`create_api_key\` |
383
+ | View leaderboard scores/standings | \`get_incentive_results\` with mode: "preview" |
384
+ | Check payout/distribution status | \`get_incentive_results\` with mode: "recipients" |
385
+ | Download/export full results CSV | \`get_incentive_results\` with mode: "download" |
386
+ | Build a landing page | Read \`torque://building-landing-pages\` resource |
387
+ `;function _t(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:Jr}]}))}var Xr='# 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 It(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:Xr}]}))}var Zr=`# Torque \u2014 Building Landing Pages
291
388
 
292
389
  How leaderboards, claims, epochs, and results connect, and how to build a landing page that displays incentive data and enables users to claim rewards.
293
390
 
@@ -338,20 +435,56 @@ GET /recurring-offer/:id/latest-eval-results?limit=10&offset=0
338
435
 
339
436
  **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"\`.
340
437
 
341
- **Offer stats** (aggregate participation):
342
- \`\`\`
343
- GET /offer/:offerId/journey/stats
344
- \`\`\`
345
- - Fully public, no auth required
346
- - Returns total started and total converted counts
347
-
348
438
  ---
349
439
 
350
- ## 3. Displaying Offer Info
440
+ ## 2b. Epoch-Level Data (for dashboards & analytics)
351
441
 
352
- ### Via MCP Tools (authenticated)
442
+ Each recurring incentive runs in **epochs** \u2014 time-bounded evaluation periods. Each epoch carries its own configuration, which can change between epochs. Use \`get_recurring_incentive\` to access the \`configs\` array.
353
443
 
354
- Use \`get_offer_details\` to fetch active incentive/campaign offers for a project \u2014 reward pools, timeframes, and metadata.
444
+ ### Data available per epoch config:
445
+
446
+ **Distribution Config** (reward structure for that epoch):
447
+ | Field | Description |
448
+ |-------|-------------|
449
+ | \`distributionType\` | FORMULA, RAFFLE, DIRECT, MANUAL, or CSV |
450
+ | \`emissionType\` | TOKENS, SOL, or POINTS |
451
+ | \`tokenAddress\` | Token mint address (when emissionType is TOKENS) |
452
+ | \`tokenDecimals\` | Token decimal places (when emissionType is TOKENS) |
453
+ | \`totalFundAmount\` | Total reward budget for the epoch |
454
+ | \`customFormula\` | Payout formula (for FORMULA distribution) |
455
+ | \`prizeBuckets\` | Array of {amount, count} prize tiers (for RAFFLE) |
456
+ | \`selectionLogic\` | WEIGHTED_BY_METRIC or EQUAL_CHANCES (for RAFFLE) |
457
+ | \`maxPerParticipant\` | Per-user reward cap (if set) |
458
+ | \`claimWindowDuration\` | Claim period duration in seconds |
459
+ | \`distributionMethod\` | AIRDROP or CLAIM |
460
+
461
+ **Offer Metadata** (display info for that epoch):
462
+ | Field | Description |
463
+ |-------|-------------|
464
+ | \`title\` | Incentive display title |
465
+ | \`description\` | Optional description |
466
+ | \`image\` | Optional image URL |
467
+
468
+ **Epoch Timing:**
469
+ | Field | Description |
470
+ |-------|-------------|
471
+ | \`evalStart\` / \`evalEnd\` | Evaluation window \u2014 when user activity is tracked |
472
+ | \`claimStart\` / \`claimEnd\` | Claim window \u2014 when users can claim rewards |
473
+ | \`status\` | UPCOMING, EVALUATING, CLAIMING, COMPLETED, or FAILED |
474
+
475
+ ### Historical data across epochs
476
+
477
+ Distribution configs and metadata can change epoch-to-epoch (e.g., increasing the reward pool, changing the formula). This means builders can:
478
+ - Show reward history across epochs (how the incentive evolved)
479
+ - Display per-epoch analytics (participants, total distributed, rewards per wallet)
480
+ - Compare configs between epochs to highlight changes
481
+ - Build dashboards showing incentive performance over time
482
+
483
+ Use \`get_incentive_results\` with a specific \`epochConfigId\` to fetch results or recipients for any historical epoch, not just the latest.
484
+
485
+ ---
486
+
487
+ ## 3. Displaying Offer Info
355
488
 
356
489
  ### Via REST API (for frontends)
357
490
 
@@ -420,7 +553,6 @@ Claims are **fully server-side** \u2014 no transaction signing is required from
420
553
  | Endpoint Category | Auth Required | Safe For |
421
554
  |-------------------|---------------|----------|
422
555
  | Latest eval results (\`/recurring-offer/:id/latest-eval-results\`) | None | Client-side, fully public |
423
- | Offer stats (\`/offer/:id/journey/stats\`) | None | Client-side, fully public |
424
556
  | Claim trigger (\`/claim\`) | Optional API key | Client-side for non-gated |
425
557
  | Claim status (\`/claim/details/byAllocation\`) | Optional API key | Client-side for non-gated |
426
558
  | Offer details (\`/claim/details/byOffer\`) | Optional API key | Client-side for non-gated |
@@ -445,7 +577,7 @@ Claims are **fully server-side** \u2014 no transaction signing is required from
445
577
  \u2502 \u2502 (get pubkey) \u2502 \u2502 \u2022 GET /recurring-offer/ \u2502 \u2502
446
578
  \u2502 \u2502 \u2502 \u2502 :id/latest-eval- \u2502 \u2502
447
579
  \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502 results \u2502 \u2502
448
- \u2502 \u2502 \u2502 \u2022 GET /stats \u2502 \u2502
580
+ \u2502 \u2502 \u2502 \u2502 \u2502
449
581
  \u2502 \u2502 \u2502 \u2022 GET /claim \u2502 \u2502
450
582
  \u2502 \u2502 \u2502 \u2022 GET /claim/details/... \u2502 \u2502
451
583
  \u2502 \u2502 \u2502 \u2022 GET /claim/details/ \u2502 \u2502
@@ -471,4 +603,4 @@ Claims are **fully server-side** \u2014 no transaction signing is required from
471
603
  **Frontend** handles wallet connection and all public endpoints \u2014 eval results display, offer info, claim triggering, and claim status polling. No secrets needed.
472
604
 
473
605
  **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.
474
- `;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.1"}),r=Q(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,Ne,r),v(e,je,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)});
606
+ `;function kt(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:Zr}]}))}function rn(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 T(t,e,r){t.registerTool(e.name,{description:rn(e),inputSchema:e.inputSchema},async n=>e.handler(n,r))}async function St(t){let e=new en({name:"torque-mcp",version:"0.3.5"}),r=Y(t);await r.auth.loadPersistedToken(),T(e,Oe,r),T(e,Ue,r),T(e,Me,r),T(e,Fe,r),T(e,Qe,r),T(e,Be,r),T(e,We,r),T(e,Ge,r),T(e,Ye,r),T(e,Je,r),T(e,Xe,r),T(e,Ze,r),T(e,At,r),T(e,et,r),T(e,tt,r),T(e,rt,r),T(e,nt,r),T(e,vt,r),T(e,bt,r),T(e,wt,r),T(e,Tt,r),T(e,ht,r),T(e,yt,r),T(e,Et,r),e.registerPrompt(z.name,z.config,z.handler),Rt(e),_t(e),It(e),kt(e);let n=new tn;await e.connect(n)}var{action:xt,params:nn}=ue(process.argv.slice(2));xt==="help"&&(le(),process.exit(0));xt==="version"&&(de(),process.exit(0));var on=pe(nn);St(on).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.3.1",
3
+ "version": "0.3.5",
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",