@wzrd_sol/solana-agent-plugin 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Action: wzrd_velocity
3
+ *
4
+ * Analyze attention velocity across markets — classify signal strength.
5
+ * No auth required. Pure analytics over leaderboard data.
6
+ *
7
+ * Signal tiers:
8
+ * BREAKOUT — top 10% velocity (p90+)
9
+ * MOMENTUM — p70-90
10
+ * EMERGING — new market (<300 snapshots) with above-median velocity
11
+ * STABLE — p40-70
12
+ * COOLING — p20-40
13
+ * WEAK — below p20
14
+ */
15
+
16
+ import type { WzrdClient, WzrdMarket } from '../client.js';
17
+
18
+ type SignalTier = 'BREAKOUT' | 'MOMENTUM' | 'EMERGING' | 'STABLE' | 'COOLING' | 'WEAK';
19
+
20
+ export interface MarketSignal {
21
+ market_id: number;
22
+ metric: string;
23
+ platform: string;
24
+ velocity_ema: number;
25
+ signal: SignalTier;
26
+ percentile: number;
27
+ }
28
+
29
+ export const VELOCITY_ACTION = {
30
+ name: 'wzrd_velocity',
31
+ similes: ['wzrd_signal', 'attention_signal', 'market_analysis', 'velocity_check'],
32
+ description:
33
+ 'Analyze attention velocity across WZRD markets. Classifies each market into ' +
34
+ 'signal tiers: BREAKOUT (top 10%), MOMENTUM (70-90th percentile), EMERGING ' +
35
+ '(new + above median), STABLE, COOLING, WEAK. ' +
36
+ 'Use this to find the best deposit opportunities — BREAKOUT and MOMENTUM ' +
37
+ 'markets have the strongest attention momentum.',
38
+
39
+ examples: [
40
+ [
41
+ {
42
+ user: 'user',
43
+ content: { text: 'What markets have breakout velocity?' },
44
+ },
45
+ {
46
+ user: 'assistant',
47
+ content: {
48
+ text: 'BREAKOUT signals:\n' +
49
+ '• Qwen 3.5 35B (804K velocity, p95)\n' +
50
+ 'MOMENTUM signals:\n' +
51
+ '• Qwen 3.5 9B (769K velocity, p85)\n' +
52
+ '• Qwen 2.5 72B (378K velocity, p72)',
53
+ },
54
+ },
55
+ ],
56
+ ],
57
+
58
+ validate: async () => true,
59
+
60
+ handler: async (
61
+ client: WzrdClient,
62
+ params: { platform?: string; min_signal?: SignalTier },
63
+ ) => {
64
+ const data = await client.getLeaderboard(50, params.platform);
65
+ const markets = data.markets;
66
+
67
+ if (markets.length === 0) {
68
+ return { success: true, text: 'No markets found.', data: { signals: [] } };
69
+ }
70
+
71
+ // Sort by velocity for percentile calculation
72
+ const sorted = [...markets].sort((a, b) => a.velocity_ema - b.velocity_ema);
73
+ const signals: MarketSignal[] = markets.map((m: WzrdMarket) => {
74
+ const rank = sorted.findIndex((s) => s.market_id === m.market_id);
75
+ const percentile = ((rank + 1) / sorted.length) * 100;
76
+ const signal = classify(m, percentile);
77
+ return {
78
+ market_id: m.market_id,
79
+ metric: m.metric,
80
+ platform: m.platform,
81
+ velocity_ema: m.velocity_ema,
82
+ signal,
83
+ percentile: Math.round(percentile),
84
+ };
85
+ });
86
+
87
+ // Filter by minimum signal if requested
88
+ const tierOrder: SignalTier[] = ['BREAKOUT', 'MOMENTUM', 'EMERGING', 'STABLE', 'COOLING', 'WEAK'];
89
+ const minIdx = params.min_signal
90
+ ? tierOrder.indexOf(params.min_signal)
91
+ : tierOrder.length;
92
+ const filtered = minIdx < tierOrder.length
93
+ ? signals.filter((s) => tierOrder.indexOf(s.signal) <= minIdx)
94
+ : signals;
95
+
96
+ // Group by signal tier
97
+ const grouped = new Map<SignalTier, MarketSignal[]>();
98
+ for (const s of filtered) {
99
+ const arr = grouped.get(s.signal) ?? [];
100
+ arr.push(s);
101
+ grouped.set(s.signal, arr);
102
+ }
103
+
104
+ const lines: string[] = [];
105
+ for (const tier of tierOrder) {
106
+ const group = grouped.get(tier);
107
+ if (!group?.length) continue;
108
+ lines.push(`${tier}:`);
109
+ for (const s of group) {
110
+ lines.push(
111
+ ` • ${s.metric} (${formatVelocity(s.velocity_ema)} velocity, p${s.percentile}) [${s.platform}]`,
112
+ );
113
+ }
114
+ }
115
+
116
+ // Median velocity
117
+ const medianIdx = Math.floor(sorted.length / 2);
118
+ const medianVelocity = sorted[medianIdx]?.velocity_ema ?? 0;
119
+
120
+ return {
121
+ success: true,
122
+ text:
123
+ `Velocity analysis (${markets.length} markets, ` +
124
+ `median ${formatVelocity(medianVelocity)}):\n${lines.join('\n')}`,
125
+ data: { signals: filtered, median_velocity: medianVelocity },
126
+ };
127
+ },
128
+ };
129
+
130
+ function classify(m: WzrdMarket, percentile: number): SignalTier {
131
+ if (percentile >= 90) return 'BREAKOUT';
132
+ if (percentile >= 70) return 'MOMENTUM';
133
+ if (m.snapshot_count < 300 && percentile >= 50) return 'EMERGING';
134
+ if (percentile >= 40) return 'STABLE';
135
+ if (percentile >= 20) return 'COOLING';
136
+ return 'WEAK';
137
+ }
138
+
139
+ function formatVelocity(v: number): string {
140
+ if (v >= 1_000_000) return `${(v / 1_000_000).toFixed(1)}M`;
141
+ if (v >= 1_000) return `${(v / 1_000).toFixed(0)}K`;
142
+ return v.toFixed(0);
143
+ }
package/src/client.ts ADDED
@@ -0,0 +1,207 @@
1
+ /**
2
+ * WZRD API client — handles agent auth (Ed25519 challenge/verify),
3
+ * token caching, and all REST calls.
4
+ *
5
+ * Standalone: no framework dependency. Works with any Keypair holder.
6
+ */
7
+
8
+ import { Keypair } from '@solana/web3.js';
9
+
10
+ const DEFAULT_API_URL = 'https://api.twzrd.xyz';
11
+ const TOKEN_REFRESH_MARGIN_MS = 5 * 60 * 1000;
12
+
13
+ // ── Base58 encoder (zero-dependency) ────────────────────
14
+
15
+ const B58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
16
+ function toBase58(bytes: Uint8Array): string {
17
+ const digits = [0];
18
+ for (const byte of bytes) {
19
+ let carry = byte;
20
+ for (let j = 0; j < digits.length; j++) {
21
+ carry += digits[j] << 8;
22
+ digits[j] = carry % 58;
23
+ carry = (carry / 58) | 0;
24
+ }
25
+ while (carry > 0) {
26
+ digits.push(carry % 58);
27
+ carry = (carry / 58) | 0;
28
+ }
29
+ }
30
+ let out = '';
31
+ for (const b of bytes) {
32
+ if (b !== 0) break;
33
+ out += '1';
34
+ }
35
+ for (let i = digits.length - 1; i >= 0; i--) out += B58[digits[i]];
36
+ return out;
37
+ }
38
+
39
+ // ── Types ───────────────────────────────────────────────
40
+
41
+ export interface WzrdMarket {
42
+ market_id: number;
43
+ channel_id: string;
44
+ platform: string;
45
+ metric: string;
46
+ velocity_ema: number;
47
+ multiplier_bps: number;
48
+ position_count: number;
49
+ tvl_usdc: number;
50
+ rank: number;
51
+ status: string;
52
+ snapshot_count: number;
53
+ last_scored_at: string;
54
+ }
55
+
56
+ export interface WzrdLeaderboard {
57
+ markets: WzrdMarket[];
58
+ root: { root_seq: number; root_hash: string; published_slot: number };
59
+ total_positions: number;
60
+ total_tvl_usdc: number;
61
+ total_snapshots: number;
62
+ updated_at: string;
63
+ }
64
+
65
+ export interface WzrdPosition {
66
+ market_id: number;
67
+ metric: string;
68
+ usdc_deposited: number;
69
+ vlofi_minted: number;
70
+ multiplier_bps: number;
71
+ status: string;
72
+ is_settled: boolean;
73
+ }
74
+
75
+ export interface WzrdPortfolio {
76
+ positions: WzrdPosition[];
77
+ total_deposited_usdc: number;
78
+ total_vlofi: number;
79
+ total_ccm_earned: number;
80
+ }
81
+
82
+ export interface WzrdClaim {
83
+ root_seq: number;
84
+ cumulative_total: number;
85
+ claimed_total: number;
86
+ leaf_index: number;
87
+ proof: string[];
88
+ accounts: Record<string, string>;
89
+ }
90
+
91
+ export interface WzrdRelayResult {
92
+ root_seq: number;
93
+ cumulative_total: number;
94
+ tx_sig: string;
95
+ }
96
+
97
+ // ── Client ──────────────────────────────────────────────
98
+
99
+ export class WzrdClient {
100
+ private token: string | null = null;
101
+ private tokenExpiresAt = 0;
102
+
103
+ constructor(
104
+ private readonly keypair: Keypair,
105
+ private readonly apiUrl: string = DEFAULT_API_URL,
106
+ ) {}
107
+
108
+ get pubkey(): string {
109
+ return this.keypair.publicKey.toBase58();
110
+ }
111
+
112
+ // ── Auth ────────────────────────────────────────────
113
+
114
+ private async authenticate(): Promise<void> {
115
+ // 1. Challenge
116
+ const cr = await fetch(`${this.apiUrl}/v1/agent/challenge`);
117
+ if (!cr.ok) throw new Error(`Challenge failed: ${cr.status}`);
118
+ const { nonce } = (await cr.json()) as { nonce: string };
119
+
120
+ // 2. Sign with Ed25519
121
+ const message = `wzrd-agent-auth v1 | wallet:${this.pubkey} | nonce:${nonce}`;
122
+ const { default: nacl } = await import('tweetnacl');
123
+ const sig = nacl.sign.detached(
124
+ new TextEncoder().encode(message),
125
+ this.keypair.secretKey,
126
+ );
127
+
128
+ // 3. Verify
129
+ const vr = await fetch(`${this.apiUrl}/v1/agent/verify`, {
130
+ method: 'POST',
131
+ headers: { 'Content-Type': 'application/json' },
132
+ body: JSON.stringify({
133
+ pubkey: this.pubkey,
134
+ nonce,
135
+ signature: toBase58(sig),
136
+ }),
137
+ });
138
+ if (!vr.ok) throw new Error(`Verify failed: ${vr.status} ${await vr.text()}`);
139
+
140
+ const { token, expires_at } = (await vr.json()) as {
141
+ token: string;
142
+ expires_at: string;
143
+ };
144
+ this.token = token;
145
+ this.tokenExpiresAt = new Date(expires_at).getTime();
146
+ }
147
+
148
+ private async getToken(): Promise<string> {
149
+ if (
150
+ !this.token ||
151
+ Date.now() > this.tokenExpiresAt - TOKEN_REFRESH_MARGIN_MS
152
+ ) {
153
+ await this.authenticate();
154
+ }
155
+ return this.token!;
156
+ }
157
+
158
+ private async authedFetch(path: string, init?: RequestInit): Promise<Response> {
159
+ const token = await this.getToken();
160
+ return fetch(`${this.apiUrl}${path}`, {
161
+ ...init,
162
+ headers: { ...init?.headers, Authorization: `Bearer ${token}` },
163
+ });
164
+ }
165
+
166
+ // ── Public API ──────────────────────────────────────
167
+
168
+ /** Fetch leaderboard — no auth required. */
169
+ async getLeaderboard(limit = 20, platform?: string): Promise<WzrdLeaderboard> {
170
+ const params = new URLSearchParams({ limit: String(limit) });
171
+ if (platform) params.set('platform', platform);
172
+ const res = await fetch(`${this.apiUrl}/v1/leaderboard?${params}`);
173
+ if (!res.ok) throw new Error(`Leaderboard failed: ${res.status}`);
174
+ return (await res.json()) as WzrdLeaderboard;
175
+ }
176
+
177
+ /** Fetch authenticated portfolio. */
178
+ async getPortfolio(): Promise<WzrdPortfolio> {
179
+ const res = await this.authedFetch('/v1/portfolio');
180
+ if (!res.ok) throw new Error(`Portfolio failed: ${res.status}`);
181
+ return (await res.json()) as WzrdPortfolio;
182
+ }
183
+
184
+ /** Fetch claim data (proof + amounts). */
185
+ async getClaims(): Promise<WzrdClaim> {
186
+ const res = await this.authedFetch(`/v1/claims/${this.pubkey}`);
187
+ if (!res.ok) throw new Error(`Claims failed: ${res.status}`);
188
+ return (await res.json()) as WzrdClaim;
189
+ }
190
+
191
+ /** Execute gasless relay claim — server pays tx fees. */
192
+ async claimRelay(): Promise<WzrdRelayResult> {
193
+ const res = await this.authedFetch(`/v1/claims/${this.pubkey}/relay`, {
194
+ method: 'POST',
195
+ headers: { 'Content-Type': 'application/json' },
196
+ });
197
+ if (!res.ok) throw new Error(`Relay claim failed: ${res.status} ${await res.text()}`);
198
+ return (await res.json()) as WzrdRelayResult;
199
+ }
200
+
201
+ /** Fetch system health — no auth required. */
202
+ async getHealth(): Promise<Record<string, unknown>> {
203
+ const res = await fetch(`${this.apiUrl}/health`);
204
+ if (!res.ok) throw new Error(`Health failed: ${res.status}`);
205
+ return (await res.json()) as Record<string, unknown>;
206
+ }
207
+ }
package/src/index.ts ADDED
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @wzrd_sol/solana-agent-plugin
3
+ *
4
+ * WZRD Liquid Attention Protocol plugin for autonomous Solana agents.
5
+ *
6
+ * 5 actions:
7
+ * wzrd_leaderboard — browse attention markets (no auth)
8
+ * wzrd_velocity — classify signal strength (no auth)
9
+ * wzrd_portfolio — view positions + CCM earned (auth)
10
+ * wzrd_deposit — deposit USDC → mint vLOFI (auth + keypair)
11
+ * wzrd_claim — claim CCM via gasless relay (auth)
12
+ *
13
+ * Works with:
14
+ * - Solana Agent Kit (SendAI) — register as plugin
15
+ * - ElizaOS — wrap actions as ElizaOS Actions
16
+ * - Standalone — use WzrdClient directly
17
+ *
18
+ * Quick start:
19
+ * import { WzrdClient } from '@wzrd_sol/solana-agent-plugin';
20
+ * const client = new WzrdClient(keypair);
21
+ * const leaderboard = await client.getLeaderboard();
22
+ */
23
+
24
+ export { WzrdClient } from './client.js';
25
+ export type {
26
+ WzrdMarket,
27
+ WzrdLeaderboard,
28
+ WzrdPosition,
29
+ WzrdPortfolio,
30
+ WzrdClaim,
31
+ WzrdRelayResult,
32
+ } from './client.js';
33
+
34
+ export {
35
+ LEADERBOARD_ACTION,
36
+ PORTFOLIO_ACTION,
37
+ DEPOSIT_ACTION,
38
+ CLAIM_ACTION,
39
+ VELOCITY_ACTION,
40
+ } from './actions/index.js';
41
+
42
+ // ── Convenience: all actions as array ────────────────────
43
+
44
+ import { LEADERBOARD_ACTION } from './actions/leaderboard.js';
45
+ import { PORTFOLIO_ACTION } from './actions/portfolio.js';
46
+ import { DEPOSIT_ACTION } from './actions/deposit.js';
47
+ import { CLAIM_ACTION } from './actions/claim.js';
48
+ import { VELOCITY_ACTION } from './actions/velocity.js';
49
+
50
+ export const WZRD_ACTIONS = [
51
+ LEADERBOARD_ACTION,
52
+ PORTFOLIO_ACTION,
53
+ DEPOSIT_ACTION,
54
+ CLAIM_ACTION,
55
+ VELOCITY_ACTION,
56
+ ] as const;
57
+
58
+ /** Plugin metadata for framework registration. */
59
+ export const WZRD_PLUGIN = {
60
+ name: 'wzrd',
61
+ description: 'WZRD Liquid Attention Protocol — deposit USDC into AI attention markets, earn CCM yield',
62
+ actions: WZRD_ACTIONS,
63
+ version: '0.1.0',
64
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "nodenext",
5
+ "moduleResolution": "nodenext",
6
+ "declaration": true,
7
+ "outDir": "dist",
8
+ "rootDir": "src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true
12
+ },
13
+ "include": ["src"]
14
+ }