@wzrd_sol/eliza-plugin 0.1.1 → 0.2.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.
@@ -1,3 +1,6 @@
1
- /** ELIZA Action: WZRD_CLAIM — claim accrued CCM via gasless relay (auth). */
1
+ /**
2
+ * WZRD_CLAIM — Claim accrued CCM via gasless relay.
3
+ * Server pays all transaction fees. Agent needs 0 SOL.
4
+ */
2
5
  import type { Action } from '@elizaos/core';
3
6
  export declare const claimAction: Action;
@@ -1,45 +1,48 @@
1
- import { getWzrdClient } from '../client.js';
1
+ import { getWzrdClient } from '../client-factory.js';
2
2
  export const claimAction = {
3
3
  name: 'WZRD_CLAIM',
4
- similes: ['WZRD_HARVEST', 'CLAIM_CCM', 'COLLECT_YIELD'],
5
- description: 'Claim accrued CCM tokens via gasless relay. Server pays tx fees.',
6
- examples: [[
4
+ similes: ['WZRD_HARVEST', 'CLAIM_CCM', 'COLLECT_REWARDS'],
5
+ description: 'Claim accrued CCM tokens via gasless relay. Server pays tx fees — agent needs 0 SOL.',
6
+ examples: [
7
+ [
7
8
  { name: '{{user1}}', content: { text: 'Claim my WZRD rewards' } },
8
- { name: '{{agentName}}', content: { text: 'Claimed CCM via gasless relay. Tx: 5S7L...' } },
9
- ]],
9
+ { name: '{{agentName}}', content: { text: 'Claimed 142.5 CCM via gasless relay. Tx: 5S7L...' } },
10
+ ],
11
+ ],
10
12
  validate: async () => true,
11
13
  handler: async (runtime, _msg, _state, _opt, callback) => {
12
14
  const client = getWzrdClient(runtime);
13
- let claims;
14
15
  try {
15
- claims = await client.getClaims();
16
+ // Check if there's anything to claim
17
+ const status = await client.getClaimStatus();
18
+ if (status.claimable <= 0) {
19
+ const text = `No CCM to claim right now.\n` +
20
+ `Cumulative: ${(status.cumulative_total / 1e9).toFixed(2)} CCM\n` +
21
+ `Already claimed: ${(status.claimed_total / 1e9).toFixed(2)} CCM\n` +
22
+ `Run WZRD_EARN first to accrue rewards.`;
23
+ await callback?.({ text });
24
+ return { success: true, data: status };
25
+ }
26
+ const result = await client.claimRelay();
27
+ if (result.status === 'already_claimed') {
28
+ await callback?.({ text: `Already claimed through root ${result.root_seq}.` });
29
+ return { success: true, data: result };
30
+ }
31
+ const text = `Claimed CCM via gasless relay.\n` +
32
+ `Amount: ${(result.cumulative_total / 1e9).toFixed(2)} CCM (cumulative)\n` +
33
+ `Root: ${result.root_seq}\n` +
34
+ `Tx: ${result.tx_sig?.slice(0, 20)}...`;
35
+ await callback?.({ text });
36
+ return { success: true, data: result };
16
37
  }
17
38
  catch (err) {
18
39
  const msg = err instanceof Error ? err.message : String(err);
19
40
  if (msg.includes('404')) {
20
- const text = 'No claims found. Deposit into a market first to start accruing CCM.';
21
- await callback?.({ text });
22
- return { success: true, text };
41
+ await callback?.({ text: 'No claims found yet. Run WZRD_EARN to start accruing CCM.' });
42
+ return { success: true, data: {} };
23
43
  }
24
- const text = `Failed to fetch claims: ${msg}`;
25
- await callback?.({ text });
26
- return { success: false, error: text };
27
- }
28
- const claimable = claims.cumulative_total - claims.claimed_total;
29
- if (claimable <= 0) {
30
- const text = `No CCM to claim. Cumulative: ${claims.cumulative_total}, claimed: ${claims.claimed_total}.`;
31
- await callback?.({ text });
32
- return { success: true, text, data: claims };
33
- }
34
- const result = await client.claimRelay();
35
- if (result.status === 'already_claimed') {
36
- const text = `Already claimed through root ${result.root_seq}.`;
37
- await callback?.({ text });
38
- return { success: true, text, data: result };
44
+ await callback?.({ text: `Claim failed: ${msg}` });
45
+ return { success: false, error: msg };
39
46
  }
40
- const text = `Claimed CCM via relay. Cumulative: ${(result.cumulative_total / 1e6).toFixed(4)} CCM. ` +
41
- `Root: ${result.root_seq}. Tx: ${result.tx_sig?.slice(0, 16)}...`;
42
- await callback?.({ text });
43
- return { success: true, text, data: result };
44
47
  },
45
48
  };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * WZRD_EARN — Full earn cycle: infer → report → check rewards.
