@wzrd_sol/solana-agent-plugin 0.1.0 → 0.1.2

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.
@@ -7,9 +7,73 @@
7
7
  * Flow: check claimable amount → relay claim → receive CCM.
8
8
  */
9
9
 
10
- import type { WzrdClient } from '../client.js';
10
+ import { z } from 'zod';
11
+ import type { Action, SolanaAgentKit } from 'solana-agent-kit';
11
12
 
12
- export const CLAIM_ACTION = {
13
+ import { formatCcm, getClaimsData, getClientForAgent } from '../runtime.js';
14
+
15
+ export const claimSchema = z.object({
16
+ execute: z.boolean().optional(),
17
+ });
18
+
19
+ export async function claimHandler(
20
+ agent: SolanaAgentKit,
21
+ input: z.infer<typeof claimSchema>,
22
+ ): Promise<Record<string, unknown>> {
23
+ const claims = await getClaimsData(agent);
24
+ const claimable = claims.cumulative_total - claims.claimed_total;
25
+
26
+ if (claimable <= 0) {
27
+ return {
28
+ success: true,
29
+ text:
30
+ `No CCM to claim. Cumulative: ${claims.cumulative_total}, ` +
31
+ `already claimed: ${claims.claimed_total}. ` +
32
+ `Deposit into markets and wait for the next scoring cycle.`,
33
+ data: claims,
34
+ };
35
+ }
36
+
37
+ if (input.execute === false) {
38
+ return {
39
+ success: true,
40
+ text:
41
+ `${formatCcm(claimable)} CCM claimable ` +
42
+ `(${formatCcm(claims.cumulative_total)} cumulative, ` +
43
+ `${formatCcm(claims.claimed_total)} claimed). ` +
44
+ `Root seq: ${claims.root_seq}. Call with execute=true to claim.`,
45
+ data: claims,
46
+ };
47
+ }
48
+
49
+ const result = await getClientForAgent(agent).claimRelay();
50
+
51
+ if (result.status === 'already_claimed') {
52
+ return {
53
+ success: true,
54
+ text:
55
+ `Already claimed through root ${result.root_seq}. ` +
56
+ `Claimed total: ${formatCcm(result.claimed_total ?? claims.claimed_total)} CCM.`,
57
+ data: result,
58
+ };
59
+ }
60
+
61
+ return {
62
+ success: true,
63
+ text:
64
+ `Claimed CCM via gasless relay. ` +
65
+ `Cumulative total: ${formatCcm(result.cumulative_total)}. ` +
66
+ `Root seq: ${result.root_seq}. ` +
67
+ `Tx: ${result.tx_sig?.slice(0, 16)}...`,
68
+ data: {
69
+ ...result,
70
+ claimable_before: claimable,
71
+ explorer: result.tx_sig ? `https://solscan.io/tx/${result.tx_sig}` : null,
72
+ },
73
+ };
74
+ }
75
+
76
+ export const CLAIM_ACTION: Action = {
13
77
  name: 'wzrd_claim',
14
78
  similes: ['wzrd_harvest', 'claim_ccm', 'redeem_rewards', 'collect_yield'],
15
79
  description:
@@ -22,77 +86,16 @@ export const CLAIM_ACTION = {
22
86
  examples: [
23
87
  [
24
88
  {
25
- user: 'user',
26
- content: { text: 'Claim my CCM rewards' },
27
- },
28
- {
29
- user: 'assistant',
30
- content: {
31
- text: 'Claimed 100,000 CCM via gasless relay. ' +
32
- 'Cumulative total: 250,000 CCM. Tx: 2VKz...J39c',
33
- },
34
- },
35
- ],
36
- [
37
- {
38
- user: 'user',
39
- content: { text: 'How much CCM can I claim?' },
40
- },
41
- {
42
- user: 'assistant',
43
- content: {
44
- text: 'You have 100,000 CCM claimable (250,000 cumulative, 150,000 already claimed). ' +
45
- 'Shall I claim it now?',
89
+ input: { execute: false },
90
+ output: {
91
+ success: true,
92
+ claimable: 100000,
93
+ cumulative_total: 250000,
46
94
  },
95
+ explanation: 'Check how much CCM is available before triggering a relay claim.',
47
96
  },
48
97
  ],
49
98
  ],
50
-
51
- validate: async () => true,
52
-
53
- handler: async (client: WzrdClient, params: { execute?: boolean }) => {
54
- // 1. Check claimable amount
55
- const claims = await client.getClaims();
56
- const claimable = claims.cumulative_total - claims.claimed_total;
57
-
58
- if (claimable <= 0) {
59
- return {
60
- success: true,
61
- text:
62
- `No CCM to claim. Cumulative: ${claims.cumulative_total}, ` +
63
- `already claimed: ${claims.claimed_total}. ` +
64
- `Deposit into markets and wait for the next scoring cycle.`,
65
- data: claims,
66
- };
67
- }
68
-
69
- // 2. If just checking (not executing), report the amount
70
- if (params.execute === false) {
71
- return {
72
- success: true,
73
- text:
74
- `${claimable} CCM claimable ` +
75
- `(${claims.cumulative_total} cumulative, ${claims.claimed_total} claimed). ` +
76
- `Root seq: ${claims.root_seq}. Call with execute=true to claim.`,
77
- data: claims,
78
- };
79
- }
80
-
81
- // 3. Execute gasless relay claim
82
- const result = await client.claimRelay();
83
-
84
- return {
85
- success: true,
86
- text:
87
- `Claimed CCM via gasless relay. ` +
88
- `Cumulative total: ${result.cumulative_total}. ` +
89
- `Root seq: ${result.root_seq}. ` +
90
- `Tx: ${result.tx_sig.slice(0, 16)}...`,
91
- data: {
92
- ...result,
93
- claimable_before: claimable,
94
- explorer: `https://solscan.io/tx/${result.tx_sig}`,
95
- },
96
- };
97
- },
99
+ schema: claimSchema,
100
+ handler: async (agent, input) => claimHandler(agent, claimSchema.parse(input)),
98
101
  };
@@ -9,189 +9,221 @@
9
9
  */
10
10
 
11
11
  import {
12
- Connection,
13
- Keypair,
14
12
  ComputeBudgetProgram,
15
13
  TransactionMessage,
16
14
  VersionedTransaction,
17
15
  } from '@solana/web3.js';
18
- import {
19
- createDepositMarketIx,
20
- fetchOnChainPosition,
21
- fetchTokenBalance,
22
- getAta,
23
- TOKEN_PROGRAM_ID,
24
- } from '@wzrd_sol/sdk';
16
+ import { z } from 'zod';
17
+ import type { Action, SolanaAgentKit } from 'solana-agent-kit';
25
18
 
26
- import type { WzrdClient } from '../client.js';
19
+ import { formatUsdc, getWalletPublicKey } from '../runtime.js';
27
20
 
28
21
  const USDC_MINT = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';
29
22
  const CONFIRM_TIMEOUT_MS = 60_000;
30
23
 
31
- export interface DepositParams {
32
- market_id: number;
33
- amount_usdc: number;
34
- priority_fee?: number; // microLamports per CU, default 50_000
35
- }
24
+ type WzrdSdk = typeof import('@wzrd_sol/sdk');
25
+
26
+ export const depositSchema = z.object({
27
+ market_id: z.number().int().min(1),
28
+ amount_usdc: z.number().positive().max(100),
29
+ priority_fee: z.number().int().positive().optional(),
30
+ });
31
+
32
+ export async function depositHandler(
33
+ agent: SolanaAgentKit,
34
+ input: z.infer<typeof depositSchema>,
35
+ ): Promise<Record<string, unknown>> {
36
+ const { market_id, amount_usdc, priority_fee = 50_000 } = input;
37
+ const connection = agent.connection;
38
+ const payer = getWalletPublicKey(agent);
39
+ const amountNative = BigInt(Math.round(amount_usdc * 1_000_000));
40
+ const t0 = Date.now();
41
+ const sdk = await loadSdk();
42
+ const {
43
+ createDepositMarketIx,
44
+ fetchMarketVault,
45
+ fetchOnChainPosition,
46
+ fetchTokenBalance,
47
+ getAta,
48
+ TOKEN_PROGRAM_ID,
49
+ } = sdk;
50
+
51
+ const vault = await fetchMarketVault(connection, market_id);
52
+ if (!vault) {
53
+ return {
54
+ success: false,
55
+ text:
56
+ `Market ${market_id} is listed but does not have an on-chain vault yet. ` +
57
+ 'Pick a market with an initialized vault before depositing.',
58
+ data: { market_id, reason: 'missing_vault' },
59
+ };
60
+ }
36
61
 
37
- export const DEPOSIT_ACTION = {
38
- name: 'wzrd_deposit',
39
- similes: ['wzrd_invest', 'deposit_usdc', 'buy_attention', 'enter_market'],
40
- description:
41
- 'Deposit USDC into a WZRD attention market to mint vLOFI tokens. ' +
42
- 'vLOFI represents your position in the market. As the underlying AI model ' +
43
- 'gains attention (downloads, stars), your multiplier increases and you earn ' +
44
- 'more CCM on each merkle root publication. ' +
45
- 'Requires: market_id (from leaderboard) and amount_usdc (e.g., 0.01). ' +
46
- 'The deposit is atomic — USDC is transferred and vLOFI is minted in one transaction.',
62
+ const existing = await fetchOnChainPosition(connection, payer, market_id);
63
+ if (existing && existing.depositedAmount > 0n && !existing.settled) {
64
+ return {
65
+ success: false,
66
+ text:
67
+ `Position already exists in market ${market_id} ` +
68
+ `(${formatUsdc(existing.depositedAmount)} USDC deposited). Cannot double-deposit.`,
69
+ data: { market_id, deposited_amount: existing.depositedAmount.toString() },
70
+ };
71
+ }
47
72
 
48
- examples: [
49
- [
50
- {
51
- user: 'user',
52
- content: { text: 'Deposit 0.01 USDC into market 10' },
53
- },
54
- {
55
- user: 'assistant',
56
- content: {
57
- text: 'Deposited 0.01 USDC into Market #10 (Qwen 2.5 72B). ' +
58
- 'Received 10,000 vLOFI. Tx: X7FG...Cziv (772ms, 50687 CU)',
59
- },
60
- },
61
- ],
62
- [
63
- {
64
- user: 'user',
65
- content: { text: 'Invest in the highest velocity market' },
66
- },
67
- {
68
- user: 'assistant',
69
- content: {
70
- text: 'Looking up leaderboard... Top market is #16 (Qwen 3.5 35B) ' +
71
- 'with 804K velocity. Depositing 0.01 USDC...',
72
- },
73
+ const usdcAta = getAta(payer, vault.depositMint, TOKEN_PROGRAM_ID);
74
+ const usdcBalance = await fetchTokenBalance(connection, usdcAta);
75
+ if (usdcBalance < amountNative) {
76
+ return {
77
+ success: false,
78
+ text: `Insufficient USDC: have ${formatUsdc(usdcBalance)}, need ${amount_usdc.toFixed(4)}`,
79
+ data: {
80
+ market_id,
81
+ required_native: amountNative.toString(),
82
+ balance_native: usdcBalance.toString(),
73
83
  },
74
- ],
75
- ],
76
-
77
- validate: async (_client: WzrdClient, params: DepositParams) => {
78
- if (!params.market_id || params.market_id < 1) return false;
79
- if (!params.amount_usdc || params.amount_usdc <= 0) return false;
80
- if (params.amount_usdc > 100) return false; // safety cap
81
- return true;
82
- },
83
-
84
- handler: async (
85
- _client: WzrdClient,
86
- params: DepositParams,
87
- connection: Connection,
88
- wallet: Keypair,
89
- ) => {
90
- const { market_id, amount_usdc, priority_fee = 50_000 } = params;
91
- const amountNative = BigInt(Math.round(amount_usdc * 1_000_000));
92
- const t0 = Date.now();
93
-
94
- // 1. Idempotency — check existing position
95
- const existing = await fetchOnChainPosition(
96
- connection,
97
- wallet.publicKey,
98
- market_id,
99
- );
100
- if (existing && existing.depositedAmount > 0n && !existing.settled) {
101
- return {
102
- success: false,
103
- text: `Position already exists in market ${market_id} ` +
104
- `(${existing.depositedAmount} deposited). Cannot double-deposit.`,
105
- };
106
- }
107
-
108
- // 2. Balance checks
109
- const { PublicKey } = await import('@solana/web3.js');
110
- const usdcMint = new PublicKey(USDC_MINT);
111
- const usdcAta = getAta(wallet.publicKey, usdcMint, TOKEN_PROGRAM_ID);
112
- const usdcBalance = await fetchTokenBalance(connection, usdcAta);
113
- if (usdcBalance < amountNative) {
114
- return {
115
- success: false,
116
- text: `Insufficient USDC: have ${Number(usdcBalance) / 1e6}, need ${amount_usdc}`,
117
- };
118
- }
119
-
120
- const solBalance = await connection.getBalance(wallet.publicKey);
121
- if (solBalance < 10_000) {
122
- return {
123
- success: false,
124
- text: `Insufficient SOL for tx fees: ${solBalance} lamports`,
125
- };
126
- }
84
+ };
85
+ }
127
86
 
128
- // 3. Build deposit instructions
129
- const ixs = await createDepositMarketIx(
130
- connection,
131
- wallet.publicKey,
132
- market_id,
133
- amountNative,
134
- );
135
-
136
- ixs.unshift(
137
- ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }),
138
- ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority_fee }),
139
- );
140
-
141
- // 4. Simulate
142
- const { blockhash, lastValidBlockHeight } =
143
- await connection.getLatestBlockhash('confirmed');
144
- const message = new TransactionMessage({
145
- payerKey: wallet.publicKey,
146
- recentBlockhash: blockhash,
147
- instructions: ixs,
148
- }).compileToV0Message();
149
-
150
- const tx = new VersionedTransaction(message);
151
- tx.sign([wallet]);
152
-
153
- const sim = await connection.simulateTransaction(tx);
154
- if (sim.value.err) {
87
+ const solBalance = await connection.getBalance(payer);
88
+ if (solBalance < 10_000) {
89
+ return {
90
+ success: false,
91
+ text: `Insufficient SOL for tx fees: ${solBalance} lamports`,
92
+ data: { market_id, sol_balance_lamports: solBalance },
93
+ };
94
+ }
95
+
96
+ let ixs;
97
+ try {
98
+ ixs = await createDepositMarketIx(connection, payer, market_id, amountNative);
99
+ } catch (error) {
100
+ const message = error instanceof Error ? error.message : String(error);
101
+ if (message.includes('MarketVault not found')) {
155
102
  return {
156
103
  success: false,
157
- text: `Simulation failed: ${JSON.stringify(sim.value.err)}`,
158
- data: { logs: sim.value.logs?.slice(-5) },
104
+ text:
105
+ `Market ${market_id} does not have a live vault yet. ` +
106
+ 'Choose a depositable market and retry.',
107
+ data: { market_id, reason: 'missing_vault' },
159
108
  };
160
109
  }
161
-
162
- // 5. Send + confirm
163
- const signature = await connection.sendTransaction(tx, {
164
- skipPreflight: true,
165
- maxRetries: 3,
166
- });
167
-
168
- const confirmed = connection.confirmTransaction(
169
- { signature, blockhash, lastValidBlockHeight },
170
- 'confirmed',
171
- );
172
- const timeout = new Promise<never>((_, reject) =>
173
- setTimeout(
174
- () => reject(new Error(`Timeout after ${CONFIRM_TIMEOUT_MS}ms`)),
175
- CONFIRM_TIMEOUT_MS,
176
- ),
177
- );
178
-
110
+ throw error;
111
+ }
112
+
113
+ ixs.unshift(
114
+ ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }),
115
+ ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority_fee }),
116
+ );
117
+
118
+ const { blockhash, lastValidBlockHeight } =
119
+ await connection.getLatestBlockhash('confirmed');
120
+ const message = new TransactionMessage({
121
+ payerKey: payer,
122
+ recentBlockhash: blockhash,
123
+ instructions: ixs,
124
+ }).compileToV0Message();
125
+
126
+ const tx = new VersionedTransaction(message);
127
+ const signedTx = await agent.wallet.signTransaction(tx);
128
+
129
+ const sim = await connection.simulateTransaction(signedTx);
130
+ if (sim.value.err) {
131
+ return {
132
+ success: false,
133
+ text: `Simulation failed: ${JSON.stringify(sim.value.err)}`,
134
+ data: { market_id, logs: sim.value.logs?.slice(-5) ?? [] },
135
+ };
136
+ }
137
+
138
+ const { signature } = await agent.wallet.signAndSendTransaction(tx, {
139
+ skipPreflight: true,
140
+ maxRetries: 3,
141
+ });
142
+
143
+ const confirmed = connection.confirmTransaction(
144
+ { signature, blockhash, lastValidBlockHeight },
145
+ 'confirmed',
146
+ );
147
+ const timeout = new Promise<never>((_, reject) =>
148
+ setTimeout(
149
+ () => reject(new Error(`Timeout after ${CONFIRM_TIMEOUT_MS}ms`)),
150
+ CONFIRM_TIMEOUT_MS,
151
+ ),
152
+ );
153
+
154
+ try {
179
155
  await Promise.race([confirmed, timeout]);
180
- const elapsed = Date.now() - t0;
181
-
156
+ } catch (error) {
182
157
  return {
183
158
  success: true,
184
159
  text:
185
- `Deposited ${amount_usdc} USDC into Market #${market_id}. ` +
186
- `Tx: ${signature.slice(0, 16)}... (${elapsed}ms, ${sim.value.unitsConsumed} CU)`,
160
+ `Deposit transaction sent for Market #${market_id}, but confirmation timed out. ` +
161
+ `Tx: ${signature}`,
187
162
  data: {
188
163
  signature,
189
164
  market_id,
190
165
  amount_usdc,
191
- cu_used: sim.value.unitsConsumed,
192
- elapsed_ms: elapsed,
166
+ timed_out: true,
193
167
  explorer: `https://solscan.io/tx/${signature}`,
194
168
  },
195
169
  };
196
- },
170
+ }
171
+
172
+ const elapsed = Date.now() - t0;
173
+ return {
174
+ success: true,
175
+ text:
176
+ `Deposited ${amount_usdc.toFixed(4)} USDC into Market #${market_id}. ` +
177
+ `Tx: ${signature.slice(0, 16)}... (${elapsed}ms, ${sim.value.unitsConsumed} CU)`,
178
+ data: {
179
+ signature,
180
+ market_id,
181
+ amount_usdc,
182
+ cu_used: sim.value.unitsConsumed,
183
+ elapsed_ms: elapsed,
184
+ explorer: `https://solscan.io/tx/${signature}`,
185
+ },
186
+ };
187
+ }
188
+
189
+ async function loadSdk(): Promise<WzrdSdk> {
190
+ try {
191
+ return await import('@wzrd_sol/sdk');
192
+ } catch (error) {
193
+ const fallbackUrl = new URL('../../../../sdk/dist/index.js', import.meta.url);
194
+ try {
195
+ return (await import(fallbackUrl.href)) as WzrdSdk;
196
+ } catch {
197
+ throw error;
198
+ }
199
+ }
200
+ }
201
+
202
+ export const DEPOSIT_ACTION: Action = {
203
+ name: 'wzrd_deposit',
204
+ similes: ['wzrd_invest', 'deposit_usdc', 'buy_attention', 'enter_market'],
205
+ description:
206
+ 'Deposit USDC into a WZRD attention market to mint vLOFI tokens. ' +
207
+ 'vLOFI represents your position in the market. As the underlying AI model ' +
208
+ 'gains attention (downloads, stars), your multiplier increases and you earn ' +
209
+ 'more CCM on each merkle root publication. ' +
210
+ 'Requires: market_id (from leaderboard) and amount_usdc (e.g., 0.01). ' +
211
+ 'The deposit is atomic — USDC is transferred and vLOFI is minted in one transaction.',
212
+
213
+ examples: [
214
+ [
215
+ {
216
+ input: { market_id: 10, amount_usdc: 0.01 },
217
+ output: {
218
+ success: true,
219
+ market_id: 10,
220
+ amount_usdc: 0.01,
221
+ signature: 'X7FG...',
222
+ },
223
+ explanation: 'Deposit 0.01 USDC into a known vaulted market and mint vLOFI.',
224
+ },
225
+ ],
226
+ ],
227
+ schema: depositSchema,
228
+ handler: async (agent, input) => depositHandler(agent, depositSchema.parse(input)),
197
229
  };
