@wzrd_sol/sdk 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.
- package/README.md +2 -0
- package/dist/accounts.d.ts +13 -0
- package/dist/accounts.js +42 -1
- package/dist/accounts.test.d.ts +1 -0
- package/dist/accounts.test.js +47 -0
- package/dist/agent-auth.d.ts +89 -0
- package/dist/agent-auth.js +147 -0
- package/dist/agent-loop.d.ts +87 -0
- package/dist/agent-loop.js +388 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +3 -0
- package/dist/index.d.ts +15 -6
- package/dist/index.js +13 -5
- package/dist/instructions.d.ts +551 -5
- package/dist/instructions.js +1466 -24
- package/dist/instructions.test.d.ts +7 -0
- package/dist/instructions.test.js +318 -0
- package/dist/model-selector.d.ts +141 -0
- package/dist/model-selector.js +247 -0
- package/dist/nav.d.ts +40 -0
- package/dist/nav.js +39 -0
- package/dist/nav.test.d.ts +4 -0
- package/dist/nav.test.js +98 -0
- package/dist/pda.d.ts +4 -0
- package/dist/pda.js +10 -1
- package/dist/stream.d.ts +88 -0
- package/dist/stream.js +220 -0
- package/dist/stream.test.d.ts +7 -0
- package/dist/stream.test.js +296 -0
- package/package.json +9 -3
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model Selector — pick the best open-source model using live WZRD velocity data.
|
|
3
|
+
*
|
|
4
|
+
* Combines on-chain attention signals from the WZRD protocol with
|
|
5
|
+
* OpenRouter pricing to compute a value score for every tracked model,
|
|
6
|
+
* then returns a ranked list filtered by budget, task, and trend.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* import { bestModel } from '@wzrd_sol/sdk';
|
|
10
|
+
* const picks = await bestModel({ task: 'code', budget: 'micro' });
|
|
11
|
+
* console.log(picks[0].model_id);
|
|
12
|
+
*
|
|
13
|
+
* @module model-selector
|
|
14
|
+
*/
|
|
15
|
+
// ── Constants ─────────────────────────────────────────────────────
|
|
16
|
+
const DEFAULT_WZRD_URL = 'https://api.twzrd.xyz';
|
|
17
|
+
const DEFAULT_OPENROUTER_URL = 'https://openrouter.ai/api/v1/models';
|
|
18
|
+
const DEFAULT_CACHE_TTL_MS = 300000; // 5 minutes
|
|
19
|
+
const EPSILON = 0.001;
|
|
20
|
+
/** Maximum blended price per million tokens for each budget tier. */
|
|
21
|
+
const BUDGET_LIMITS = {
|
|
22
|
+
micro: 0.20,
|
|
23
|
+
budget: 1.00,
|
|
24
|
+
mid: 5.00,
|
|
25
|
+
premium: Infinity,
|
|
26
|
+
};
|
|
27
|
+
/** Confidence levels ranked from lowest to highest. */
|
|
28
|
+
const CONFIDENCE_RANK = {
|
|
29
|
+
insufficient: 0,
|
|
30
|
+
low: 1,
|
|
31
|
+
medium: 2,
|
|
32
|
+
high: 3,
|
|
33
|
+
};
|
|
34
|
+
/** Task-specific model name patterns and their score boost multiplier. */
|
|
35
|
+
const TASK_BOOSTS = {
|
|
36
|
+
code: {
|
|
37
|
+
patterns: ['deepseek', 'qwen', 'codestral', 'starcoder', 'coder'],
|
|
38
|
+
boost: 1.3,
|
|
39
|
+
},
|
|
40
|
+
reasoning: {
|
|
41
|
+
patterns: ['deepseek-r', 'o1', 'o3', 'reasoning'],
|
|
42
|
+
boost: 1.5,
|
|
43
|
+
},
|
|
44
|
+
chat: {
|
|
45
|
+
patterns: ['claude', 'gpt', 'gemini', 'chat', 'llama'],
|
|
46
|
+
boost: 1.1,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
// ── ModelSelector Class ───────────────────────────────────────────
|
|
50
|
+
/**
|
|
51
|
+
* Fetches WZRD velocity data and OpenRouter pricing, caches results,
|
|
52
|
+
* and scores models by value (velocity / cost).
|
|
53
|
+
*
|
|
54
|
+
* Create one instance and reuse it — the internal cache avoids redundant
|
|
55
|
+
* HTTP requests within the TTL window.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* const selector = new ModelSelector({ wzrd_base_url: 'https://api.twzrd.xyz' });
|
|
60
|
+
* const picks = await selector.select({ task: 'code', budget: 'budget', limit: 3 });
|
|
61
|
+
* console.log(picks[0].model_id);
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export class ModelSelector {
|
|
65
|
+
constructor(config = {}) {
|
|
66
|
+
this.cache = new Map();
|
|
67
|
+
this.wzrdBaseUrl = (config.wzrd_base_url ?? DEFAULT_WZRD_URL).replace(/\/+$/, '');
|
|
68
|
+
this.openrouterUrl = config.openrouter_url ?? DEFAULT_OPENROUTER_URL;
|
|
69
|
+
this.cacheTtlMs = config.cache_ttl_ms ?? DEFAULT_CACHE_TTL_MS;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Select the best models for a given task and budget.
|
|
73
|
+
*
|
|
74
|
+
* Fetches live data from the WZRD leaderboard, momentum signal, and
|
|
75
|
+
* OpenRouter catalog (all cached for 5 minutes by default), then ranks
|
|
76
|
+
* every OpenRouter model tracked by WZRD using:
|
|
77
|
+
*
|
|
78
|
+
* value_score = velocity_ema / (blended_price_per_m_tokens + epsilon)
|
|
79
|
+
*
|
|
80
|
+
* Results are filtered by budget tier, task relevance, minimum confidence,
|
|
81
|
+
* and exclusion list, then sorted by value_score descending.
|
|
82
|
+
*
|
|
83
|
+
* @param options - Filtering and ranking options.
|
|
84
|
+
* @returns Ranked array of model recommendations (may be empty).
|
|
85
|
+
*/
|
|
86
|
+
async select(options = {}) {
|
|
87
|
+
const { budget = 'mid', task = 'chat', min_confidence = 'low', limit = 5, exclude = [], } = options;
|
|
88
|
+
// Fetch all three data sources in parallel (cached).
|
|
89
|
+
const [leaderboard, momentum, catalog] = await Promise.all([
|
|
90
|
+
this.fetchLeaderboard(),
|
|
91
|
+
this.fetchMomentum(),
|
|
92
|
+
this.fetchOpenRouterCatalog(),
|
|
93
|
+
]);
|
|
94
|
+
// If any critical source failed, return empty (graceful degradation).
|
|
95
|
+
if (leaderboard === null || catalog === null) {
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
// Build lookup indices.
|
|
99
|
+
const orIndex = new Map();
|
|
100
|
+
for (const m of catalog.data) {
|
|
101
|
+
orIndex.set(m.id, m);
|
|
102
|
+
}
|
|
103
|
+
const momentumIndex = new Map();
|
|
104
|
+
if (momentum !== null) {
|
|
105
|
+
for (const m of momentum.models) {
|
|
106
|
+
momentumIndex.set(m.market_id, m);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Filtering thresholds.
|
|
110
|
+
const maxPrice = BUDGET_LIMITS[budget];
|
|
111
|
+
const minConfidenceRank = CONFIDENCE_RANK[min_confidence];
|
|
112
|
+
const taskBoost = TASK_BOOSTS[task];
|
|
113
|
+
const excludeLower = exclude.map((e) => e.toLowerCase());
|
|
114
|
+
const candidates = [];
|
|
115
|
+
for (const market of leaderboard.markets) {
|
|
116
|
+
if (market.platform !== 'openrouter')
|
|
117
|
+
continue;
|
|
118
|
+
if (market.status !== 'open')
|
|
119
|
+
continue;
|
|
120
|
+
const orModel = orIndex.get(market.channel_id);
|
|
121
|
+
if (!orModel)
|
|
122
|
+
continue;
|
|
123
|
+
// Exclusion filter.
|
|
124
|
+
if (excludeLower.some((e) => market.channel_id.toLowerCase().includes(e)))
|
|
125
|
+
continue;
|
|
126
|
+
// Compute blended price per million tokens (3:1 prompt:completion weighting).
|
|
127
|
+
const promptPrice = parseFloat(orModel.pricing?.prompt ?? '0');
|
|
128
|
+
const completionPrice = parseFloat(orModel.pricing?.completion ?? '0');
|
|
129
|
+
const blendedPerM = ((promptPrice * 3 + completionPrice) / 4) * 1000000;
|
|
130
|
+
// Budget filter.
|
|
131
|
+
if (blendedPerM > maxPrice)
|
|
132
|
+
continue;
|
|
133
|
+
// Momentum data.
|
|
134
|
+
const mom = momentumIndex.get(market.market_id);
|
|
135
|
+
const trend = mom?.velocity_trend ?? 'insufficient_history';
|
|
136
|
+
const confidence = mom?.history_confidence ?? 'insufficient';
|
|
137
|
+
// Confidence filter.
|
|
138
|
+
const confidenceRank = CONFIDENCE_RANK[confidence];
|
|
139
|
+
if (confidenceRank < minConfidenceRank)
|
|
140
|
+
continue;
|
|
141
|
+
// Compute value score: velocity / (price + epsilon).
|
|
142
|
+
const velocityEma = market.velocity_ema;
|
|
143
|
+
const rawValueScore = velocityEma / (blendedPerM + EPSILON);
|
|
144
|
+
// Apply task-specific boost.
|
|
145
|
+
let taskMultiplier = 1.0;
|
|
146
|
+
if (taskBoost) {
|
|
147
|
+
for (const pattern of taskBoost.patterns) {
|
|
148
|
+
if (market.channel_id.toLowerCase().includes(pattern)) {
|
|
149
|
+
taskMultiplier = taskBoost.boost;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const valueScore = rawValueScore * taskMultiplier;
|
|
155
|
+
candidates.push({
|
|
156
|
+
model_id: market.channel_id,
|
|
157
|
+
provider: 'openrouter',
|
|
158
|
+
price_per_m_tokens: Math.round(blendedPerM * 1000) / 1000,
|
|
159
|
+
velocity_ema: velocityEma,
|
|
160
|
+
value_score: Math.round(valueScore * 1000) / 1000,
|
|
161
|
+
trend,
|
|
162
|
+
confidence,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
// Sort by value_score descending.
|
|
166
|
+
candidates.sort((a, b) => b.value_score - a.value_score);
|
|
167
|
+
return candidates.slice(0, limit);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Invalidate the in-memory cache.
|
|
171
|
+
*
|
|
172
|
+
* Useful when you know upstream data has changed and want
|
|
173
|
+
* fresh results on the next `select()` call.
|
|
174
|
+
*/
|
|
175
|
+
clearCache() {
|
|
176
|
+
this.cache.clear();
|
|
177
|
+
}
|
|
178
|
+
// ── Private Helpers ───────────────────────────────────────────
|
|
179
|
+
async fetchLeaderboard() {
|
|
180
|
+
return this.cachedFetch('wzrd:leaderboard', `${this.wzrdBaseUrl}/v1/leaderboard?limit=100`);
|
|
181
|
+
}
|
|
182
|
+
async fetchMomentum() {
|
|
183
|
+
return this.cachedFetch('wzrd:momentum', `${this.wzrdBaseUrl}/v1/signals/momentum`);
|
|
184
|
+
}
|
|
185
|
+
async fetchOpenRouterCatalog() {
|
|
186
|
+
return this.cachedFetch('openrouter:catalog', this.openrouterUrl);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Fetch JSON from a URL with in-memory TTL caching.
|
|
190
|
+
* Returns null on any error (network, HTTP status, parse failure).
|
|
191
|
+
*/
|
|
192
|
+
async cachedFetch(key, url) {
|
|
193
|
+
const now = Date.now();
|
|
194
|
+
const cached = this.cache.get(key);
|
|
195
|
+
if (cached && cached.expires_at > now) {
|
|
196
|
+
return cached.data;
|
|
197
|
+
}
|
|
198
|
+
try {
|
|
199
|
+
const resp = await fetch(url);
|
|
200
|
+
if (!resp.ok) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
const data = (await resp.json());
|
|
204
|
+
this.cache.set(key, { data, expires_at: now + this.cacheTtlMs });
|
|
205
|
+
return data;
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// ── Convenience Function ──────────────────────────────────────────
|
|
213
|
+
/** Shared singleton used by the convenience `bestModel()` function. */
|
|
214
|
+
let defaultSelector = null;
|
|
215
|
+
/**
|
|
216
|
+
* Pick the best open-source model for a task using live WZRD velocity data.
|
|
217
|
+
*
|
|
218
|
+
* This is a convenience wrapper around {@link ModelSelector.select} that
|
|
219
|
+
* uses a module-level singleton with default configuration. For custom
|
|
220
|
+
* base URLs or cache settings, instantiate {@link ModelSelector} directly.
|
|
221
|
+
*
|
|
222
|
+
* @param options - Filtering and ranking options.
|
|
223
|
+
* @returns Ranked array of model recommendations (may be empty if the API is unreachable).
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* ```ts
|
|
227
|
+
* import { bestModel } from '@wzrd_sol/sdk';
|
|
228
|
+
*
|
|
229
|
+
* // Cheapest model good for code
|
|
230
|
+
* const picks = await bestModel({ task: 'code', budget: 'micro' });
|
|
231
|
+
* console.log(picks[0].model_id);
|
|
232
|
+
*
|
|
233
|
+
* // Premium reasoning model, exclude specific providers
|
|
234
|
+
* const reasoning = await bestModel({
|
|
235
|
+
* task: 'reasoning',
|
|
236
|
+
* budget: 'premium',
|
|
237
|
+
* exclude: ['gpt'],
|
|
238
|
+
* limit: 3,
|
|
239
|
+
* });
|
|
240
|
+
* ```
|
|
241
|
+
*/
|
|
242
|
+
export async function bestModel(options = {}) {
|
|
243
|
+
if (!defaultSelector) {
|
|
244
|
+
defaultSelector = new ModelSelector();
|
|
245
|
+
}
|
|
246
|
+
return defaultSelector.select(options);
|
|
247
|
+
}
|
package/dist/nav.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NAV (Net Asset Value) helpers for vLOFI share computation.
|
|
3
|
+
*
|
|
4
|
+
* These pure functions mirror the on-chain deposit/settle math so that
|
|
5
|
+
* clients can preview shares received or principal returned without
|
|
6
|
+
* submitting a transaction.
|
|
7
|
+
*
|
|
8
|
+
* NAV BPS domain:
|
|
9
|
+
* - 10_000 = 1.00x (1 USDC per share, initial NAV)
|
|
10
|
+
* - 50_000 = 5.00x (max NAV, protocol ceiling)
|
|
11
|
+
* - 0 = uninitialized (pre-realloc vaults, treated as 10_000)
|
|
12
|
+
*/
|
|
13
|
+
/** Structured NAV data extracted from a MarketVault. */
|
|
14
|
+
export interface NavInfo {
|
|
15
|
+
/** NAV per share in basis points (10_000 = 1.0x). Zero means uninitialized. */
|
|
16
|
+
navPerShareBps: bigint;
|
|
17
|
+
/** Slot at which NAV was last updated on-chain. */
|
|
18
|
+
lastNavUpdateSlot: bigint;
|
|
19
|
+
/** Human-readable share price in USDC terms (navPerShareBps / 10_000). */
|
|
20
|
+
sharePrice: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Compute the number of vLOFI shares a deposit of `amount` base units will mint.
|
|
24
|
+
*
|
|
25
|
+
* Formula (matches vault.rs deposit_market):
|
|
26
|
+
* shares = amount * 10_000 / nav_per_share_bps
|
|
27
|
+
*
|
|
28
|
+
* If navPerShareBps is 0 (uninitialized / pre-realloc vault), falls back to
|
|
29
|
+
* the default 10_000 BPS (1:1 ratio).
|
|
30
|
+
*/
|
|
31
|
+
export declare function computeSharesForDeposit(amount: bigint, navPerShareBps: bigint): bigint;
|
|
32
|
+
/**
|
|
33
|
+
* Compute the USDC principal returned when settling `shares` of vLOFI.
|
|
34
|
+
*
|
|
35
|
+
* Formula (matches vault.rs settle_market):
|
|
36
|
+
* principal = shares * nav_per_share_bps / 10_000
|
|
37
|
+
*
|
|
38
|
+
* If navPerShareBps is 0 (uninitialized), falls back to 10_000 BPS.
|
|
39
|
+
*/
|
|
40
|
+
export declare function computePrincipalForSettle(shares: bigint, navPerShareBps: bigint): bigint;
|
package/dist/nav.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NAV (Net Asset Value) helpers for vLOFI share computation.
|
|
3
|
+
*
|
|
4
|
+
* These pure functions mirror the on-chain deposit/settle math so that
|
|
5
|
+
* clients can preview shares received or principal returned without
|
|
6
|
+
* submitting a transaction.
|
|
7
|
+
*
|
|
8
|
+
* NAV BPS domain:
|
|
9
|
+
* - 10_000 = 1.00x (1 USDC per share, initial NAV)
|
|
10
|
+
* - 50_000 = 5.00x (max NAV, protocol ceiling)
|
|
11
|
+
* - 0 = uninitialized (pre-realloc vaults, treated as 10_000)
|
|
12
|
+
*/
|
|
13
|
+
const DEFAULT_NAV_BPS = 10000n;
|
|
14
|
+
const BPS_SCALE = 10000n;
|
|
15
|
+
/**
|
|
16
|
+
* Compute the number of vLOFI shares a deposit of `amount` base units will mint.
|
|
17
|
+
*
|
|
18
|
+
* Formula (matches vault.rs deposit_market):
|
|
19
|
+
* shares = amount * 10_000 / nav_per_share_bps
|
|
20
|
+
*
|
|
21
|
+
* If navPerShareBps is 0 (uninitialized / pre-realloc vault), falls back to
|
|
22
|
+
* the default 10_000 BPS (1:1 ratio).
|
|
23
|
+
*/
|
|
24
|
+
export function computeSharesForDeposit(amount, navPerShareBps) {
|
|
25
|
+
const effectiveNav = navPerShareBps === 0n ? DEFAULT_NAV_BPS : navPerShareBps;
|
|
26
|
+
return (amount * BPS_SCALE) / effectiveNav;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Compute the USDC principal returned when settling `shares` of vLOFI.
|
|
30
|
+
*
|
|
31
|
+
* Formula (matches vault.rs settle_market):
|
|
32
|
+
* principal = shares * nav_per_share_bps / 10_000
|
|
33
|
+
*
|
|
34
|
+
* If navPerShareBps is 0 (uninitialized), falls back to 10_000 BPS.
|
|
35
|
+
*/
|
|
36
|
+
export function computePrincipalForSettle(shares, navPerShareBps) {
|
|
37
|
+
const effectiveNav = navPerShareBps === 0n ? DEFAULT_NAV_BPS : navPerShareBps;
|
|
38
|
+
return (shares * effectiveNav) / BPS_SCALE;
|
|
39
|
+
}
|
package/dist/nav.test.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for NAV computation helpers (computeSharesForDeposit, computePrincipalForSettle).
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect } from 'vitest';
|
|
5
|
+
import { computeSharesForDeposit, computePrincipalForSettle } from './nav.js';
|
|
6
|
+
// ── computeSharesForDeposit ──────────────────────────────
|
|
7
|
+
describe('computeSharesForDeposit', () => {
|
|
8
|
+
it('returns 1:1 shares at default NAV (10_000 BPS)', () => {
|
|
9
|
+
const shares = computeSharesForDeposit(1000000n, 10000n);
|
|
10
|
+
expect(shares).toBe(1000000n);
|
|
11
|
+
});
|
|
12
|
+
it('returns half shares at 2x NAV (20_000 BPS)', () => {
|
|
13
|
+
const shares = computeSharesForDeposit(1000000n, 20000n);
|
|
14
|
+
expect(shares).toBe(500000n);
|
|
15
|
+
});
|
|
16
|
+
it('handles zero NAV fallback — treats as 10_000 BPS', () => {
|
|
17
|
+
// zero nav fallback: uninitialized vault should behave like 1:1
|
|
18
|
+
const shares = computeSharesForDeposit(1000000n, 0n);
|
|
19
|
+
expect(shares).toBe(1000000n);
|
|
20
|
+
});
|
|
21
|
+
it('handles max NAV of 50_000 BPS (5.0x)', () => {
|
|
22
|
+
// max nav: 50_000 BPS ceiling
|
|
23
|
+
const shares = computeSharesForDeposit(5000000n, 50000n);
|
|
24
|
+
expect(shares).toBe(1000000n);
|
|
25
|
+
});
|
|
26
|
+
it('returns 0 shares for 0 deposit amount', () => {
|
|
27
|
+
const shares = computeSharesForDeposit(0n, 10000n);
|
|
28
|
+
expect(shares).toBe(0n);
|
|
29
|
+
});
|
|
30
|
+
it('handles large deposit without overflow', () => {
|
|
31
|
+
// overflow test: large amount should not throw
|
|
32
|
+
const amount = 1000000000000n; // 1M USDC in base units
|
|
33
|
+
const shares = computeSharesForDeposit(amount, 10000n);
|
|
34
|
+
expect(shares).toBe(amount);
|
|
35
|
+
});
|
|
36
|
+
it('truncates fractional shares (integer division)', () => {
|
|
37
|
+
// 3 base units at 2x NAV = 1.5 → truncates to 1
|
|
38
|
+
const shares = computeSharesForDeposit(3n, 20000n);
|
|
39
|
+
expect(shares).toBe(1n);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
// ── computePrincipalForSettle ─────────────────────────────
|
|
43
|
+
describe('computePrincipalForSettle', () => {
|
|
44
|
+
it('returns 1:1 principal at default NAV (10_000 BPS)', () => {
|
|
45
|
+
const principal = computePrincipalForSettle(1000000n, 10000n);
|
|
46
|
+
expect(principal).toBe(1000000n);
|
|
47
|
+
});
|
|
48
|
+
it('returns double principal at 2x NAV (20_000 BPS)', () => {
|
|
49
|
+
const principal = computePrincipalForSettle(1000000n, 20000n);
|
|
50
|
+
expect(principal).toBe(2000000n);
|
|
51
|
+
});
|
|
52
|
+
it('handles zero NAV fallback — treats as 10_000 BPS', () => {
|
|
53
|
+
// zero nav fallback
|
|
54
|
+
const principal = computePrincipalForSettle(1000000n, 0n);
|
|
55
|
+
expect(principal).toBe(1000000n);
|
|
56
|
+
});
|
|
57
|
+
it('handles max NAV of 50_000 BPS (5.0x)', () => {
|
|
58
|
+
// max nav: 50_000 BPS ceiling
|
|
59
|
+
const principal = computePrincipalForSettle(1000000n, 50000n);
|
|
60
|
+
expect(principal).toBe(5000000n);
|
|
61
|
+
});
|
|
62
|
+
it('returns 0 principal for 0 shares', () => {
|
|
63
|
+
const principal = computePrincipalForSettle(0n, 10000n);
|
|
64
|
+
expect(principal).toBe(0n);
|
|
65
|
+
});
|
|
66
|
+
it('handles large shares without overflow', () => {
|
|
67
|
+
// overflow test: large shares
|
|
68
|
+
const shares = 1000000000000n;
|
|
69
|
+
const principal = computePrincipalForSettle(shares, 10000n);
|
|
70
|
+
expect(principal).toBe(shares);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
// ── NavInfo type ─────────────────────────────────────────
|
|
74
|
+
describe('NavInfo type', () => {
|
|
75
|
+
it('can be constructed with valid fields', () => {
|
|
76
|
+
const info = {
|
|
77
|
+
navPerShareBps: 15000n,
|
|
78
|
+
lastNavUpdateSlot: 405000000n,
|
|
79
|
+
sharePrice: 1.5,
|
|
80
|
+
};
|
|
81
|
+
expect(info.navPerShareBps).toBe(15000n);
|
|
82
|
+
expect(info.lastNavUpdateSlot).toBe(405000000n);
|
|
83
|
+
expect(info.sharePrice).toBe(1.5);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
// ── Round-trip deposit→settle ────────────────────────────
|
|
87
|
+
describe('deposit-settle round trip', () => {
|
|
88
|
+
it('deposit then settle at same NAV returns original amount', () => {
|
|
89
|
+
const amount = 1000000n;
|
|
90
|
+
const nav = 12500n;
|
|
91
|
+
const shares = computeSharesForDeposit(amount, nav);
|
|
92
|
+
const principal = computePrincipalForSettle(shares, nav);
|
|
93
|
+
// Due to integer division, principal <= amount
|
|
94
|
+
expect(principal).toBeLessThanOrEqual(amount);
|
|
95
|
+
// But should be close (within 1 base unit rounding)
|
|
96
|
+
expect(amount - principal).toBeLessThan(nav);
|
|
97
|
+
});
|
|
98
|
+
});
|
package/dist/pda.d.ts
CHANGED
|
@@ -16,5 +16,9 @@ export declare function getGlobalRootConfigPDA(ccmMint: PublicKey, programId?: P
|
|
|
16
16
|
export declare function getClaimStatePDA(ccmMint: PublicKey, claimer: PublicKey, programId?: PublicKey): PublicKey;
|
|
17
17
|
/** Derive a ChannelConfigV2 PDA for a given mint and subject. */
|
|
18
18
|
export declare function getChannelConfigV2PDA(mint: PublicKey, subject: PublicKey, programId?: PublicKey): PublicKey;
|
|
19
|
+
/** Derive the StreamRootConfig PDA for a given vLOFI mint. */
|
|
20
|
+
export declare function getStreamRootConfigPDA(vlofiMint: PublicKey, programId?: PublicKey): PublicKey;
|
|
21
|
+
/** Derive the per-user ClaimStateStream PDA. */
|
|
22
|
+
export declare function getClaimStateStreamPDA(vlofiMint: PublicKey, claimer: PublicKey, programId?: PublicKey): PublicKey;
|
|
19
23
|
/** Derive an Associated Token Account address (works for both SPL and Token-2022). */
|
|
20
24
|
export declare function getAta(owner: PublicKey, mint: PublicKey, tokenProgramId: PublicKey): PublicKey;
|
package/dist/pda.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Seeds must match programs/attention-oracle/src/constants.rs.
|
|
5
5
|
*/
|
|
6
6
|
import { PublicKey } from '@solana/web3.js';
|
|
7
|
-
import { PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID, PROTOCOL_STATE_SEED, MARKET_VAULT_SEED, MARKET_POSITION_SEED, GLOBAL_ROOT_SEED, CLAIM_STATE_GLOBAL_SEED, CHANNEL_CONFIG_V2_SEED, } from './constants.js';
|
|
7
|
+
import { PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID, PROTOCOL_STATE_SEED, MARKET_VAULT_SEED, MARKET_POSITION_SEED, GLOBAL_ROOT_SEED, CLAIM_STATE_GLOBAL_SEED, CHANNEL_CONFIG_V2_SEED, STREAM_ROOT_SEED, CLAIM_STATE_STREAM_SEED, } from './constants.js';
|
|
8
8
|
/** Derive the singleton ProtocolState PDA. */
|
|
9
9
|
export function getProtocolStatePDA(programId = PROGRAM_ID) {
|
|
10
10
|
return PublicKey.findProgramAddressSync([Buffer.from(PROTOCOL_STATE_SEED)], programId)[0];
|
|
@@ -31,6 +31,15 @@ export function getClaimStatePDA(ccmMint, claimer, programId = PROGRAM_ID) {
|
|
|
31
31
|
export function getChannelConfigV2PDA(mint, subject, programId = PROGRAM_ID) {
|
|
32
32
|
return PublicKey.findProgramAddressSync([Buffer.from(CHANNEL_CONFIG_V2_SEED), mint.toBuffer(), subject.toBuffer()], programId)[0];
|
|
33
33
|
}
|
|
34
|
+
// ── Stream (vLOFI distribution) PDA Derivation ──────────
|
|
35
|
+
/** Derive the StreamRootConfig PDA for a given vLOFI mint. */
|
|
36
|
+
export function getStreamRootConfigPDA(vlofiMint, programId = PROGRAM_ID) {
|
|
37
|
+
return PublicKey.findProgramAddressSync([Buffer.from(STREAM_ROOT_SEED), vlofiMint.toBuffer()], programId)[0];
|
|
38
|
+
}
|
|
39
|
+
/** Derive the per-user ClaimStateStream PDA. */
|
|
40
|
+
export function getClaimStateStreamPDA(vlofiMint, claimer, programId = PROGRAM_ID) {
|
|
41
|
+
return PublicKey.findProgramAddressSync([Buffer.from(CLAIM_STATE_STREAM_SEED), vlofiMint.toBuffer(), claimer.toBuffer()], programId)[0];
|
|
42
|
+
}
|
|
34
43
|
/** Derive an Associated Token Account address (works for both SPL and Token-2022). */
|
|
35
44
|
export function getAta(owner, mint, tokenProgramId) {
|
|
36
45
|
return PublicKey.findProgramAddressSync([owner.toBuffer(), tokenProgramId.toBuffer(), mint.toBuffer()], ASSOCIATED_TOKEN_PROGRAM_ID)[0];
|
package/dist/stream.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Instruction builders for the wzrd-stream vLOFI claim system.
|
|
3
|
+
*
|
|
4
|
+
* Builds publish_stream_root, claim_stream, and claim_stream_sponsored
|
|
5
|
+
* TransactionInstructions for the vLOFI streaming distribution pipeline.
|
|
6
|
+
*
|
|
7
|
+
* Unlike CCM global claims (Token-2022), stream claims use standard SPL
|
|
8
|
+
* token program for vLOFI minting.
|
|
9
|
+
*/
|
|
10
|
+
import { PublicKey, TransactionInstruction } from '@solana/web3.js';
|
|
11
|
+
/**
|
|
12
|
+
* Build a `publish_stream_root` TransactionInstruction.
|
|
13
|
+
*
|
|
14
|
+
* Publishes a new merkle root for vLOFI stream distribution.
|
|
15
|
+
*
|
|
16
|
+
* Accounts (order must match PublishStreamRoot struct):
|
|
17
|
+
* 0. payer (signer, writable) - admin or publisher
|
|
18
|
+
* 1. protocol_state (readonly)
|
|
19
|
+
* 2. stream_root_config (writable) - PDA: seeds=[b"stream_root", vlofiMint]
|
|
20
|
+
* 3. vlofi_mint (readonly)
|
|
21
|
+
* 4. system_program (readonly)
|
|
22
|
+
*
|
|
23
|
+
* Data layout: [8 disc][8 root_seq LE][32 root][32 dataset_hash] = 80 bytes
|
|
24
|
+
*
|
|
25
|
+
* @param payer - Admin or authorized publisher (signer)
|
|
26
|
+
* @param vlofiMint - vLOFI mint address
|
|
27
|
+
* @param rootSeq - Monotonically increasing root sequence number
|
|
28
|
+
* @param root - 32-byte merkle root hash
|
|
29
|
+
* @param datasetHash - 32-byte dataset hash for auditability
|
|
30
|
+
* @param programId - Program ID (defaults to mainnet)
|
|
31
|
+
*/
|
|
32
|
+
export declare function createPublishStreamRootIx(payer: PublicKey, vlofiMint: PublicKey, rootSeq: number | bigint, root: Uint8Array | Buffer, datasetHash: Uint8Array | Buffer, programId?: PublicKey): TransactionInstruction;
|
|
33
|
+
/**
|
|
34
|
+
* Build a `claim_stream` TransactionInstruction (self-signed).
|
|
35
|
+
*
|
|
36
|
+
* Claims vLOFI tokens from the stream distribution merkle tree.
|
|
37
|
+
* The claimer signs the transaction themselves.
|
|
38
|
+
*
|
|
39
|
+
* Accounts (order must match ClaimStream struct):
|
|
40
|
+
* 0. claimer (signer, writable)
|
|
41
|
+
* 1. protocol_state (readonly)
|
|
42
|
+
* 2. stream_root_config (readonly)
|
|
43
|
+
* 3. claim_state_stream (writable) - PDA: seeds=[b"claim_stream", vlofiMint, claimer]
|
|
44
|
+
* 4. vlofi_mint (writable) - for mint_to
|
|
45
|
+
* 5. claimer_vlofi_ata (writable) - ATA(claimer, vlofiMint, TOKEN_PROGRAM)
|
|
46
|
+
* 6. token_program (readonly) - standard SPL Token
|
|
47
|
+
* 7. system_program (readonly)
|
|
48
|
+
*
|
|
49
|
+
* Data layout: [8 disc][8 root_seq LE][8 cumulative_total LE][4 proof_len LE][proof_len * 32 proof_nodes]
|
|
50
|
+
*
|
|
51
|
+
* @param claimer - Wallet claiming vLOFI (signer)
|
|
52
|
+
* @param vlofiMint - vLOFI mint address
|
|
53
|
+
* @param rootSeq - Root sequence number to claim against
|
|
54
|
+
* @param cumulativeTotal - Cumulative vLOFI amount entitled (native units)
|
|
55
|
+
* @param proofHex - Hex-encoded 32-byte merkle proof sibling hashes
|
|
56
|
+
* @param programId - Program ID (defaults to mainnet)
|
|
57
|
+
* @returns Array of instructions: idempotent ATA create + claim IX
|
|
58
|
+
*/
|
|
59
|
+
export declare function createClaimStreamIx(claimer: PublicKey, vlofiMint: PublicKey, rootSeq: number | bigint, cumulativeTotal: bigint | number, proofHex: string[], programId?: PublicKey): TransactionInstruction[];
|
|
60
|
+
/**
|
|
61
|
+
* Build a `claim_stream_sponsored` TransactionInstruction (gasless relay).
|
|
62
|
+
*
|
|
63
|
+
* A relayer pays for the transaction while vLOFI goes to the claimer.
|
|
64
|
+
* The claimer does NOT need to sign.
|
|
65
|
+
*
|
|
66
|
+
* Accounts (order must match ClaimStreamSponsored struct):
|
|
67
|
+
* 0. payer (signer, writable) - relayer
|
|
68
|
+
* 1. claimer (readonly, NOT signer)
|
|
69
|
+
* 2. protocol_state (readonly)
|
|
70
|
+
* 3. stream_root_config (readonly)
|
|
71
|
+
* 4. claim_state_stream (writable)
|
|
72
|
+
* 5. vlofi_mint (writable)
|
|
73
|
+
* 6. claimer_vlofi_ata (writable)
|
|
74
|
+
* 7. token_program (readonly)
|
|
75
|
+
* 8. system_program (readonly)
|
|
76
|
+
*
|
|
77
|
+
* Data layout: same as claim_stream
|
|
78
|
+
*
|
|
79
|
+
* @param payer - Relayer wallet (signer, pays fees)
|
|
80
|
+
* @param claimer - Wallet receiving vLOFI (NOT a signer)
|
|
81
|
+
* @param vlofiMint - vLOFI mint address
|
|
82
|
+
* @param rootSeq - Root sequence number to claim against
|
|
83
|
+
* @param cumulativeTotal - Cumulative vLOFI amount entitled (native units)
|
|
84
|
+
* @param proofHex - Hex-encoded 32-byte merkle proof sibling hashes
|
|
85
|
+
* @param programId - Program ID (defaults to mainnet)
|
|
86
|
+
* @returns Array of instructions: idempotent ATA create + sponsored claim IX
|
|
87
|
+
*/
|
|
88
|
+
export declare function createClaimStreamSponsoredIx(payer: PublicKey, claimer: PublicKey, vlofiMint: PublicKey, rootSeq: number | bigint, cumulativeTotal: bigint | number, proofHex: string[], programId?: PublicKey): TransactionInstruction[];
|