3
+ *
4
+ * Single action that runs the complete server-witnessed earn loop.
5
+ * For agents that want one-shot earning without managing execution_ids.
6
+ */
7
+ import type { Action } from '@elizaos/core';
8
+ export declare const earnAction: Action;
@@ -0,0 +1,83 @@
1
+ import { getWzrdClient } from '../client-factory.js';
2
+ const EVAL_PROMPTS = {
3
+ code: [
4
+ 'Write a Python function that checks if a binary tree is balanced. Include time complexity analysis.',
5
+ 'Implement a thread-safe LRU cache in Rust with O(1) get and put operations.',
6
+ 'Write a TypeScript generic function that deep-merges two objects, handling arrays and nested objects.',
7
+ ],
8
+ chat: [
9
+ 'Explain the difference between TCP and UDP to someone who has never programmed before.',
10
+ 'What are the tradeoffs between microservices and monolithic architecture?',
11
+ 'Describe how consensus works in proof-of-stake blockchains.',
12
+ ],
13
+ reasoning: [
14
+ 'A farmer has a fox, a chicken, and a bag of grain. He must cross a river in a boat that can only carry him and one item. How does he do it?',
15
+ 'If it takes 5 machines 5 minutes to make 5 widgets, how long does it take 100 machines to make 100 widgets?',
16
+ 'Three people check into a hotel room that costs $30. They each pay $10. The manager realizes the room should cost $25 and gives $5 to the bellboy to return. The bellboy keeps $2 and gives $1 back to each person. So each person paid $9 (total $27) plus the bellboy kept $2 (total $29). Where is the missing dollar?',
17
+ ],
18
+ };
19
+ function pickPrompt(taskType) {
20
+ const prompts = EVAL_PROMPTS[taskType] || EVAL_PROMPTS.chat;
21
+ return prompts[Math.floor(Math.random() * prompts.length)];
22
+ }
23
+ export const earnAction = {
24
+ name: 'WZRD_EARN',
25
+ similes: ['WZRD_EARN_CCM', 'EARN_REWARDS', 'RUN_EARN_LOOP'],
26
+ description: 'Run the full WZRD earn cycle: pick a prompt, run server-witnessed inference, ' +
27
+ 'report the outcome, and check pending rewards. One action, complete loop.',
28
+ examples: [
29
+ [
30
+ { name: '{{user1}}', content: { text: 'Earn some CCM on WZRD' } },
31
+ {
32
+ name: '{{agentName}}',
33
+ content: {
34
+ text: 'Earn cycle complete. Model: gemini-2.5-flash, quality: 0.85, verified. Pending: 142.5 CCM.',
35
+ },
36
+ },
37
+ ],
38
+ ],
39
+ validate: async () => true,
40
+ handler: async (runtime, message, _state, _opt, callback) => {
41
+ const content = message.content;
42
+ const taskType = content.task_type || 'code';
43
+ const prompt = content.prompt || pickPrompt(taskType);
44
+ const client = getWzrdClient(runtime);
45
+ const steps = [];
46
+ try {
47
+ // Step 1: Pick model + Infer
48
+ steps.push('→ Picking model from leaderboard...');
49
+ const model = await client.pickModel(taskType);
50
+ steps.push(`→ Running inference: ${model} (${taskType})...`);
51
+ const infer = await client.infer(prompt, model, taskType);
52
+ steps.push(`✓ Inference: ${infer.executed_model} (${infer.provider}), quality ${infer.quality_score.toFixed(2)}, ${infer.latency_ms}ms`);
53
+ // Step 2: Report with execution_id
54
+ steps.push('→ Reporting outcome...');
55
+ const report = await client.report({
56
+ model_id: infer.requested_model,
57
+ execution_id: infer.execution_id,
58
+ task_type: taskType,
59
+ quality_score: infer.quality_score,
60
+ latency_ms: infer.latency_ms,
61
+ });
62
+ steps.push(`✓ Reported: ${report.verification_state}, contribution #${report.contribution_id}, pending ${(report.pending_ccm / 1e9).toFixed(2)} CCM`);
63
+ // Step 3: Check rewards
64
+ steps.push('→ Checking rewards...');
65
+ const rewards = await client.getRewards();
66
+ steps.push(`✓ Rewards: ${(rewards.pending_ccm / 1e9).toFixed(2)} CCM pending, ` +
67
+ `${(rewards.total_rewarded_ccm / 1e9).toFixed(2)} CCM lifetime` +
68
+ (rewards.rank ? `, rank #${rewards.rank}` : ''));
69
+ const text = `Earn cycle complete:\n${steps.join('\n')}`;
70
+ await callback?.({ text });
71
+ return {
72
+ success: true,
73
+ data: { infer, report, rewards },
74
+ };
75
+ }
76
+ catch (err) {
77
+ const msg = err instanceof Error ? err.message : String(err);
78
+ steps.push(`✗ Failed: ${msg}`);
79
+ await callback?.({ text: `Earn cycle failed:\n${steps.join('\n')}` });
80
+ return { success: false, error: msg };
81
+ }
82
+ },
83
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * WZRD_INFER — Server-witnessed inference through WZRD.
3
+ *
4
+ * WZRD calls the AI provider (Gemini/Nous/OpenRouter), grades the response,
5
+ * and returns an execution_id receipt. Pass this to WZRD_REPORT for verified rewards.
6
+ */
7
+ import type { Action } from '@elizaos/core';
8
+ export declare const inferAction: Action;
@@ -0,0 +1,41 @@
1
+ import { getWzrdClient } from '../client-factory.js';
2
+ export const inferAction = {
3
+ name: 'WZRD_INFER',
4
+ similes: ['WZRD_RUN_INFERENCE', 'ASK_MODEL', 'RUN_MODEL'],
5
+ description: 'Run inference through WZRD. The server calls the AI provider, grades quality, ' +
6
+ 'and returns an execution receipt. Use the execution_id with WZRD_REPORT to earn CCM.',
7
+ examples: [
8
+ [
9
+ { name: '{{user1}}', content: { text: 'Run inference through WZRD: explain quicksort in Python' } },
10
+ { name: '{{agentName}}', content: { text: 'Inference complete. Model: gemini-2.5-flash, quality: 0.85. execution_id: abc-123...' } },
11
+ ],
12
+ ],
13
+ validate: async () => true,
14
+ handler: async (runtime, message, _state, _opt, callback) => {
15
+ const content = message.content;
16
+ const prompt = content.prompt || content.text || '';
17
+ if (!prompt) {
18
+ await callback?.({ text: 'No prompt provided. Include a prompt or text to run inference.' });
19
+ return { success: false, error: 'No prompt' };
20
+ }
21
+ const taskType = content.task_type || 'chat';
22
+ const model = content.model;
23
+ const client = getWzrdClient(runtime);
24
+ try {
25
+ const result = await client.infer(prompt, model, taskType);
26
+ const text = `Inference complete.\n` +
27
+ `Model: ${result.executed_model} (${result.provider})\n` +
28
+ `Quality: ${result.quality_score.toFixed(2)}\n` +
29
+ `Latency: ${result.latency_ms}ms\n` +
30
+ `execution_id: ${result.execution_id}\n\n` +
31
+ `Response: ${result.response_preview}`;
32
+ await callback?.({ text });
33
+ return { success: true, data: result };
34
+ }
35
+ catch (err) {
36
+ const msg = err instanceof Error ? err.message : String(err);
37
+ await callback?.({ text: `Inference failed: ${msg}` });
38
+ return { success: false, error: msg };
39
+ }
40
+ },
41
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * WZRD_REPORT — Report a model pick with execution receipt for verified CCM rewards.
3
+ *
4
+ * Must be called after WZRD_INFER. Pass the execution_id from the infer result
5
+ * to get server-verified status and quality-weighted rewards.
6
+ */
7
+ import type { Action } from '@elizaos/core';
8
+ export declare const reportAction: Action;
@@ -0,0 +1,45 @@
1
+ import { getWzrdClient } from '../client-factory.js';
2
+ export const reportAction = {
3
+ name: 'WZRD_REPORT',
4
+ similes: ['WZRD_REPORT_OUTCOME', 'REPORT_MODEL_PICK', 'SUBMIT_SIGNAL'],
5
+ description: 'Report a model pick to WZRD with an execution_id from WZRD_INFER. ' +
6
+ 'Verified reports earn CCM rewards with quality multiplier.',
7
+ examples: [
8
+ [
9
+ { name: '{{user1}}', content: { text: 'Report the inference result to earn CCM' } },
10
+ { name: '{{agentName}}', content: { text: 'Reported. Verification: verified, contribution #4521.' } },
11
+ ],
12
+ ],
13
+ validate: async () => true,
14
+ handler: async (runtime, message, _state, _opt, callback) => {
15
+ const content = message.content;
16
+ if (!content.execution_id || !content.model_id) {
17
+ await callback?.({
18
+ text: 'Missing required: execution_id and model_id. Run WZRD_INFER first to get an execution receipt.',
19
+ });
20
+ return { success: false, error: 'Missing execution_id or model_id' };
21
+ }
22
+ const client = getWzrdClient(runtime);
23
+ try {
24
+ const result = await client.report({
25
+ model_id: content.model_id,
26
+ execution_id: content.execution_id,
27
+ task_type: content.task_type,
28
+ quality_score: content.quality_score,
29
+ latency_ms: content.latency_ms,
30
+ });
31
+ const text = `Reported to WZRD.\n` +
32
+ `Verification: ${result.verification_state}\n` +
33
+ `Contribution: #${result.contribution_id}\n` +
34
+ `Pending CCM: ${(result.pending_ccm / 1e9).toFixed(2)}\n` +
35
+ `Pipeline: ${result.pipeline_state}`;
36
+ await callback?.({ text });
37
+ return { success: true, data: result };
38
+ }
39
+ catch (err) {
40
+ const msg = err instanceof Error ? err.message : String(err);
41
+ await callback?.({ text: `Report failed: ${msg}` });
42
+ return { success: false, error: msg };
43
+ }
44
+ },
45
+ };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * WZRD_REWARDS — Check pending and lifetime CCM rewards.
3
+ */
4
+ import type { Action } from '@elizaos/core';
5
+ export declare const rewardsAction: Action;
@@ -0,0 +1,31 @@
1
+ import { getWzrdClient } from '../client-factory.js';
2
+ export const rewardsAction = {
3
+ name: 'WZRD_REWARDS',
4
+ similes: ['WZRD_BALANCE', 'CHECK_REWARDS', 'MY_CCM'],
5
+ description: 'Check your pending CCM rewards, lifetime total, rank, and contribution count.',
6
+ examples: [
7
+ [
8
+ { name: '{{user1}}', content: { text: 'How much CCM have I earned?' } },
9
+ { name: '{{agentName}}', content: { text: 'Pending: 142.5 CCM. Lifetime: 326,000 CCM. Rank #3.' } },
10
+ ],
11
+ ],
12
+ validate: async () => true,
13
+ handler: async (runtime, _msg, _state, _opt, callback) => {
14
+ const client = getWzrdClient(runtime);
15
+ try {
16
+ const rewards = await client.getRewards();
17
+ const text = `WZRD Rewards:\n` +
18
+ `Pending: ${(rewards.pending_ccm / 1e9).toFixed(2)} CCM\n` +
19
+ `Lifetime: ${(rewards.total_rewarded_ccm / 1e9).toFixed(2)} CCM\n` +
20
+ `Contributions: ${rewards.contribution_count}\n` +
21
+ (rewards.rank ? `Rank: #${rewards.rank}` : 'Rank: unranked');
22
+ await callback?.({ text });
23
+ return { success: true, data: rewards };
24
+ }
25
+ catch (err) {
26
+ const msg = err instanceof Error ? err.message : String(err);
27
+ await callback?.({ text: `Failed to fetch rewards: ${msg}` });
28
+ return { success: false, error: msg };
29
+ }
30
+ },
31
+ };
@@ -0,0 +1,5 @@
1
+ import { WzrdClient } from './client.js';
2
+ import type { IAgentRuntime } from '@elizaos/core';
3
+ export declare function getWzrdClient(runtime: IAgentRuntime): WzrdClient;
4
+ /** Reset client cache — useful for tests. */
5
+ export declare function clearClientCache(): void;
@@ -0,0 +1,20 @@
1
+ /** Extract keypair from ElizaOS runtime, return cached WzrdClient. */
2
+ import { Keypair } from '@solana/web3.js';
3
+ import { WzrdClient } from './client.js';
4
+ const cache = new Map();
5
+ export function getWzrdClient(runtime) {
6
+ const sk = runtime.getSetting('SOLANA_PRIVATE_KEY');
7
+ if (!sk)
8
+ throw new Error('SOLANA_PRIVATE_KEY not configured in ElizaOS runtime');
9
+ const kp = Keypair.fromSecretKey(Uint8Array.from(JSON.parse(String(sk))));
10
+ const pub = kp.publicKey.toBase58();
11
+ if (!cache.has(pub)) {
12
+ const apiUrl = runtime.getSetting('WZRD_API_URL');
13
+ cache.set(pub, new WzrdClient(kp, typeof apiUrl === 'string' ? apiUrl : undefined));
14
+ }
15
+ return cache.get(pub);
16
+ }
17
+ /** Reset client cache — useful for tests. */
18
+ export function clearClientCache() {
19
+ cache.clear();
20
+ }
package/dist/client.d.ts CHANGED
@@ -1,3 +1,90 @@
1
- import { WzrdClient } from '@wzrd_sol/solana-agent-plugin';
2
- import type { IAgentRuntime } from '@elizaos/core';
3
- export declare function getWzrdClient(runtime: IAgentRuntime): WzrdClient;
1
+ /**
2
+ * Standalone WZRD API client for ElizaOS agents.
3
+ * No external dependencies beyond @solana/web3.js for Ed25519 signing.
4
+ *
5
+ * Auth flow: challenge → sign → verify → Bearer token (24h TTL)
6
+ * Earn flow: infer → report(execution_id) → claim
7
+ */
8
+ import { Keypair } from '@solana/web3.js';
9
+ export interface InferResult {
10
+ execution_id: string;
11
+ executed_model: string;
12
+ requested_model: string;
13
+ provider: string;
14
+ quality_score: number;
15
+ response_preview: string;
16
+ latency_ms: number;
17
+ cost_usd?: number;
18
+ }
19
+ export interface ReportResult {
20
+ contribution_id: number;
21
+ verification_state: string;
22
+ lifetime_contributions: number;
23
+ pending_ccm: number;
24
+ pipeline_state: string;
25
+ idempotent: boolean;
26
+ provider_receipt_present: boolean;
27
+ }
28
+ export interface RewardsBalance {
29
+ pending_ccm: number;
30
+ total_rewarded_ccm: number;
31
+ rank: number | null;
32
+ contribution_count: number;
33
+ }
34
+ export interface ClaimResult {
35
+ tx_sig: string | null;
36
+ root_seq: number;
37
+ cumulative_total: number;
38
+ status: string;
39
+ }
40
+ export interface LeaderboardResult {
41
+ market_count: number;
42
+ total_tvl: number;
43
+ root: {
44
+ root_seq: number;
45
+ };
46
+ markets: Array<{
47
+ market_id: number;
48
+ metric: string;
49
+ platform: string;
50
+ velocity_ema: number;
51
+ multiplier_bps: number;
52
+ snapshot_count: number;
53
+ }>;
54
+ }
55
+ export declare class WzrdClient {
56
+ private keypair;
57
+ private apiUrl;
58
+ private token;
59
+ private tokenExpiry;
60
+ constructor(keypair: Keypair, apiUrl?: string);
61
+ get pubkey(): string;
62
+ /** Ed25519 challenge → sign → verify → Bearer token */
63
+ private authenticate;
64
+ /** Authenticated fetch helper */
65
+ private authedFetch;
66
+ /** Pick a model from the momentum signal — returns top model's channel_id */
67
+ pickModel(taskType?: string): Promise<string>;
68
+ /** Server-witnessed inference — WZRD calls the provider, grades quality */
69
+ infer(prompt: string, model?: string, taskType?: string): Promise<InferResult>;
70
+ /** Report model pick with execution_id for verified rewards */
71
+ report(params: {
72
+ model_id: string;
73
+ execution_id: string;
74
+ task_type?: string;
75
+ quality_score?: number;
76
+ latency_ms?: number;
77
+ }): Promise<ReportResult>;
78
+ /** Check pending + total rewards (flattens nested /v1/agent/earned response) */
79
+ getRewards(): Promise<RewardsBalance>;
80
+ /** Gasless CCM claim via server relay */
81
+ claimRelay(): Promise<ClaimResult>;
82
+ /** Public: fetch leaderboard (no auth) */
83
+ getLeaderboard(limit?: number): Promise<LeaderboardResult>;
84
+ /** Public: fetch claims status */
85
+ getClaimStatus(): Promise<{
86
+ cumulative_total: number;
87
+ claimed_total: number;
88
+ claimable: number;
89
+ }>;
90
+ }
package/dist/client.js CHANGED
@@ -1,17 +1,134 @@
1
- /** Shared helper: extract wallet from ELIZA runtime, return cached WzrdClient. */
2
- import { Keypair } from '@solana/web3.js';
3
- import { WzrdClient } from '@wzrd_sol/solana-agent-plugin';
4
- const DEFAULT_API_URL = 'https://api.twzrd.xyz';
5
- const cache = new Map();
6
- export function getWzrdClient(runtime) {
7
- const sk = runtime.getSetting('SOLANA_PRIVATE_KEY');
8
- if (!sk)
9
- throw new Error('SOLANA_PRIVATE_KEY not configured in ELIZA runtime');
10
- const kp = Keypair.fromSecretKey(Uint8Array.from(JSON.parse(String(sk))));
11
- const pub = kp.publicKey.toBase58();
12
- if (!cache.has(pub)) {
13
- const apiUrl = runtime.getSetting('WZRD_API_URL');
14
- cache.set(pub, new WzrdClient(kp, (typeof apiUrl === 'string' ? apiUrl : undefined) || DEFAULT_API_URL));
15
- }
16
- return cache.get(pub);
1
+ import nacl from 'tweetnacl';
2
+ import bs58 from 'bs58';
3
+ const DEFAULT_API = 'https://api.twzrd.xyz';
4
+ export class WzrdClient {
5
+ keypair;
6
+ apiUrl;
7
+ token = null;
8
+ tokenExpiry = 0;
9
+ constructor(keypair, apiUrl) {
10
+ this.keypair = keypair;
11
+ this.apiUrl = apiUrl || DEFAULT_API;
12
+ }
13
+ get pubkey() {
14
+ return this.keypair.publicKey.toBase58();
15
+ }
16
+ /** Ed25519 challenge → sign → verify → Bearer token */
17
+ async authenticate() {
18
+ if (this.token && Date.now() < this.tokenExpiry)
19
+ return this.token;
20
+ // 1. Get challenge
21
+ const challengeRes = await fetch(`${this.apiUrl}/v1/agent/challenge?pubkey=${this.pubkey}`);
22
+ if (!challengeRes.ok)
23
+ throw new Error(`Challenge failed: ${challengeRes.status}`);
24
+ const { nonce } = await challengeRes.json();
25
+ // 2. Construct message locally (must match server's agent_auth_message format)
26
+ const message = `wzrd-agent-auth v1 | wallet:${this.pubkey} | nonce:${nonce}`;
27
+ const messageBytes = new TextEncoder().encode(message);
28
+ const signature = nacl.sign.detached(messageBytes, this.keypair.secretKey);
29
+ // 3. Verify — signature must be base58-encoded (Solana Signature format)
30
+ const verifyRes = await fetch(`${this.apiUrl}/v1/agent/verify`, {
31
+ method: 'POST',
32
+ headers: { 'Content-Type': 'application/json' },
33
+ body: JSON.stringify({
34
+ pubkey: this.pubkey,
35
+ nonce,
36
+ signature: bs58.encode(signature),
37
+ }),
38
+ });
39
+ if (!verifyRes.ok)
40
+ throw new Error(`Verify failed: ${verifyRes.status}`);
41
+ const { token } = await verifyRes.json();
42
+ this.token = token;
43
+ this.tokenExpiry = Date.now() + 23 * 60 * 60 * 1000; // refresh 1h before 24h expiry
44
+ return token;
45
+ }
46
+ /** Authenticated fetch helper */
47
+ async authedFetch(path, init) {
48
+ const token = await this.authenticate();
49
+ const headers = new Headers(init?.headers);
50
+ headers.set('Authorization', `Bearer ${token}`);
51
+ headers.set('Content-Type', 'application/json');
52
+ return fetch(`${this.apiUrl}${path}`, { ...init, headers });
53
+ }
54
+ /** Pick a model from the momentum signal — returns top model's channel_id */
55
+ async pickModel(taskType) {
56
+ const res = await fetch(`${this.apiUrl}/v1/signals/momentum?limit=10&trending=true`);
57
+ if (!res.ok)
58
+ throw new Error(`Momentum fetch failed: ${res.status}`);
59
+ const data = await res.json();
60
+ if (!data.models?.length)
61
+ throw new Error('No models available in momentum feed');
62
+ // Return the top-ranked model's channel_id (e.g. "moonshotai/Kimi-K2.5")
63
+ return data.models[0].model;
64
+ }
65
+ /** Server-witnessed inference — WZRD calls the provider, grades quality */
66
+ async infer(prompt, model, taskType) {
67
+ const resolvedModel = model || await this.pickModel(taskType);
68
+ const res = await this.authedFetch('/v1/agent/infer', {
69
+ method: 'POST',
70
+ body: JSON.stringify({ model: resolvedModel, prompt, task_type: taskType || 'chat' }),
71
+ });
72
+ if (!res.ok) {
73
+ const body = await res.text().catch(() => '');
74
+ throw new Error(`Infer failed (${res.status}): ${body}`);
75
+ }
76
+ return res.json();
77
+ }
78
+ /** Report model pick with execution_id for verified rewards */
79
+ async report(params) {
80
+ const res = await this.authedFetch('/v1/agent/report', {
81
+ method: 'POST',
82
+ body: JSON.stringify(params),
83
+ });
84
+ if (!res.ok) {
85
+ const body = await res.text().catch(() => '');
86
+ throw new Error(`Report failed (${res.status}): ${body}`);
87
+ }
88
+ return res.json();
89
+ }
90
+ /** Check pending + total rewards (flattens nested /v1/agent/earned response) */
91
+ async getRewards() {
92
+ const res = await this.authedFetch('/v1/agent/earned');
93
+ if (!res.ok)
94
+ throw new Error(`Rewards check failed: ${res.status}`);
95
+ const data = await res.json();
96
+ const economy = data.economy;
97
+ const routing = data.routing;
98
+ return {
99
+ pending_ccm: Number(economy?.pending_ccm ?? 0),
100
+ total_rewarded_ccm: Number(economy?.earned_ccm ?? 0),
101
+ rank: null, // rank not in this endpoint
102
+ contribution_count: Number(routing?.lifetime_contributions ?? 0),
103
+ };
104
+ }
105
+ /** Gasless CCM claim via server relay */
106
+ async claimRelay() {
107
+ const res = await this.authedFetch(`/v1/claims/${this.pubkey}/relay`, {
108
+ method: 'POST',
109
+ });
110
+ if (!res.ok) {
111
+ const body = await res.text().catch(() => '');
112
+ throw new Error(`Claim relay failed (${res.status}): ${body}`);
113
+ }
114
+ return res.json();
115
+ }
116
+ /** Public: fetch leaderboard (no auth) */
117
+ async getLeaderboard(limit = 20) {
118
+ const res = await fetch(`${this.apiUrl}/v1/leaderboard?limit=${limit}`);
119
+ if (!res.ok)
120
+ throw new Error(`Leaderboard failed: ${res.status}`);
121
+ return res.json();
122
+ }
123
+ /** Public: fetch claims status */
124
+ async getClaimStatus() {
125
+ const res = await this.authedFetch(`/v1/claims/${this.pubkey}`);
126
+ if (!res.ok) {
127
+ if (res.status === 404)
128
+ return { cumulative_total: 0, claimed_total: 0, claimable: 0 };
129
+ throw new Error(`Claim status failed: ${res.status}`);
130
+ }
131
+ const data = await res.json();
132
+ return { ...data, claimable: data.cumulative_total - data.claimed_total };
133
+ }
17
134
  }
package/dist/index.d.ts CHANGED
@@ -1,16 +1,22 @@
1
1
  /**
2
- * @wzrd_sol/eliza-plugin — WZRD Liquid Attention Protocol for ElizaOS.
3
- * Thin adapter wrapping @wzrd_sol/solana-agent-plugin's WzrdClient.
2
+ * @wzrd_sol/eliza-plugin — WZRD Earn Loop for ElizaOS
4
3
  *
5
- * Config (runtime.getSetting): SOLANA_PRIVATE_KEY (required), WZRD_API_URL, SOLANA_RPC_URL
4
+ * Server-witnessed inference: infer report → earn CCM → claim.
5
+ * No external dependencies beyond @solana/web3.js + tweetnacl.
6
+ *
7
+ * Config (runtime.getSetting):
8
+ * SOLANA_PRIVATE_KEY — required, JSON array of secret key bytes
9
+ * WZRD_API_URL — optional, defaults to https://api.twzrd.xyz
6
10
  */
7
11
  import type { Plugin } from '@elizaos/core';
8
- import { leaderboardAction } from './actions/leaderboard.js';
9
- import { portfolioAction } from './actions/portfolio.js';
12
+ import { inferAction } from './actions/infer.js';
13
+ import { reportAction } from './actions/report.js';
14
+ import { earnAction } from './actions/earn.js';
10
15
  import { claimAction } from './actions/claim.js';
11
- import { depositAction } from './actions/deposit.js';
12
- import { velocityAction } from './actions/velocity.js';
16
+ import { rewardsAction } from './actions/rewards.js';
13
17
  export declare const wzrdPlugin: Plugin;
14
18
  export default wzrdPlugin;
15
- export { leaderboardAction, portfolioAction, claimAction, depositAction, velocityAction };
16
- export { getWzrdClient } from './client.js';
19
+ export { earnAction, inferAction, reportAction, claimAction, rewardsAction };
20
+ export { getWzrdClient, clearClientCache } from './client-factory.js';
21
+ export { WzrdClient } from './client.js';
22
+ export type { InferResult, ReportResult, RewardsBalance, ClaimResult } from './client.js';
package/dist/index.js CHANGED
@@ -1,14 +1,15 @@
1
- import { leaderboardAction } from './actions/leaderboard.js';
2
- import { portfolioAction } from './actions/portfolio.js';
1
+ import { inferAction } from './actions/infer.js';
2
+ import { reportAction } from './actions/report.js';
3
+ import { earnAction } from './actions/earn.js';
3
4
  import { claimAction } from './actions/claim.js';
4
- import { depositAction } from './actions/deposit.js';
5
- import { velocityAction } from './actions/velocity.js';
5
+ import { rewardsAction } from './actions/rewards.js';
6
6
  export const wzrdPlugin = {
7
7
  name: 'wzrd',
8
- description: 'WZRD Liquid Attention Protocol — deposit USDC into AI attention markets, ' +
9
- 'earn CCM yield based on model velocity.',
10
- actions: [leaderboardAction, portfolioAction, claimAction, depositAction, velocityAction],
8
+ description: 'WZRD Liquid Attention Protocol — earn CCM on Solana via server-witnessed inference. ' +
9
+ 'Infer through WZRD, report outcomes, claim rewards via gasless relay.',
10
+ actions: [earnAction, inferAction, reportAction, claimAction, rewardsAction],
11
11
  };
12
12
  export default wzrdPlugin;
13
- export { leaderboardAction, portfolioAction, claimAction, depositAction, velocityAction };
14
- export { getWzrdClient } from './client.js';
13
+ export { earnAction, inferAction, reportAction, claimAction, rewardsAction };
14
+ export { getWzrdClient, clearClientCache } from './client-factory.js';
15
+ export { WzrdClient } from './client.js';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@wzrd_sol/eliza-plugin",
3
- "version": "0.1.1",
4
- "description": "WZRD Liquid Attention Protocol plugin for ElizaOS — leaderboard, portfolio, deposit, claim, velocity",
3
+ "version": "0.2.0",
4
+ "description": "WZRD earn loop for ElizaOS — server-witnessed inference, verified CCM rewards, gasless claims",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -14,12 +14,13 @@
14
14
  },
15
15
  "scripts": {
16
16
  "build": "tsc",
17
+ "test": "node --test dist/test/*.js",
17
18
  "prepublishOnly": "npm run build"
18
19
  },
19
20
  "dependencies": {
20
- "@wzrd_sol/solana-agent-plugin": "^0.1.0",
21
- "@wzrd_sol/sdk": "^0.1.1",
22
- "@solana/web3.js": "^1.95.0"
21
+ "@solana/web3.js": "^1.95.0",
22
+ "bs58": "^5.0.0",
23
+ "tweetnacl": "^1.0.3"
23
24
  },
24
25
  "peerDependencies": {
25
26
  "@elizaos/core": "^1.0.0"
@@ -38,9 +39,11 @@
38
39
  "eliza",
39
40
  "elizaos",
40
41
  "wzrd",
41
- "attention",
42
- "defi",
43
- "ai-agent"
42
+ "ccm",
43
+ "inference",
44
+ "earn",
45
+ "ai-agent",
46
+ "mcp"
44
47
  ],
45
48
  "license": "MIT",
46
49
  "publishConfig": {
@@ -48,7 +51,7 @@
48
51
  },
49
52
  "repository": {
50
53
  "type": "git",
51
- "url": "https://github.com/twzrd-sol/wzrd-final",
54
+ "url": "git+https://github.com/twzrd-sol/wzrd-velocity.git",
52
55
  "directory": "agents/eliza-plugin"
53
56
  },
54
57
  "files": [
@@ -1,2 +0,0 @@
1
- import type { Action } from '@elizaos/core';
2
- export declare const depositAction: Action;
@@ -1,70 +0,0 @@
1
- /** ELIZA Action: WZRD_DEPOSIT — deposit USDC into attention market, mint vLOFI (auth + keypair). */
2
- import { Keypair, ComputeBudgetProgram, TransactionMessage, VersionedTransaction, Connection } from '@solana/web3.js';
3
- export const depositAction = {
4
- name: 'WZRD_DEPOSIT',
5
- similes: ['DEPOSIT_USDC', 'BUY_ATTENTION', 'ENTER_MARKET'],
6
- description: 'Deposit USDC into a WZRD attention market to mint vLOFI. Requires market_id and amount_usdc in message content.',
7
- examples: [[
8
- { name: '{{user1}}', content: { text: 'Deposit 0.01 USDC into WZRD market 10' } },
9
- { name: '{{agentName}}', content: { text: 'Deposited 0.0100 USDC into Market #10. Tx: X7FG...' } },
10
- ]],
11
- validate: async () => true,
12
- handler: async (runtime, message, _state, _opt, callback) => {
13
- const { market_id: marketId, amount_usdc: amountUsdc, priority_fee = 50_000 } = message.content;
14
- if (!marketId || !amountUsdc || amountUsdc > 100) {
15
- const text = !marketId || !amountUsdc
16
- ? 'Missing required: market_id and amount_usdc.' : 'Max 100 USDC per deposit.';
17
- await callback?.({ text });
18
- return { success: false, text };
19
- }
20
- const sk = runtime.getSetting('SOLANA_PRIVATE_KEY');
21
- if (!sk)
22
- throw new Error('SOLANA_PRIVATE_KEY not configured');
23
- const kp = Keypair.fromSecretKey(Uint8Array.from(JSON.parse(String(sk))));
24
- const payer = kp.publicKey;
25
- const amt = BigInt(Math.round(amountUsdc * 1e6));
26
- const rpcSetting = runtime.getSetting('SOLANA_RPC_URL');
27
- const rpc = (typeof rpcSetting === 'string' ? rpcSetting : undefined) || 'https://api.mainnet-beta.solana.com';
28
- const conn = new Connection(rpc, 'confirmed');
29
- const sdk = await import('@wzrd_sol/sdk');
30
- const vault = await sdk.fetchMarketVault(conn, marketId);
31
- if (!vault) {
32
- await callback?.({ text: `Market ${marketId} has no vault.` });
33
- return { success: false };
34
- }
35
- const pos = await sdk.fetchOnChainPosition(conn, payer, marketId);
36
- if (pos && pos.depositedAmount > 0n && !pos.settled) {
37
- await callback?.({ text: `Position exists in market ${marketId}.` });
38
- return { success: false };
39
- }
40
- const bal = await sdk.fetchTokenBalance(conn, sdk.getAta(payer, vault.depositMint, sdk.TOKEN_PROGRAM_ID));
41
- if (bal < amt) {
42
- await callback?.({ text: `Insufficient USDC.` });
43
- return { success: false };
44
- }
45
- const ixs = await sdk.createDepositMarketIx(conn, payer, marketId, amt);
46
- ixs.unshift(ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }), ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority_fee }));
47
- const { blockhash, lastValidBlockHeight } = await conn.getLatestBlockhash('confirmed');
48
- const tx = new VersionedTransaction(new TransactionMessage({ payerKey: payer, recentBlockhash: blockhash, instructions: ixs }).compileToV0Message());
49
- tx.sign([kp]);
50
- const sim = await conn.simulateTransaction(tx);
51
- if (sim.value.err) {
52
- await callback?.({ text: `Sim failed: ${JSON.stringify(sim.value.err)}` });
53
- return { success: false };
54
- }
55
- const sig = await conn.sendTransaction(tx, { skipPreflight: true, maxRetries: 3 });
56
- try {
57
- await Promise.race([
58
- conn.confirmTransaction({ signature: sig, blockhash, lastValidBlockHeight }, 'confirmed'),
59
- new Promise((_, rej) => setTimeout(() => rej(new Error('Timeout')), 60_000)),
60
- ]);
61
- }
62
- catch {
63
- await callback?.({ text: `Deposit sent, confirmation timed out. Tx: ${sig}` });
64
- return { success: true, data: { signature: sig, timed_out: true } };
65
- }
66
- const text = `Deposited ${amountUsdc.toFixed(4)} USDC into Market #${marketId}. Tx: ${sig.slice(0, 16)}...`;
67
- await callback?.({ text });
68
- return { success: true, text, data: { signature: sig, market_id: marketId, amount_usdc: amountUsdc } };
69
- },
70
- };
@@ -1,3 +0,0 @@
1
- /** ELIZA Action: WZRD_LEADERBOARD — fetch attention market leaderboard (no auth). */
2
- import type { Action } from '@elizaos/core';
3
- export declare const leaderboardAction: Action;
@@ -1,25 +0,0 @@
1
- import { getWzrdClient } from '../client.js';
2
- export const leaderboardAction = {
3
- name: 'WZRD_LEADERBOARD',
4
- similes: ['WZRD_MARKETS', 'WZRD_TRENDING', 'TOP_MODELS'],
5
- description: 'Fetch the WZRD attention market leaderboard ranked by velocity EMA.',
6
- examples: [[
7
- { name: '{{user1}}', content: { text: 'Show me the top WZRD markets' } },
8
- { name: '{{agentName}}', content: { text: 'Here are the top WZRD attention markets...' } },
9
- ]],
10
- validate: async () => true,
11
- handler: async (runtime, _msg, _state, _opt, callback) => {
12
- const result = await getWzrdClient(runtime).getLeaderboard(10);
13
- const lines = result.markets.map((m, i) => `${i + 1}. ${m.metric} — ${fmtV(m.velocity_ema)} velocity (${m.platform}) | ${m.multiplier_bps / 10_000}x | id=${m.market_id}`);
14
- const text = `WZRD Leaderboard (${result.markets.length} markets, root_seq=${result.root.root_seq}):\n${lines.join('\n')}`;
15
- await callback?.({ text });
16
- return { success: true, data: result };
17
- },
18
- };
19
- function fmtV(v) {
20
- if (v >= 1e6)
21
- return `${(v / 1e6).toFixed(1)}M`;
22
- if (v >= 1e3)
23
- return `${(v / 1e3).toFixed(0)}K`;
24
- return v.toFixed(0);
25
- }
@@ -1,3 +0,0 @@
1
- /** ELIZA Action: WZRD_PORTFOLIO — fetch agent's positions + CCM earned (auth). */
2
- import type { Action } from '@elizaos/core';
3
- export declare const portfolioAction: Action;
@@ -1,20 +0,0 @@
1
- import { getWzrdClient } from '../client.js';
2
- export const portfolioAction = {
3
- name: 'WZRD_PORTFOLIO',
4
- similes: ['WZRD_POSITIONS', 'WZRD_BALANCE', 'MY_POSITIONS'],
5
- description: 'Fetch your WZRD portfolio — open positions, USDC deposited, CCM earned.',
6
- examples: [[
7
- { name: '{{user1}}', content: { text: 'Show my WZRD portfolio' } },
8
- { name: '{{agentName}}', content: { text: 'Your WZRD portfolio has 2 open positions...' } },
9
- ]],
10
- validate: async () => true,
11
- handler: async (runtime, _msg, _state, _opt, callback) => {
12
- const p = await getWzrdClient(runtime).getPortfolio();
13
- const open = p.positions.filter((x) => !x.is_settled);
14
- const lines = open.map((x) => `Market #${x.market_id} (${x.metric}): ${(x.usdc_deposited / 1e6).toFixed(4)} USDC, ${(x.multiplier_bps / 1e4).toFixed(1)}x`);
15
- const text = `Portfolio (${open.length} open):\n${lines.length ? lines.join('\n') : '(none)'}\n` +
16
- `Total: ${(p.total_deposited_usdc / 1e6).toFixed(4)} USDC, ${(p.total_ccm_earned / 1e6).toFixed(4)} CCM`;
17
- await callback?.({ text });
18
- return { success: true, data: p };
19
- },
20
- };
@@ -1,3 +0,0 @@
1
- /** ELIZA Action: WZRD_VELOCITY — classify attention signal strength (no auth). */
2
- import type { Action } from '@elizaos/core';
3
- export declare const velocityAction: Action;
@@ -1,57 +0,0 @@
1
- import { getWzrdClient } from '../client.js';
2
- export const velocityAction = {
3
- name: 'WZRD_VELOCITY',
4
- similes: ['WZRD_SIGNAL', 'ATTENTION_SIGNAL', 'VELOCITY_CHECK'],
5
- description: 'Analyze velocity across WZRD markets. Classifies into BREAKOUT/MOMENTUM/EMERGING/STABLE/COOLING/WEAK.',
6
- examples: [[
7
- { name: '{{user1}}', content: { text: 'Analyze WZRD velocity signals' } },
8
- { name: '{{agentName}}', content: { text: 'BREAKOUT: Qwen 3.5 35B (450K velocity, p95)...' } },
9
- ]],
10
- validate: async () => true,
11
- handler: async (runtime, _msg, _state, _opt, callback) => {
12
- const { markets } = await getWzrdClient(runtime).getLeaderboard(50);
13
- if (!markets.length) {
14
- await callback?.({ text: 'No markets found.' });
15
- return { success: true, data: { signals: [] } };
16
- }
17
- const sorted = [...markets].sort((a, b) => a.velocity_ema - b.velocity_ema);
18
- const signals = markets.map((m) => {
19
- const pct = ((sorted.findIndex((s) => s.market_id === m.market_id) + 1) / sorted.length) * 100;
20
- return { market_id: m.market_id, metric: m.metric, platform: m.platform,
21
- velocity_ema: m.velocity_ema, signal: classify(m, pct), percentile: Math.round(pct) };
22
- });
23
- const tiers = ['BREAKOUT', 'MOMENTUM', 'EMERGING', 'STABLE', 'COOLING', 'WEAK'];
24
- const lines = [];
25
- for (const t of tiers) {
26
- const g = signals.filter((s) => s.signal === t);
27
- if (!g.length)
28
- continue;
29
- lines.push(`${t}:`);
30
- g.forEach((s) => lines.push(` ${s.metric} (${fmtV(s.velocity_ema)}, p${s.percentile}) [${s.platform}]`));
31
- }
32
- const median = sorted[Math.floor(sorted.length / 2)]?.velocity_ema ?? 0;
33
- const text = `Velocity analysis (${markets.length} markets, median ${fmtV(median)}):\n${lines.join('\n')}`;
34
- await callback?.({ text });
35
- return { success: true, data: { signals, median_velocity: median } };
36
- },
37
- };
38
- function classify(m, p) {
39
- if (p >= 90)
40
- return 'BREAKOUT';
41
- if (p >= 70)
42
- return 'MOMENTUM';
43
- if (m.snapshot_count < 300 && p >= 50)
44
- return 'EMERGING';
45
- if (p >= 40)
46
- return 'STABLE';
47
- if (p >= 20)
48
- return 'COOLING';
49
- return 'WEAK';
50
- }
51
- function fmtV(v) {
52
- if (v >= 1e6)
53
- return `${(v / 1e6).toFixed(1)}M`;
54
- if (v >= 1e3)
55
- return `${(v / 1e3).toFixed(0)}K`;
56
- return v.toFixed(0);
57
- }