@@ -1,3 +1,4 @@
1
+ export type { MarketSignal } from './velocity.js';
1
2
  export { LEADERBOARD_ACTION } from './leaderboard.js';
2
3
  export { PORTFOLIO_ACTION } from './portfolio.js';
3
4
  export { DEPOSIT_ACTION } from './deposit.js';
@@ -5,9 +5,39 @@
5
5
  * No auth required. Permissionless read.
6
6
  */
7
7
 
8
- import type { WzrdClient, WzrdMarket } from '../client.js';
8
+ import { z } from 'zod';
9
+ import type { Action, SolanaAgentKit } from 'solana-agent-kit';
9
10
 
10
- export const LEADERBOARD_ACTION = {
11
+ import { formatVelocity, getLeaderboardData } from '../runtime.js';
12
+
13
+ export const leaderboardSchema = z.object({
14
+ limit: z.number().int().min(1).max(50).optional(),
15
+ platform: z.string().min(1).optional(),
16
+ });
17
+
18
+ export async function leaderboardHandler(
19
+ agent: SolanaAgentKit,
20
+ input: z.infer<typeof leaderboardSchema>,
21
+ ): Promise<Record<string, unknown>> {
22
+ const data = await getLeaderboardData(agent, input);
23
+
24
+ const lines = data.markets.map(
25
+ (m, i) =>
26
+ `${i + 1}. ${m.metric} — ${formatVelocity(m.velocity_ema)} velocity ` +
27
+ `(${m.platform}) | ${m.multiplier_bps / 10000}x multiplier | ` +
28
+ `${m.position_count} positions | market_id=${m.market_id}`,
29
+ );
30
+
31
+ return {
32
+ success: true,
33
+ text:
34
+ `WZRD Leaderboard (${data.markets.length} markets, ` +
35
+ `root_seq=${data.root.root_seq}):\n${lines.join('\n')}`,
36
+ data,
37
+ };
38
+ }
39
+
40
+ export const LEADERBOARD_ACTION: Action = {
11
41
  name: 'wzrd_leaderboard',
12
42
  similes: ['wzrd_markets', 'wzrd_trending', 'attention_markets', 'top_models'],
13
43
  description:
@@ -19,63 +49,16 @@ export const LEADERBOARD_ACTION = {
19
49
  examples: [
20
50
  [
21
51
  {
22
- user: 'user',
23
- content: { text: 'Show me the top attention markets' },
24
- },
25
- {
26
- user: 'assistant',
27
- content: {
28
- text: 'Here are the top WZRD markets by velocity:\n' +
29
- '1. Qwen 3.5 35B — 804K velocity (HuggingFace)\n' +
30
- '2. Qwen 3.5 9B — 769K velocity (HuggingFace)\n' +
31
- '3. Ollama — 104K velocity (GitHub)',
32
- },
33
- },
34
- ],
35
- [
36
- {
37
- user: 'user',
38
- content: { text: 'What HuggingFace models are trending?' },
39
- },
40
- {
41
- user: 'assistant',
42
- content: {
43
- text: 'Top HuggingFace models by attention velocity: ...',
52
+ input: { limit: 3 },
53
+ output: {
54
+ success: true,
55
+ root_seq: 1549,
56
+ markets: ['#16 Qwen 3.5 35B', '#14 Qwen 3.5 9B', '#10 Qwen 2.5 72B'],
44
57
  },
58
+ explanation: 'Fetch the top three WZRD markets without requiring auth.',
45
59
  },
46
60
  ],
47
61
  ],
48
-
49
- validate: async () => true,
50
-
51
- handler: async (
52
- client: WzrdClient,
53
- params: { limit?: number; platform?: string },
54
- ) => {
55
- const data = await client.getLeaderboard(
56
- params.limit ?? 10,
57
- params.platform,
58
- );
59
-
60
- const lines = data.markets.map(
61
- (m: WzrdMarket, i: number) =>
62
- `${i + 1}. ${m.metric} — ${formatVelocity(m.velocity_ema)} velocity ` +
63
- `(${m.platform}) | ${m.multiplier_bps / 10000}x multiplier | ` +
64
- `${m.position_count} positions | market_id=${m.market_id}`,
65
- );
66
-
67
- return {
68
- success: true,
69
- text:
70
- `WZRD Leaderboard (${data.markets.length} markets, ` +
71
- `root_seq=${data.root.root_seq}):\n${lines.join('\n')}`,
72
- data,
73
- };
74
- },
62
+ schema: leaderboardSchema,
63
+ handler: async (agent, input) => leaderboardHandler(agent, leaderboardSchema.parse(input)),
75
64
  };
76
-
77
- function formatVelocity(v: number): string {
78
- if (v >= 1_000_000) return `${(v / 1_000_000).toFixed(1)}M`;
79
- if (v >= 1_000) return `${(v / 1_000).toFixed(0)}K`;
80
- return v.toFixed(0);
81
- }