@torque-labs/mcp 0.3.5 → 0.4.1
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.
- package/README.md +17 -1
- package/dist/index.js +102 -56
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -142,11 +142,17 @@ You can provide either an auth token or a private key, or authenticate at runtim
|
|
|
142
142
|
| `create_instruction` | Add an instruction to an already-uploaded IDL |
|
|
143
143
|
| `list_idls` | List uploaded IDLs and their tracked instructions |
|
|
144
144
|
|
|
145
|
+
### Dune Event Sources
|
|
146
|
+
|
|
147
|
+
| Tool | Description |
|
|
148
|
+
| ----------------------------- | --------------------------------------------------------------------------------------------- |
|
|
149
|
+
| `register_dune_event_source` | Register a saved Dune query as a daily-ingested event source on the active project. Requires the Dune MCP installed and the query saved as non-temp. Preview first, then set `confirmed: true` to execute. |
|
|
150
|
+
|
|
145
151
|
### Incentive Query Building
|
|
146
152
|
|
|
147
153
|
| Tool | Description |
|
|
148
154
|
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
149
|
-
| `generate_incentive_query` | Generate SQL for any incentive source — token swaps, bonding curve trades, token holds, custom events,
|
|
155
|
+
| `generate_incentive_query` | Generate SQL for any incentive source — token swaps, bonding curve trades, token holds, custom events, IDL instructions, or registered Dune queries |
|
|
150
156
|
| `preview_incentive_query` | CREATION-TIME ONLY — test/validate a query against real indexed data before creating an incentive (not for live results) |
|
|
151
157
|
|
|
152
158
|
### Recurring Incentives
|
|
@@ -225,6 +231,16 @@ For on-chain program interactions not covered by token actions.
|
|
|
225
231
|
4. `preview_incentive_query` — (optional) validate results
|
|
226
232
|
5. `create_recurring_incentive` — create the reward program
|
|
227
233
|
|
|
234
|
+
### Dune Query Incentive
|
|
235
|
+
|
|
236
|
+
For activity tracked by a saved Dune Analytics query. Requires the [Dune MCP](https://docs.dune.com/mcp) installed alongside this server. The Dune query must be saved (non-temp) and return exactly one day of results per run, scoped by a single `DATE`-cast string parameter.
|
|
237
|
+
|
|
238
|
+
1. `register_dune_event_source` — register the Dune query on the active project (preview, then `confirmed: true`)
|
|
239
|
+
2. Wait for at least one daily ingestion run to populate indexed data
|
|
240
|
+
3. `generate_incentive_query` with `source: "dune_query"` and `duneEventQueryId` set to the id returned by step 1 — build the SQL query
|
|
241
|
+
4. `preview_incentive_query` — (optional) validate results
|
|
242
|
+
5. `create_recurring_incentive` — create the reward program
|
|
243
|
+
|
|
228
244
|
## Development
|
|
229
245
|
|
|
230
246
|
### Setup
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
function
|
|
2
|
+
function me(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 fe(){let t=`
|
|
3
3
|
Torque MCP Server \u2014 connect AI assistants to the Torque incentive platform on Solana.
|
|
4
4
|
|
|
5
5
|
Usage:
|
|
@@ -25,59 +25,71 @@ 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
|
|
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
|
|
28
|
+
`.trimStart();process.stderr.write(t)}function he(){process.stderr.write(`0.4.1
|
|
29
|
+
`)}var Qt="https://server.torque.so",Bt="https://platform.torque.so",Wt="https://ingest.torque.so",Gt="Sign in to Torque";function ge(t){let e=t.apiUrl??process.env.TORQUE_API_URL??Qt,r=t.platformUrl??process.env.TORQUE_PLATFORM_URL??Bt,n=t.ingesterUrl??process.env.TORQUE_INGESTER_URL??Wt,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:Gt,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 Vt,getBase58Encoder as Ht,getTransactionDecoder as Kt,getTransactionEncoder as Yt,partiallySignTransaction as zt,signBytes as Jt}from"@solana/kit";async function ye(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 Vt(r);return{address:n.address,async signMessage(i){let o=await Jt(n.keyPair.privateKey,i);return new Uint8Array(o)},async signTransaction(i){let o=Kt(),s=Yt(),u=o.decode(i),c=await zt([n.keyPair],u);return new Uint8Array(s.encode(c))}}}import Q from"fs/promises";import B from"path";var W=class{token=null;authToken=null;baseUrl;cacheDir;constructor(e,r){this.baseUrl=e,this.cacheDir=r||B.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=B.join(this.cacheDir,"token.json");try{await Q.unlink(e)}catch{}}async loadPersistedToken(){let e=B.join(this.cacheDir,"token.json");try{let r=await Q.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 Q.mkdir(this.cacheDir,{recursive:!0});let n=B.join(this.cacheDir,"token.json"),i={token:e,publicKey:r,timestamp:Date.now()};await Q.writeFile(n,JSON.stringify(i,null,2))}};function J(t){let e=new W(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 ye(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 mr}from"zod";import{getBase58Decoder as fr}from"@solana/kit";import{z as a}from"zod";var Ln=a.enum(["SUCCESS","BAD_REQUEST","NOT_AUTHORIZED","FORBIDDEN","NOT_FOUND","INTERNAL_ERROR"]);function b(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 b(a.array(t),e)}var On=a.enum(["TOKENS","SOL","POINTS"]),Un=a.enum(["DIRECT","MANUAL","CSV","FORMULA","RAFFLE"]),Xt=a.enum(["AIRDROP","CLAIM"]),Zt=a.enum(["EQUAL_CHANCES","WEIGHTED_BY_METRIC"]),ve=a.enum(["ALLOCATION","PERCENTAGE"]),er=a.enum(["UPCOMING","EVALUATING","CLAIMING","COMPLETED","FAILED"]),tr=a.enum(["DISTRIBUTED","PENDING","FAILED"]),rr=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")})]),nr=a.discriminatedUnion("distributionType",[a.object({distributionType:a.literal("DIRECT"),audienceId:a.string().nullish(),csvFile:a.string().nullish()}),a.object({distributionType:a.literal("MANUAL"),valueType:ve,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:ve.optional(),csvFile:a.string()}),a.object({distributionType:a.literal("FORMULA"),customFormula:a.string()}),a.object({distributionType:a.literal("RAFFLE"),selectionLogic:Zt,prizeBuckets:a.array(a.object({amount:a.number(),count:a.number()})),allowDuplicateWinners:a.boolean().optional()})]),ir=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()})]),or=a.object({totalFundAmount:a.number(),maxPerParticipant:a.number().nullish(),distributionMethod:Xt,claimWindowStart:a.coerce.date().optional(),claimWindowDuration:a.number(),timezoneOffset:a.number().optional()}),sr=nr.and(ir).and(or),ar=a.object({title:a.string(),description:a.string().optional(),image:a.string().optional(),primitive:rr.nullish()}),ur=a.object({id:a.string(),recurringOfferId:a.string().nullable(),epochNumber:a.number(),status:er,evalStart:a.coerce.date(),evalEnd:a.coerce.date(),claimStart:a.coerce.date(),claimEnd:a.coerce.date(),sqlQueryVersionId:a.string(),evalQueryFrequency:a.number(),distributionConfig:sr,offerMetadata:ar,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()}),cr=ur.extend({sqlQuery:a.object({id:a.string(),name:a.string()}).nullish()}),C=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(cr).optional()}),lr=a.object({walletAddress:a.string(),metricValue:a.number()}),be=a.object({results:a.array(lr),total:a.number(),offset:a.number(),limit:a.number(),lastUpdated:a.coerce.date().nullable()}),dr=a.object({walletAddress:a.string(),allocatedAmount:a.number(),distributedAmount:a.number().nullable(),status:tr}),we=a.object({recipients:a.array(dr),total:a.number(),offset:a.number(),limit:a.number()}),q=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()}),Ee=a.object({id:a.string(),name:a.string().nullable(),key:a.string(),isActive:a.boolean(),lastUsedAt:a.coerce.date().nullable(),createdAt:a.coerce.date()}),Te=a.object({id:a.string(),name:a.string().nullable(),key:a.string()}),Ae=a.object({id:a.string(),name:a.string().optional()}),Re=a.object({queryId:a.string()}),_e=a.object({status:a.string(),preview:a.string().nullish(),error:a.string().nullish(),totalRows:a.number().nullish(),duration:a.number().nullish()}),Ie=a.object({url:a.string()}),j=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()}),Se=j.extend({projects:a.array(a.object({id:a.string(),projectId:a.string(),customEventId:a.string(),createdAt:a.coerce.date()}))}),pr=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()}),G=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(pr).nullish(),createdAt:a.coerce.date(),updatedAt:a.coerce.date()}),De=a.object({id:a.string(),instructionIds:a.array(a.string()).optional()}),xe=a.object({id:a.string()}),ke=a.object({addresses:a.object({programAddress:a.string().optional(),tokenAddress:a.string().optional()}).optional(),files:a.record(a.string(),a.string()).optional()});function X(t){if(t.length!==0)return t.reduce((e,r)=>r.epochNumber>e.epochNumber?r:e)}var k="\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 Pe(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
|
|
33
|
-
`)}function
|
|
32
|
+
`)}function Ce(t){let e=["\u{1F4CB} **New Project Preview**",k,"",` 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("",k,"\u26A0\uFE0F Confirm to proceed."),e.join(`
|
|
33
|
+
`)}function $e(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
|
|
35
|
+
`)}function Ne(t,e){if(t.length===0)return["\u{1F4CB} **No custom events found**","","Use `create_custom_event` to create one."].join(`
|
|
36
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
|
|
38
|
-
`)}function
|
|
39
|
-
`)}function
|
|
37
|
+
`)}function qe(t){let e=t.fields,r=["\u{1F4CB} **New Custom Event Preview**",k,"",` 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.","",k,"\u26A0\uFE0F Confirm to proceed."),r.join(`
|
|
38
|
+
`)}function je(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 Z(t,e){let r=t.fields,n=["\u{1F4CB} **New Dune Event Source Preview**",k,"",` Dune Query ID: ${t.duneQueryId}`,` Query Name: ${t.queryName}`,` Wallet Column: ${t.userPubkeyField}`];t.txHashField!==void 0&&n.push(` Tx Hash Column: ${t.txHashField===null?"disabled":t.txHashField}`),t.timestampField!==void 0&&n.push(` Timestamp Col: ${t.timestampField===null?"disabled":t.timestampField}`),t.usdValueField!==void 0&&n.push(` USD Value Col: ${t.usdValueField===null?"disabled":t.usdValueField}`),t.dateParamName!==void 0&&n.push(` Date Param: ${t.dateParamName===null?"disabled":t.dateParamName}`),n.push("",` Tracked Fields (${r.length}):`);for(let i of r)n.push(` ${i.duneColumn} \u2192 \`${i.fieldName}\` (${i.type})`);if(n.push("","\u2139\uFE0F Once registered, Torque's ingestion engine will execute this Dune query daily."),e&&e.length>0){n.push(""),n.push("\u26A0\uFE0F **Preferred fields not configured:**");for(let i of e)n.push(` \u2022 ${i}`);n.push(""),n.push("Without these, incentive queries based on this source will be harder to build effectively. You can fill them in now, or set `acknowledgeIncompleteConfig: true` to proceed anyway.")}return n.push("",k,"\u26A0\uFE0F Confirm to proceed."),n.join(`
|
|
40
|
+
`)}function Le(t,e){return["\u2705 Dune event source registered successfully.","",` Project: ${e}`,` Source ID: ${t.id}`,` Dune Query ID: ${t.duneQueryId}`,` Query Name: ${t.queryName}`,"","Torque's ingestion engine will execute this Dune query daily \u2014 no additional setup required."].join(`
|
|
41
|
+
`)}function Oe(t,e){let r=["\u274C **Cannot register Dune event source \u2014 preferred fields are missing.**","","The following preferred fields are not configured:",""];for(let n of t)r.push(` \u2022 ${n}`);return r.push(""),r.push("Without these fields, incentive queries built from this source will be significantly harder to construct effectively."),r.push(""),r.push("**Options:**"),r.push(" 1. Provide the missing field names to build a complete config, then set `confirmed: true`."),r.push(" 2. If you are intentionally leaving these disabled, set both `confirmed: true` and `acknowledgeIncompleteConfig: true` to proceed."),r.push(""),r.push(k),r.push(Z(e,t)),r.join(`
|
|
42
|
+
`)}function Ue(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
43
|
`);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
|
|
42
|
-
`)}function
|
|
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=
|
|
44
|
-
`)}function
|
|
45
|
-
`)}function
|
|
46
|
-
`)}var
|
|
44
|
+
`)}function Me(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(`
|
|
45
|
+
`)}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(`
|
|
46
|
+
`);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=X(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(`
|
|
47
|
+
`)}function Qe(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=X(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(`
|
|
48
|
+
`)}function Be(t){let e={leaderboard:"Leaderboard Competition",rebate:"Rebate",raffle:"Raffle",direct:"Direct Distribution"},r=t.type,n=["\u{1F4CB} **New Incentive Preview**",k,"",` 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("",k,"\u26A0\uFE0F Confirm to proceed."),n.join(`
|
|
49
|
+
`)}var We={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:mr.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=fr().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}}}},Ge={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
50
|
|
|
48
|
-
`+L(e.config.platformUrl)}]}:{content:[{type:"text",text:L(e.config.platformUrl)}]}},
|
|
51
|
+
`+L(e.config.platformUrl)}]}:{content:[{type:"text",text:L(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 hr}from"zod";function y(t){return t.auth.getToken()?null:{content:[{type:"text",text:L(t.config.platformUrl)}],isError:!0}}function T(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 He={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(Ee,n);return{content:[{type:"text",text:Ue(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}}}},Ke={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:hr.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=b(Te,i);return{content:[{type:"text",text:Me(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 P}from"zod";var gr=.05,Ye={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:P.string().describe("Project name"),description:P.string().optional().describe("Project description"),image:P.string().optional().describe("Project image URL"),defaultTokenAddress:P.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:P.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:P.array(P.string()).optional().describe("Wallet addresses to add as project admins"),confirmed:P.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:Ce(t)}]};try{let n={name:t.name,protocolFee:gr};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=b(q,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}}}},ze={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(q,n);return{content:[{type:"text",text:Pe(i,e.activeProject?.id)}]}}catch(n){return{content:[{type:"text",text:`Failed to list projects: ${n instanceof Error?n.message:String(n)}`}],isError:!0}}}},Je={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:P.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=b(q,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 v}from"zod";var Xe="tokenswap_partitioned",Ze="launchlabtrade_partitioned",et="mplxgenesislaunchpooldepositwithdraw_partitioned",V="tokenbalance_partitioned",yr=[/'/,/"/,/;/,/--/,/\/\*/,/\\/];function $(t,e){for(let r of yr)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
52
|
|
|
50
53
|
\`\`\`sql
|
|
51
54
|
${t.sql}
|
|
52
55
|
\`\`\`
|
|
53
56
|
|
|
54
|
-
Use this query with \`create_recurring_incentive\` to set up the incentive.`},{type:"text",text:JSON.stringify({sql:t.sql,...t.metadata})}]}}function
|
|
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?`
|
|
57
|
+
Use this query with \`create_recurring_incentive\` to set up the incentive.`},{type:"text",text:JSON.stringify({sql:t.sql,...t.metadata})}]}}function ee(t){return Array.isArray(t)?t:t.split(`
|
|
58
|
+
`).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 te={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 vr(t,e){if(!(t in te))return`Unknown type "${t}". Valid types: leaderboard, rebate, raffle, direct. See the torque://incentive-types resource for details.`;let r=te[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 br(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=ee(e.allocations),c=e.customFormula;o.customFormula=c||"N",o.totalFundAmount=u.reduce((l,d)=>l+d.amount,0)}return o}var tt={DAILY:1,WEEKLY:7,MONTHLY:30};function wr(t){if(t.evalDurationDays)return t.evalDurationDays;let e=t.interval;return e&&e in tt?tt[e]:null}var rt={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:v.string().describe("Display name for the recurring incentive"),description:v.string().optional().describe("Optional description"),type:v.string().describe("Incentive type: 'leaderboard', 'rebate', 'raffle', or 'direct'. Determines the reward distribution shape and what additional inputs are required."),emissionType:v.enum(["TOKENS","SOL"]).describe("Type of reward emission"),tokenAddress:v.string().optional().describe("SPL token mint address (required if emissionType is TOKENS)"),tokenDecimals:v.number().optional().describe("Token decimal places (required if emissionType is TOKENS)"),totalFundAmount:v.number().describe("Total reward pool per epoch (ignored for raffle \u2014 calculated from buckets)"),evalDurationDays:v.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:v.enum(["DAILY","WEEKLY","MONTHLY"]).optional().describe("Shorthand for evalDurationDays: DAILY=1, WEEKLY=7, MONTHLY=30. Either this or evalDurationDays must be provided."),startDate:v.string().describe("When the first epoch's evaluation period begins (ISO 8601 date string)"),maxIterations:v.number().optional().describe("Maximum number of epochs to run. Omit for unlimited."),maxPerParticipant:v.number().optional().describe("Maximum reward per user per epoch. Useful for capping rewards."),rebatePercentage:v.number().optional().describe("Rebate percentage (required if type is 'rebate'). E.g. 5 for 5%."),raffleBuckets:v.array(v.object({amount:v.number(),count:v.number()})).optional().describe("Prize tiers for raffle (required if type is 'raffle'). Array of {amount, count}."),raffleWeighting:v.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:v.union([v.string(),v.array(v.object({address:v.string(),amount:v.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:v.string().optional().describe("Custom SQL query generated by generate_incentive_query. When provided, replaces the default stub query."),customEventId:v.string().optional().describe("ID of the custom event this incentive is based on. Mutually exclusive with instructionId."),instructionId:v.string().optional().describe("ID of the IDL instruction this incentive is based on. Mutually exclusive with customEventId."),customFormula:v.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:v.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=T(e);if(n)return n;let i=t.type,o=vr(i,t);if(o)return{content:[{type:"text",text:o}],isError:!0};let s=wr(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:ee(t.allocations)}),{content:[{type:"text",text:Be(l)}]}}let u=te[i],c=e.activeProject.id;try{if(i==="direct"&&t.allocations){let F=ee(t.allocations).map(pe=>`('${$(pe.address,"address")}', ${pe.amount})`).join(", ");t.sqlQuery=`SELECT address, value FROM (VALUES ${F}) 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=b(Ae,l),p=br(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=b(C,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}}}},nt={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=T(e);if(n)return n;let i=e.activeProject.id;try{let o=await e.apiRequest(`/project/${i}/recurring-offer`),s=I(C,o);return{content:[{type:"text",text:Fe(s)}]}}catch(o){return{content:[{type:"text",text:`Failed to list recurring incentives: ${o instanceof Error?o.message:String(o)}`}],isError:!0}}}},it={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:v.string().describe("The recurring incentive ID")},handler:async(t,e)=>{let r=y(e);if(r)return r;let n=T(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=b(C,s);return{content:[{type:"text",text:Qe(u)}]}}catch(s){return{content:[{type:"text",text:`Failed to get recurring incentive: ${s instanceof Error?s.message:String(s)}`}],isError:!0}}}},ot={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:v.string().describe("The recurring incentive ID")},handler:async(t,e)=>{let r=y(e);if(r)return r;let n=T(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=b(v.record(v.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 x}from"zod";var st={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:x.string().describe("Identifier for the custom event"),name:x.string().describe("Display name for the custom event"),confirmed:x.boolean().optional().describe("Set to true to confirm and execute the creation. Omit to preview first."),fields:x.array(x.object({fieldName:x.string().describe("Field name"),type:x.enum(["string","boolean","number"]).describe("Field data type"),label:x.string().optional().describe("Display label for the field"),description:x.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:qe(t)}]};try{let i=await e.apiRequest("/events",{method:"POST",body:{eventName:t.eventName,name:t.name,fields:n}}),o=b(j,i),s=je(e.config.ingesterUrl,t.eventName,n,e.config.platformUrl),u=e.activeProject,c=u?`
|
|
56
59
|
|
|
57
60
|
\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}}}},
|
|
61
|
+
${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}}}},at={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=T(e);if(n)return n;let i=e.activeProject.id;try{let o=await e.apiRequest(`/projects/${i}/events`),s=I(j,o);return{content:[{type:"text",text:$e(s)}]}}catch(o){return{content:[{type:"text",text:`Failed to list project events: ${o instanceof Error?o.message:String(o)}`}],isError:!0}}}},ut={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(Se,n),s=I(q,i),u=new Map;for(let c of s)u.set(c.id,c.name);return{content:[{type:"text",text:Ne(o,u)}]}}catch(n){return{content:[{type:"text",text:`Failed to list custom events: ${n instanceof Error?n.message:String(n)}`}],isError:!0}}}},ct={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:x.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=T(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 re="customevent_partitioned",Er=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 ne(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Tr(t){return t.replace(/\\/g,"\\\\").replace(/'/g,"''")}function lt(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${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 Ar=new Set(["SUM","AVG"]);function dt(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${ne(u.fieldName)}\\b`).test(s)&&Ar.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 pt(t,e){let r=[],n=[],i=dt(e.valueExpression,t);r.push(...i);let{resolved:o}=lt(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=dt(h,t);r.push(...w);let{resolved:R}=lt(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" = '${Tr(t.id)}'`,'"receivedAt" BETWEEN {{startDate}} AND {{endDate}}',...c],m=`SELECT
|
|
59
62
|
${p.join(`,
|
|
60
63
|
`)}
|
|
61
|
-
FROM ${
|
|
64
|
+
FROM ${re}
|
|
62
65
|
WHERE ${f.join(`
|
|
63
66
|
AND `)}`;return l&&(m+=`
|
|
64
67
|
GROUP BY "userPubkey"`),m+=`
|
|
65
68
|
ORDER BY value ${d}`,e.limit!==void 0&&e.limit>0&&(m+=`
|
|
66
|
-
LIMIT ${e.limit}`),{sql:m,warnings:n}}var
|
|
67
|
-
`)}function
|
|
68
|
-
`)}var
|
|
69
|
+
LIMIT ${e.limit}`),{sql:m,warnings:n}}var Rr=/\b(INSERT|UPDATE|DELETE|DROP|ALTER|TRUNCATE|CREATE)\b/i;function mt(t,e){let r=[],n=[];/^\s*SELECT\b/i.test(t)||r.push("Query must be a SELECT statement."),Rr.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."),/["']?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;)Er.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 _r(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 Ir(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 Sr(t,e){let r=t.name.toLowerCase(),n=Ir(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(`
|
|
70
|
+
`)}function ft(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:** ${_r(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(Sr(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(`
|
|
71
|
+
`)}var ie="mapped_custom_instruction",Dr=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 oe(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function xr(t){return t.replace(/\\/g,"\\\\").replace(/'/g,"''")}function ht(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${oe(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${oe(s)}\\b`,"g"),`"${c}"`)}return{resolved:o,referencedFields:r}}var kr=new Set(["SUM","AVG"]);function gt(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${oe(u.fieldName)}\\b`).test(s)&&kr.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 yt(t,e){let r=[],n=[],i=gt(e.valueExpression,t.instruction);r.push(...i);let{resolved:o}=ht(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=gt(h,t.instruction);r.push(...w);let{resolved:R}=ht(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" = '${xr(t.id)}'`,'"receivedAt" BETWEEN {{startDate}} AND {{endDate}}',...c],m=`SELECT
|
|
69
72
|
${p.join(`,
|
|
70
73
|
`)}
|
|
71
|
-
FROM ${
|
|
74
|
+
FROM ${ie}
|
|
72
75
|
WHERE ${f.join(`
|
|
73
76
|
AND `)}`;return l&&(m+=`
|
|
74
77
|
GROUP BY "feePayer"`),m+=`
|
|
75
|
-
ORDER BY value ${d}`,{sql:m,warnings:n}}var
|
|
76
|
-
`)}function
|
|
77
|
-
`)}var
|
|
78
|
-
|
|
79
|
-
`)}
|
|
80
|
-
|
|
78
|
+
ORDER BY value ${d}`,{sql:m,warnings:n}}var Pr=/\b(INSERT|UPDATE|DELETE|DROP|ALTER|TRUNCATE|CREATE)\b/i;function vt(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(ie)||r.push(`Query must reference the "${ie}" 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;)Dr.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 Cr(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 bt(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:** ${Cr(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($r(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(`
|
|
79
|
+
`)}function $r(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(`
|
|
80
|
+
`)}var se="dune_query_results",Nr=new Set(["id","platformId","duneQueryId","queryName","projectId","userPubkey","partitionDate","txHash","eventTimestamp","usdValue",...Array.from({length:10},(t,e)=>`num_val_${e+1}`),...Array.from({length:5},(t,e)=>`str_val_${e+1}`),...Array.from({length:5},(t,e)=>`bool_val_${e+1}`),"createdAt"]);function ae(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function qr(t){return t.replace(/\\/g,"\\\\").replace(/'/g,"''")}function wt(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})}e.usdValueField&&n.set("usdValue",{column:"usdValue",type:"number"});let i=Array.from(n.keys()).sort((s,u)=>u.length-s.length),o=t;for(let s of i)if(new RegExp(`\\b${ae(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${ae(s)}\\b`,"g"),`"${c}"`)}return{resolved:o,referencedFields:r}}var jr=new Set(["SUM","AVG"]);function Et(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${ae(u.fieldName)}\\b`).test(s)&&jr.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 Tt(t,e){let r=[],n=[];r.push(...Et(e.valueExpression,t));let{resolved:i}=wt(e.valueExpression,t),o=/^\s*[\d.]+\s*$/.test(e.valueExpression),s=/\bCOUNT\s*\(\s*\*\s*\)/i.test(e.valueExpression);if(!o&&!s&&i===e.valueExpression){let m=/\b(num_val_|str_val_|bool_val_)\d+\b/.test(i),h=/"?usdValue"?/.test(i);!m&&!h&&n.push(`Value expression "${e.valueExpression}" did not match any known fields. Available fields: ${t.fields.map(w=>w.fieldName).join(", ")}`+(t.usdValueField?", usdValue":""))}let u=[];if(e.filters)for(let m of e.filters){r.push(...Et(m,t));let{resolved:h}=wt(m,t);u.push(h)}if(r.length>0)return{errors:r};let c=e.groupByPubkey!==!1,l=e.orderBy||"DESC",d=[' "userPubkey" AS address',` ${i} AS value`],p=[`"platformId" = '${qr(t.id)}'`,'"eventTimestamp" BETWEEN {{startDate}} AND {{endDate}}',...u],f=`SELECT
|
|
81
|
+
${d.join(`,
|
|
82
|
+
`)}
|
|
83
|
+
FROM ${se}
|
|
84
|
+
WHERE ${p.join(`
|
|
85
|
+
AND `)}`;return c&&(f+=`
|
|
86
|
+
GROUP BY "userPubkey"`),f+=`
|
|
87
|
+
ORDER BY value ${l}`,e.limit!==void 0&&e.limit>0&&(f+=`
|
|
88
|
+
LIMIT ${e.limit}`),{sql:f,warnings:n}}var Lr=/\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."),Lr.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(se)||r.push(`Query must reference the "${se}" 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.'),/["']?platformId["']?\s*=/i.test(t)||r.push(`Query must include a "platformId" filter for query scoping. Add: AND "platformId" = '${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;)Nr.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 Rt(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",`Dune Query: ${e.queryName} (Dune ID: ${e.duneQueryId})`];n?.isRawQuery?i.push("","**What this does:** Custom SQL against the registered Dune query results."):n?.valueExpression&&i.push("",`**What this does:** Evaluates \`${n.valueExpression}\` per wallet from "${e.queryName}" results.`),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(e.usdValueField&&i.push(" usdValue (number) \u2192 usdValue [top-level column]"),r.length>0){i.push("","\u26A0\uFE0F Warnings:");for(let o of r)i.push(` - ${o}`)}return 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(`
|
|
89
|
+
`)}var _t={raydium_launchlab:{table:Ze,identifierColumn:"poolState",identifierParam:"poolState",amountColumn:"amountIn",volumeExpression:'SUM("amountIn")',directionMap:{buy:"buy",sell:"sell"},label:"Raydium LaunchLab"},metaplex_genesis:{table:et,identifierColumn:"genesisAccount",identifierParam:"genesisAccount",amountColumn:"amountQuoteToken",volumeExpression:'SUM(COALESCE("usdAmount", "uiAmountQuoteToken"))',directionMap:{buy:"deposit",sell:"withdraw"},label:"Metaplex Genesis"}};function Or(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 ${V}`,` WHERE "mint" = '${t}'`,` AND "amount" >= ${e}`,' AND "createdAt" BETWEEN {{startDate}} AND {{endDate}}',") sub","WHERE rn = 1","ORDER BY value DESC"].join(`
|
|
90
|
+
`)}function Ur(t,e){return['SELECT "owner" AS address, COUNT(DISTINCT DATE("createdAt")) AS value',`FROM ${V}`,`WHERE "mint" = '${t}'`,` AND "amount" >= ${e}`,' AND "createdAt" BETWEEN {{startDate}} AND {{endDate}}','GROUP BY "owner"',"ORDER BY value DESC"].join(`
|
|
91
|
+
`)}function Mr(t,e){return['SELECT "owner" AS address, COUNT(*) AS value',`FROM ${V}`,`WHERE "mint" = '${t}'`,` AND "amount" >= ${e}`,' AND "createdAt" BETWEEN {{startDate}} AND {{endDate}}','GROUP BY "owner"',"ORDER BY value DESC"].join(`
|
|
92
|
+
`)}var Fr={balance:Or,duration:Ur,count:Mr},Qr={balance:"latest qualifying token balance per wallet",duration:"number of distinct days with qualifying balance",count:"number of qualifying balance records"},ue={swap:["volume","count"],bonding_curve:["volume","count"],hold:["balance","duration","count"]};function g(t){return{isError:!0,content:[{type:"text",text:`\u274C ${t}`}]}}function Br(t){let e=t.tokenMint;if(!e)return g("tokenMint is required for swap source.");let r=t.direction??"buy",n=t.minUsdAmount,i=t.protocol,o=t.measure;try{$(e,"tokenMint"),i&&$(i,"protocol")}catch(p){return g(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 ${Xe}`,`WHERE ${c.join(`
|
|
81
93
|
AND `)}`,'GROUP BY "feePayer"',"ORDER BY value DESC"].join(`
|
|
82
94
|
`),d=`\u2705 Generated swap query for token \`${e}\`
|
|
83
95
|
|
|
@@ -85,79 +97,97 @@ ORDER BY value ${d}`,{sql:m,warnings:n}}var br=/\b(INSERT|UPDATE|DELETE|DROP|ALT
|
|
|
85
97
|
**Measure:** ${o}${o==="volume"?" (USD)":" (transactions)"}
|
|
86
98
|
`+(n!==void 0?`**Min USD per swap:** ${n}
|
|
87
99
|
`:"")+(i?`**Protocol:** ${i}
|
|
88
|
-
`:"**Protocol:** all");return H({description:d,sql:l,metadata:{source:"swap",tokenMint:e,direction:r,measure:o,minUsdAmount:n,protocol:i}})}function
|
|
100
|
+
`:"**Protocol:** all");return H({description:d,sql:l,metadata:{source:"swap",tokenMint:e,direction:r,measure:o,minUsdAmount:n,protocol:i}})}function Wr(t){let e=t.launchpad;if(!e||!(e in _t))return g("launchpad is required for bonding_curve source. Use 'raydium_launchlab' or 'metaplex_genesis'.");let r=_t[e],n=e==="raydium_launchlab"?t.poolState:t.genesisAccount;if(!n)return g(`${r.identifierParam} is required for ${r.label}. This identifies the specific token's bonding curve.`);try{$(n,r.identifierParam)}catch(p){return g(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
101
|
AND `)}`,'GROUP BY "feePayer"',"ORDER BY value DESC"].join(`
|
|
90
102
|
`),d=`\u2705 Generated bonding curve query for ${r.label}
|
|
91
103
|
|
|
92
104
|
**${r.identifierParam}:** \`${n}\`
|
|
93
105
|
**Direction:** ${i}${i!=="both"?` (mapped to '${r.directionMap[i]??i}')`:""}
|
|
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
|
|
106
|
+
**Measure:** ${s}`;return H({description:d,sql:l,metadata:{source:"bonding_curve",launchpad:e,[r.identifierParam]:n,direction:i,measure:s,minAmountIn:o}})}function Gr(t){let e=t.tokenMint;if(!e)return g("tokenMint is required for hold source.");let r=t.minBalance;if(r==null)return g("minBalance is required for hold source.");if(r<=0)return g("minBalance must be greater than 0. A hold incentive without a minimum balance threshold would include all wallets.");let n=t.measure;try{$(e,"tokenMint")}catch(s){return g(s.message)}let i=Fr[n](e,r),o=`\u2705 Generated hold query for token \`${e}\`
|
|
95
107
|
|
|
96
108
|
**Min balance:** ${r} (raw amount)
|
|
97
|
-
**Measure:** ${n} \u2014 ${
|
|
109
|
+
**Measure:** ${n} \u2014 ${Qr[n]}`;return H({description:o,sql:i,metadata:{source:"hold",tokenMint:e,minBalance:r,measure:n}})}async function Vr(t,e){let r=t.customEventId;if(!r)return g("customEventId is required for custom_event source.");let n=e.activeProject.id,i;try{let d=await e.apiRequest(`/projects/${n}/events`),f=I(j,d).find(m=>m.id===r);if(!f)return g("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 g("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 g(`Failed to fetch project events: ${d instanceof Error?d.message:String(d)}`)}let o=t.rawQuery,s=t.valueExpression;if(o&&s)return g('Provide either "valueExpression" or "rawQuery", not both.');if(!o&&!s)return g(`Provide either "valueExpression" (structured mode) or "rawQuery" (raw SQL mode).
|
|
98
110
|
|
|
99
111
|
**Structured mode examples:**
|
|
100
112
|
- valueExpression: "SUM(amount)" \u2014 total per user
|
|
101
113
|
- valueExpression: "COUNT(*)" \u2014 event count per user
|
|
102
114
|
- valueExpression: "1" \u2014 constant value for everyone
|
|
103
115
|
|
|
104
|
-
**Raw SQL mode:** Provide a full SELECT query with {{startDate}} and {{endDate}} parameters.`);let u,c;if(o){let d=
|
|
116
|
+
**Raw SQL mode:** Provide a full SELECT query with {{startDate}} and {{endDate}} parameters.`);let u,c;if(o){let d=mt(o,i);if(!d.valid)return g(`SQL query validation failed:
|
|
105
117
|
`+d.errors.map(p=>` - ${p}`).join(`
|
|
106
118
|
`)+(d.warnings.length>0?`
|
|
107
119
|
|
|
108
120
|
\u26A0\uFE0F Warnings:
|
|
109
121
|
`+d.warnings.map(p=>` - ${p}`).join(`
|
|
110
|
-
`):""));u=o,c=d.warnings}else{let d=
|
|
122
|
+
`):""));u=o,c=d.warnings}else{let d=pt(i,{valueExpression:s,filters:t.filters,groupByPubkey:t.groupByPubkey,orderBy:t.orderBy,limit:t.limit});if("errors"in d)return g(`Query build failed:
|
|
111
123
|
`+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:
|
|
124
|
+
`));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:ft(u,i,c,l)}]}}async function Hr(t,e){let r=t.instructionId;if(!r)return g("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(G,p),m,h;for(let w of f){if(!w.instructions)continue;let R=w.instructions.find(F=>F.id===r);if(R){m=R,h=w;break}}if(!m||!h)return g("Instruction not found on this project. Use `list_idls` to see available IDLs and their instructions.");i=m,o=h}catch(p){return g(`Failed to fetch project IDLs: ${p instanceof Error?p.message:String(p)}`)}let s=t.rawQuery,u=t.valueExpression;if(s&&u)return g('Provide either "valueExpression" or "rawQuery", not both.');if(!s&&!u)return g(`Provide either "valueExpression" (structured mode) or "rawQuery" (raw SQL mode).
|
|
113
125
|
|
|
114
126
|
**Structured mode examples:**
|
|
115
127
|
- valueExpression: "SUM(${i.instruction[0]?.fieldName??"field"})" \u2014 total per user
|
|
116
128
|
- valueExpression: "COUNT(*)" \u2014 instruction count per user
|
|
117
129
|
- valueExpression: "1" \u2014 constant value for everyone
|
|
118
130
|
|
|
119
|
-
**Raw SQL mode:** Provide a full SELECT query with {{startDate}} and {{endDate}} parameters.`);let c,l;if(s){let p=
|
|
131
|
+
**Raw SQL mode:** Provide a full SELECT query with {{startDate}} and {{endDate}} parameters.`);let c,l;if(s){let p=vt(s,i);if(!p.valid)return g(`SQL query validation failed:
|
|
120
132
|
`+p.errors.map(f=>` - ${f}`).join(`
|
|
121
133
|
`)+(p.warnings.length>0?`
|
|
122
134
|
|
|
123
135
|
\u26A0\uFE0F Warnings:
|
|
124
136
|
`+p.warnings.map(f=>` - ${f}`).join(`
|
|
125
|
-
`):""));c=s,l=p.warnings}else{let p=
|
|
137
|
+
`):""));c=s,l=p.warnings}else{let p=yt(i,{valueExpression:u,filters:t.filters,groupByFeePayer:t.groupByFeePayer,orderBy:t.orderBy});if("errors"in p)return g(`Query build failed:
|
|
126
138
|
`+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:
|
|
139
|
+
`));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:bt(c,i,o,l,d)}]}}async function Kr(t,e){let r=t.duneEventQueryId;if(!r)return g("duneEventQueryId is required for dune_query source. Use `register_dune_event_source` to register a Dune query first, then pass the returned id here.");let n=e.activeProject.id,i;try{let d=await e.apiRequest(`/projects/${n}/dune-queries`),f=(Array.isArray(d)?d:d.data??[]).find(m=>m.id===r);if(!f)return g("Dune event query not found on this project. Use `register_dune_event_source` to register one first.");if(!f.columnMapping||Object.keys(f.columnMapping).length===0)return g("This Dune query does not have column mappings yet. Dune queries need to be ingested at least once before incentive queries can be generated.");i=f}catch(d){return g(`Failed to fetch Dune event queries: ${d instanceof Error?d.message:String(d)}`)}let o=t.rawQuery,s=t.valueExpression;if(o&&s)return g('Provide either "valueExpression" or "rawQuery", not both.');if(!o&&!s){let d=i.fields.map(p=>p.fieldName).join(", ");return g(`Provide either "valueExpression" (structured mode) or "rawQuery" (raw SQL mode).
|
|
140
|
+
|
|
141
|
+
**Structured mode examples:**
|
|
142
|
+
- valueExpression: "SUM(${i.fields.find(p=>p.type==="number")?.fieldName??"field"})" \u2014 total per user
|
|
143
|
+
- valueExpression: "COUNT(*)" \u2014 row count per user
|
|
144
|
+
- valueExpression: "SUM(usdValue)" \u2014 total USD per user (if usdValueField is set)
|
|
145
|
+
- valueExpression: "1" \u2014 constant value for everyone
|
|
146
|
+
|
|
147
|
+
Available fields: ${d}${i.usdValueField?", usdValue":""}
|
|
148
|
+
|
|
149
|
+
**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 g(`SQL query validation failed:
|
|
150
|
+
`+d.errors.map(p=>` - ${p}`).join(`
|
|
151
|
+
`)+(d.warnings.length>0?`
|
|
152
|
+
|
|
153
|
+
\u26A0\uFE0F Warnings:
|
|
154
|
+
`+d.warnings.map(p=>` - ${p}`).join(`
|
|
155
|
+
`):""));u=o,c=d.warnings}else{let d=Tt(i,{valueExpression:s,filters:t.filters,groupByPubkey:t.groupByPubkey,orderBy:t.orderBy,limit:t.limit});if("errors"in d)return g(`Query build failed:
|
|
156
|
+
`+d.errors.map(p=>` - ${p}`).join(`
|
|
157
|
+
`));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,duneEventQueryId:i.id,duneQueryId:i.duneQueryId,queryName:i.queryName})}]}:{content:[{type:"text",text:Rt(u,i,c,l)}]}}var It={name:"generate_incentive_query",description:"Generate a SQL query for any incentive source \u2014 token swaps, bonding curve trades, token holds, custom events, IDL instructions, or registered Dune queries. 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, IDL instruction, and Dune query sources require prior setup and at least one ingestion run. 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","dune_query"]).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. dune_query uses results from a Dune Analytics query registered via `register_dune_event_source`."),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."),duneEventQueryId:A.string().optional().describe("Torque-side DuneEventQuery ID (required for dune_query) \u2014 the `id` returned by `register_dune_event_source`, not the numeric Dune query ID."),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, dune_query). Omit to preview.")},handler:async(t,e)=>{let r=y(e);if(r)return r;let n=T(e);if(n)return n;let i=t.source;if(i in ue){let o=t.measure;if(!o)return g(`measure is required for ${i} source.`);if(!ue[i].includes(o))return g(`Invalid measure '${o}' for ${i} source. Allowed: ${ue[i].join(", ")}`)}switch(i){case"swap":return Br(t);case"bonding_curve":return Wr(t);case"hold":return Gr(t);case"custom_event":return Vr(t,e);case"idl_instruction":return Hr(t,e);case"dune_query":return Kr(t,e);default:return g(`Unknown source: ${i}`)}}};import{z as ce}from"zod";var Yr=5e3,St=6e4,zr=3,Jr=20;function Xr(){let t=new Date,e=new Date(Date.UTC(t.getUTCFullYear(),t.getUTCMonth(),t.getUTCDate())),r=new Date(e);return r.setUTCDate(r.getUTCDate()-zr),{startDate:r.toISOString(),endDate:e.toISOString()}}function Zr(t){return new Promise(e=>setTimeout(e,t))}function en(t,e){let r=t.trim().split(`
|
|
128
158
|
`);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
|
|
159
|
+
`)}async function tn(t,e,r){let n=Date.now();for(;Date.now()-n<St;){let i=await t.apiRequest(`/project/${e}/query/${r}/result`),o=b(_e,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 Zr(Yr)}return{status:"TIMEOUT",preview:null,error:null,totalRows:null,duration:null,timedOut:!0}}var Dt={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:ce.string().describe("The SQL query to execute and preview. Typically from generate_incentive_query output."),startDate:ce.string().optional().describe("ISO 8601 start date for the query window. Default: midnight 3 days ago."),endDate:ce.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=T(e);if(n)return n;let i=e.activeProject.id,o=t.sqlQuery,s=Xr(),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=b(Re,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 tn(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 ${St/1e3}s.
|
|
130
160
|
|
|
131
161
|
**Query ID:** \`${l}\`
|
|
132
162
|
|
|
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=
|
|
163
|
+
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=en(m,Jr),{content:[{type:"text",text:`\u2705 Query preview complete
|
|
134
164
|
|
|
135
165
|
**Results:** ${p} rows`+(f!==null?` (${(f/1e3).toFixed(1)}s)`:"")+`
|
|
136
166
|
**Date range:** ${u} \u2192 ${c}
|
|
137
167
|
|
|
138
|
-
`+h},{type:"text",text:JSON.stringify({queryId:l,totalRows:p,duration:f,preview:m,startDate:u,endDate:c})}]}}};import{z as
|
|
139
|
-
`)}var
|
|
168
|
+
`+h},{type:"text",text:JSON.stringify({queryId:l,totalRows:p,duration:f,preview:m,startDate:u,endDate:c})}]}}};import{z as Y}from"zod";import an from"fs/promises";var rn=new Set(["u8","u16","u32","u64","u128","i8","i16","i32","i64","i128","f32","f64"]),nn=new Set(["string","publicKey","pubkey","bytes"]);function on(t){if(rn.has(t))return"number";if(nn.has(t))return"string";if(t==="bool")return"boolean"}function le(t){if(typeof t=="string")return t;if(typeof t=="object"&&t!==null){let e=t.name;if(typeof e=="string")return e}}function U(t){if(typeof t=="string")return t;if(typeof t=="object"&&t!==null){let e=t;if("defined"in e)return le(e.defined)??"unknown";if("option"in e)return`option<${U(e.option)}>`;if("vec"in e)return`vec<${U(e.vec)}>`;if("array"in e){let r=e.array;return`array<${U(r[0])}>`}}return"unknown"}function O(t,e,r,n=new Set){if(typeof e=="string"){let i=on(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 O(t,i.option,r,n).map(s=>({...s,optional:!0}));if("vec"in i){let o=U(i.vec);return[{path:t,type:"string",optional:!1,idlType:`vec<${o}>`}]}if("array"in i){let o=i.array,s=U(o[0]);return[{path:t,type:"string",optional:!1,idlType:`array<${s}>`}]}if("defined"in i){let o=le(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=O(`${t}.${p.name}`,p.type,r,u);c.push(...f)}else{let p=O(t,d,r,u);c.push(...p)}}return c}}}return[{path:t,type:"string",optional:!1,idlType:String(e)}]}var N=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 O(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 sn={number:15,string:15,boolean:10,accounts:30};function K(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=sn[n];i>o&&r.push({type:n,count:i,max:o})}return{name:t.name,counts:e,withinLimits:r.length===0,exceeded:r}}function de(t,e){let r=e.map(K),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(`
|
|
169
|
+
`)}var xt={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:Y.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:Y.record(Y.string(),Y.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 an.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 N;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=de(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
170
|
|
|
141
|
-
${s}`},{type:"text",text:JSON.stringify({programName:o.name,programAddress:o.address,instructions:o.instructions.map(c=>({name:c.name,analysis:
|
|
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
|
|
171
|
+
${s}`},{type:"text",text:JSON.stringify({programName:o.name,programAddress:o.address,instructions:o.instructions.map(c=>({name:c.name,analysis:K(c),fields:c.fields,accounts:c.accounts}))})}]}}};import{z as _}from"zod";import un from"fs/promises";function cn(t){return t.replace(/_/g," ").replace(/\b\w/g,e=>e.toUpperCase())}var kt={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=T(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 un.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 N;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:cn(m.instructionName),fields:m.fields,accounts:m.accounts})));try{let m=await e.apiRequest(`/projects/${p}/idl/create`,{method:"POST",body:f}),h=b(De,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(`
|
|
172
|
+
`)},{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 D}from"zod";function ln(t){return t.replace(/_/g," ").replace(/\b\w/g,e=>e.toUpperCase())}var Pt={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:D.string().describe("ID of the existing IDL to add the instruction to. Use `list_idls` to find this."),instructionName:D.string().describe("Name of the instruction from the IDL (e.g. 'buy_exact_in')."),label:D.string().optional().describe("Display label for the instruction. Defaults to title-cased instruction name."),fields:D.array(D.object({fieldName:D.string().describe("Dot-path field name (e.g. 'amount' or 'config.feeRate')"),type:D.enum(["string","number","boolean"]).describe("Field data type"),label:D.string().nullish().describe("Display label"),description:D.string().nullish().describe("Field description")})).describe("Fields to track for this instruction. Use `parse_idl` to discover available fields."),accounts:D.array(D.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=T(e);if(n)return n;let i=e.activeProject.id,o=t.idlId,s=t.instructionName,u=t.label??ln(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=b(xe,d).id;return{content:[{type:"text",text:`\u2705 Instruction "${s}" created successfully on the IDL.
|
|
143
173
|
\u{1F4C1} Instruction ID: \`${f}\`
|
|
144
174
|
|
|
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
|
|
175
|
+
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 Ct={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=T(e);if(n)return n;let i=e.activeProject.id;try{let o=await e.apiRequest(`/projects/${i}/idl`),s=I(G,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
176
|
`)}]};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
|
|
177
|
+
`)},{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 $t={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=T(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=b(ke,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
178
|
|
|
149
179
|
${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
|
|
180
|
+
`)}`}]},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 M}from"zod";var Nt={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
181
|
|
|
152
182
|
Modes:
|
|
153
183
|
- "preview" (default): Scores/rankings \u2014 who is leading and metric values; latest epoch or specific epochConfigId.
|
|
154
184
|
- "recipients": Allocations, payout amounts, distribution status (DISTRIBUTED/PENDING/FAILED); requires epochConfigId or auto-selects latest CLAIMING/COMPLETED epoch.
|
|
155
185
|
- "download": Signed S3 CSV URL (5 min expiry) for full data export with all columns.
|
|
156
186
|
|
|
157
|
-
Use \`get_recurring_incentive\` for epoch IDs/statuses, \`list_recurring_incentives\` for incentive IDs.`,inputSchema:{recurringOfferId:
|
|
158
|
-
`)}]}}async function
|
|
159
|
-
`)}]}}async function
|
|
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
|
|
187
|
+
Use \`get_recurring_incentive\` for epoch IDs/statuses, \`list_recurring_incentives\` for incentive IDs.`,inputSchema:{recurringOfferId:M.string().describe("The recurring incentive ID. Use `list_recurring_incentives` to find it."),mode:M.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:M.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:M.number().optional().describe("Max rows to return (default: 50). Applies to preview and recipients modes."),offset:M.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=T(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 mn(e,i,o,u):s==="recipients"?await pn(e,i,o,u,c,l):await dn(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 dn(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=b(be,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(`
|
|
188
|
+
`)}]}}async function pn(t,e,r,n,i,o){let s=n;if(!s){let p=await t.apiRequest(`/project/${e}/recurring-offer/${r}`),h=[...b(C,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=b(we,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(`
|
|
189
|
+
`)}]}}async function mn(t,e,r,n){let i=await t.apiRequest(`/project/${e}/recurring-offer/${r}`),s=b(C,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=b(Ie,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(`
|
|
190
|
+
`)}]}}import{z as S}from"zod";var fn=S.object({duneQueryId:S.number().int().positive().describe("Dune query ID to register."),queryName:S.string().trim().min(1).describe("Human-friendly name for this Dune query source."),fields:S.array(S.object({duneColumn:S.string().trim().min(1).describe("Column name returned by the Dune query result."),fieldName:S.string().trim().min(1).regex(/^[a-z0-9_]+$/,"Must be lowercase alphanumeric with underscores only").describe("Tracked field name to store in Torque (lowercase_with_underscores)."),type:S.enum(["number","string","boolean"]).describe("Tracked field type for this Dune column.")})).min(1).describe("Tracked value fields to persist from the Dune query result."),userPubkeyField:S.string().trim().min(1).describe("Which Dune result column contains the user wallet address/public key."),txHashField:S.string().trim().min(1).nullable().optional().describe("Dune result column containing the transaction hash. Should only be null for queries that produce aggregated results."),timestampField:S.string().trim().min(1).describe("Dune result column containing the event timestamp. If the query produces aggregated results, this field should be filled with the date of aggregation."),usdValueField:S.string().trim().min(1).nullable().optional().describe("Dune result column containing a USD value. Should be filled in \u2014 set null only to disable USD tracking (requires acknowledgeIncompleteConfig)."),dateParamName:S.string().trim().min(1).describe("Dune query parameter name for date scoping (e.g. `date`). Must be filled in to work with incentives. The parameter is passed as a string and must be cast to a DATE inside the SQL. The Dune query must return exactly ONE day of results per run \u2014 the ingester passes a single target date per daily execution. Preferred filter (when the table has a date partition column): `WHERE <date_partition_col> = TRY_CAST({{<dateParamName>}} AS DATE)`. Fallback (timestamp-only tables): `WHERE <ts_col> >= TRY_CAST({{<dateParamName>}} AS TIMESTAMP) AND <ts_col> < TRY_CAST({{<dateParamName>}} AS TIMESTAMP) + INTERVAL '1' DAY`. Do NOT use sliding windows, `BETWEEN` ranges, `- INTERVAL '1' DAY` offsets, or hard-coded dates."),confirmed:S.boolean().optional().describe("Set to true to confirm and execute the registration. Omit to preview first."),acknowledgeIncompleteConfig:S.boolean().optional().describe("Required when any of txHashField, timestampField, usdValueField, or dateParamName is null/omitted. Warn the user first \u2014 incomplete configurations make incentive query building significantly harder.")}),hn={txHashField:"Transaction hash column",timestampField:"Event timestamp column",usdValueField:"USD value column",dateParamName:"Date query parameter"};function gn(t){let e=[];for(let[r,n]of Object.entries(hn)){let i=t[r];i==null&&e.push(n)}return e}var qt={name:"register_dune_event_source",description:"Register a Dune query as an event source on the active project to be executed daily by Torque's ingestion engine. PREREQUISITE: The user must have the Dune MCP installed; if not, point them to the Dune MCP setup for their assistant before proceeding. PREREQUISITE: The Dune query must be saved as non-temp (`is_temp: false`) before registering \u2014 if temp, update it via the Dune MCP first. Use `fields` to map Dune result columns to typed tracked values; wallet, tx hash, timestamp, and USD columns are configured via dedicated parameters. The Dune query must return exactly one day of results per run, scoped by a single string date parameter that is cast to a DATE inside the SQL (see `dateParamName`). Omit `confirmed` to preview first, then set `confirmed: true` to execute.",requires:["auth","activeProject"],inputSchema:fn.shape,handler:async(t,e)=>{let r=y(e);if(r)return r;let n=T(e);if(n)return n;let i=t;if(!i.fields?.length)return{content:[{type:"text",text:"\u274C Dune event source must track at least one field."}],isError:!0};let o=gn(i);if(!i.confirmed)return{content:[{type:"text",text:Z(i,o)}]};if(o.length>0&&!i.acknowledgeIncompleteConfig)return{content:[{type:"text",text:Oe(o,i)}],isError:!0};let s=e.activeProject.id;try{let u=await e.apiRequest(`/projects/${s}/dune-queries`,{method:"POST",body:{duneQueryId:i.duneQueryId,queryName:i.queryName,fields:i.fields,userPubkeyField:i.userPubkeyField,...i.txHashField!==void 0?{txHashField:i.txHashField}:{},...i.timestampField!==void 0?{timestampField:i.timestampField}:{},...i.usdValueField!==void 0?{usdValueField:i.usdValueField}:{},...i.dateParamName!==void 0?{dateParamName:i.dateParamName}:{}}});return{content:[{type:"text",text:Le({id:u.id,duneQueryId:u.duneQueryId??i.duneQueryId,queryName:u.queryName??i.queryName},e.activeProject.name)},{type:"text",text:JSON.stringify({id:u.id,duneQueryId:u.duneQueryId??i.duneQueryId,queryName:u.queryName??i.queryName,projectId:s})}]}}catch(u){return{content:[{type:"text",text:`\u274C Failed to register Dune event source: ${u instanceof Error?u.message:String(u)}`}],isError:!0}}}};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, custom instruction, or Dune query."},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.
|
|
161
191
|
|
|
162
192
|
**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.
|
|
163
193
|
|
|
@@ -195,6 +225,15 @@ For tracking specific on-chain program instructions not covered by token actions
|
|
|
195
225
|
2. Use an existing instruction, or upload an IDL: \`parse_idl\` \u2192 \`create_idl\` / \`create_instruction\`
|
|
196
226
|
3. Generate query: \`generate_incentive_query\` with \`source: "idl_instruction"\`
|
|
197
227
|
|
|
228
|
+
### d) Dune Query (arbitrary SQL via Dune Analytics \u2014 requires Dune query registration)
|
|
229
|
+
For activity measured by a Dune query: cross-chain data, complex SQL, or anything not indexed natively by Torque. Registered queries are ingested into the \`dune_query_results\` table and become a first-class event source.
|
|
230
|
+
|
|
231
|
+
**Prerequisite:** The user must have the Dune MCP installed. If not, point them to the Dune MCP setup for their assistant before proceeding.
|
|
232
|
+
|
|
233
|
+
1. Register the Dune query: \`register_dune_event_source\` (preview first, then confirm). Map result columns to tracked fields and fill in \`txHashField\`, \`timestampField\`, \`usdValueField\`, and \`dateParamName\` \u2014 skipping any requires \`acknowledgeIncompleteConfig\` and makes query building harder
|
|
234
|
+
2. Validate the Dune query via the Dune MCP \u2014 \`executeQueryById\` with a recent date for the configured \`dateParamName\`, then \`getExecutionResults\`. Confirm the execution succeeds and returns rows with the expected columns. If it errors or returns no rows, fix the Dune query (via the Dune MCP) before proceeding
|
|
235
|
+
3. Generate query: \`generate_incentive_query\` with \`source: "dune_query"\`
|
|
236
|
+
|
|
198
237
|
## Step 2: Generate the Query
|
|
199
238
|
|
|
200
239
|
Call \`generate_incentive_query\` with the appropriate \`source\` parameter and source-specific params. The tool returns a SQL query that will power the incentive.
|
|
@@ -252,6 +291,7 @@ Not all measures work with all actions. Use this matrix:
|
|
|
252
291
|
| Hold | no | no | YES | YES |
|
|
253
292
|
| Custom Event | depends | YES | no | no |
|
|
254
293
|
| Custom Instruction | depends | YES | no | no |
|
|
294
|
+
| Dune Query | YES | YES | YES | YES |
|
|
255
295
|
|
|
256
296
|
The measure determines what the \`value\` column in the query represents:
|
|
257
297
|
- **Leaderboard** \u2014 value is used for ranking; actual payouts come from \`customFormula\` (using RANK, N, TOTAL_REWARD_POOL, etc.)
|
|
@@ -279,8 +319,9 @@ Call \`create_recurring_incentive\` with \`confirmed: false\` first to preview.
|
|
|
279
319
|
|
|
280
320
|
- **Auth expired mid-flow:** Call \`check_auth_status\` then \`authenticate\` again before retrying the failed step.
|
|
281
321
|
- **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\`.
|
|
322
|
+
- **Dune source not ready:** If \`generate_incentive_query\` with \`source: "dune_query"\` errors because the source is missing or empty, re-validate the Dune query via the Dune MCP (\`executeQueryById\` + \`getExecutionResults\`) \u2014 a successful run returning rows for the configured \`dateParamName\` is the gate. Fix the Dune query if the execution fails or returns no rows. If the user hasn't installed the Dune MCP, \`register_dune_event_source\` is unusable; direct them to install it first.
|
|
282
323
|
- **Preview looks wrong:** Go back to Step 2 and adjust the query parameters (filters, measure, direction).
|
|
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
|
|
324
|
+
- **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 yn='# 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### Path D: Dune Query Incentive\nFor activity measured by a Dune Analytics query (cross-chain, complex SQL, or data not yet indexed by Torque).\n\n**Prerequisite:** The user must have the Dune MCP installed \u2014 if not, point them to the Dune MCP setup for their assistant before proceeding.\n\n1. **Select project** \u2192 `list_projects` + `set_active_project`\n2. **Register the Dune query** \u2192 `register_dune_event_source` (preview first, then confirm). Ensure the Dune query is saved as non-temp (`is_temp: false`) before registering \u2014 if temp, update it via the Dune MCP first. Map result columns to tracked fields and fill in txHashField, timestampField, usdValueField, and dateParamName \u2014 skipping any of these requires `acknowledgeIncompleteConfig` and makes query building harder. The Dune query must return exactly one day of results per run, filtered by a single string date parameter cast to DATE \u2014 prefer `WHERE <date_partition> = TRY_CAST({{<dateParamName>}} AS DATE)`, or a half-open range on a timestamp column. No sliding windows, `BETWEEN`, or hard-coded dates.\n3. **Validate the Dune query** \u2014 via the Dune MCP (`executeQueryById` with a recent date for the configured `dateParamName`, then `getExecutionResults`). Confirm the execution succeeds and returns rows with the expected columns. If it errors or returns no rows, fix the Dune query before proceeding\n4. **Generate query** \u2192 `generate_incentive_query` with `source: "dune_query"`\n5. **(Optional) Preview** \u2192 `preview_incentive_query` to validate results with real data\n6. **Configure recipe** \u2192 `create_recurring_incentive`\n7. **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- Dune query sources require the Dune MCP to be installed on the user\'s assistant, and the query itself must execute successfully and return rows (validate via the Dune MCP before generating)\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 jt(t){t.registerResource("workflows-guide","torque://workflows",{description:"Guide to common Torque workflows \u2014 token incentives (swap, hold, bonding curve), custom event incentives, IDL instruction incentives, and Dune query 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:yn}]}))}var vn=`# Torque MCP \u2014 Capabilities
|
|
284
325
|
|
|
285
326
|
## What is Torque MCP?
|
|
286
327
|
|
|
@@ -330,6 +371,11 @@ Most tools also require an **active project** \u2014 call \`list_projects\` then
|
|
|
330
371
|
| \`create_instruction\` | Add an instruction to an already-uploaded IDL | auth, project |
|
|
331
372
|
| \`list_idls\` | List uploaded IDLs and their tracked instructions | auth, project |
|
|
332
373
|
|
|
374
|
+
### Dune Event Sources
|
|
375
|
+
| Tool | Description | Requires |
|
|
376
|
+
|------|-------------|----------|
|
|
377
|
+
| \`register_dune_event_source\` | Register a Dune query as a project-scoped Dune event source config | auth, project |
|
|
378
|
+
|
|
333
379
|
### Incentive Query Builder
|
|
334
380
|
| Tool | Description | Requires |
|
|
335
381
|
|------|-------------|----------|
|
|
@@ -384,7 +430,7 @@ Most tools also require an **active project** \u2014 call \`list_projects\` then
|
|
|
384
430
|
| Check payout/distribution status | \`get_incentive_results\` with mode: "recipients" |
|
|
385
431
|
| Download/export full results CSV | \`get_incentive_results\` with mode: "download" |
|
|
386
432
|
| Build a landing page | Read \`torque://building-landing-pages\` resource |
|
|
387
|
-
`;function
|
|
433
|
+
`;function Lt(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:vn}]}))}var bn='# 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 Ot(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:bn}]}))}var wn=`# Torque \u2014 Building Landing Pages
|
|
388
434
|
|
|
389
435
|
How leaderboards, claims, epochs, and results connect, and how to build a landing page that displays incentive data and enables users to claim rewards.
|
|
390
436
|
|
|
@@ -603,4 +649,4 @@ Claims are **fully server-side** \u2014 no transaction signing is required from
|
|
|
603
649
|
**Frontend** handles wallet connection and all public endpoints \u2014 eval results display, offer info, claim triggering, and claim status polling. No secrets needed.
|
|
604
650
|
|
|
605
651
|
**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.
|
|
606
|
-
`;function
|
|
652
|
+
`;function Ut(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:wn}]}))}function An(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 E(t,e,r){t.registerTool(e.name,{description:An(e),inputSchema:e.inputSchema},async n=>e.handler(n,r))}async function Mt(t){let e=new En({name:"torque-mcp",version:"0.4.1"}),r=J(t);await r.auth.loadPersistedToken(),E(e,We,r),E(e,Ge,r),E(e,Ve,r),E(e,He,r),E(e,Ke,r),E(e,Ye,r),E(e,ze,r),E(e,Je,r),E(e,rt,r),E(e,nt,r),E(e,it,r),E(e,ot,r),E(e,Nt,r),E(e,st,r),E(e,at,r),E(e,ut,r),E(e,ct,r),E(e,xt,r),E(e,kt,r),E(e,Pt,r),E(e,Ct,r),E(e,It,r),E(e,Dt,r),E(e,$t,r),E(e,qt,r),e.registerPrompt(z.name,z.config,z.handler),jt(e),Lt(e),Ot(e),Ut(e);let n=new Tn;await e.connect(n)}var{action:Ft,params:Rn}=me(process.argv.slice(2));Ft==="help"&&(fe(),process.exit(0));Ft==="version"&&(he(),process.exit(0));var _n=ge(Rn);Mt(_n).catch(t=>{console.error("Failed to start Torque MCP server:",t),process.exit(1)});
|
package/package.json
CHANGED