@wzrd_sol/sdk 0.1.2 → 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/dist/accounts.d.ts +11 -0
- package/dist/accounts.js +40 -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 +13 -6
- package/dist/index.js +12 -5
- package/dist/instructions.d.ts +69 -0
- package/dist/instructions.js +200 -2
- package/dist/instructions.test.js +2 -2
- package/dist/model-selector.d.ts +141 -0
- package/dist/model-selector.js +247 -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
package/dist/instructions.js
CHANGED
|
@@ -221,8 +221,10 @@ export async function createSettleMarketIx(connection, user, marketId, programId
|
|
|
221
221
|
const protocolState = getProtocolStatePDA(programId);
|
|
222
222
|
const marketVault = getMarketVaultPDA(protocolState, marketId, programId);
|
|
223
223
|
const userPosition = getUserPositionPDA(marketVault, user, programId);
|
|
224
|
-
|
|
225
|
-
|
|
224
|
+
// 'auto' resolves based on program ID: mainnet uses legacy_ccm layout
|
|
225
|
+
const accountsMode = options.accountsMode === 'auto' || !options.accountsMode
|
|
226
|
+
? (programId.equals(PROGRAM_ID) ? 'legacy_ccm' : 'current')
|
|
227
|
+
: options.accountsMode;
|
|
226
228
|
// Fetch vault + protocol state to discover mints/accounts.
|
|
227
229
|
const vaultInfo = await connection.getAccountInfo(marketVault);
|
|
228
230
|
if (!vaultInfo)
|
|
@@ -523,6 +525,31 @@ function getMarketStatePDA(ccmMint, marketId, programId = PROGRAM_ID) {
|
|
|
523
525
|
idBuf.writeBigUInt64LE(BigInt(marketId));
|
|
524
526
|
return PublicKey.findProgramAddressSync([MARKET_STATE_SEED, ccmMint.toBuffer(), idBuf], programId)[0];
|
|
525
527
|
}
|
|
528
|
+
// ── Prediction market PDA helpers ────────────────────────────────────
|
|
529
|
+
const PM_VAULT_SEED = Buffer.from('market_vault');
|
|
530
|
+
const MARKET_YES_MINT_SEED = Buffer.from('market_yes');
|
|
531
|
+
const MARKET_NO_MINT_SEED = Buffer.from('market_no');
|
|
532
|
+
const MARKET_MINT_AUTH_SEED = Buffer.from('market_auth');
|
|
533
|
+
function getPredictionVaultPDA(ccmMint, marketId, programId = PROGRAM_ID) {
|
|
534
|
+
const idBuf = Buffer.alloc(8);
|
|
535
|
+
idBuf.writeBigUInt64LE(BigInt(marketId));
|
|
536
|
+
return PublicKey.findProgramAddressSync([PM_VAULT_SEED, ccmMint.toBuffer(), idBuf], programId)[0];
|
|
537
|
+
}
|
|
538
|
+
function getPredictionYesMintPDA(ccmMint, marketId, programId = PROGRAM_ID) {
|
|
539
|
+
const idBuf = Buffer.alloc(8);
|
|
540
|
+
idBuf.writeBigUInt64LE(BigInt(marketId));
|
|
541
|
+
return PublicKey.findProgramAddressSync([MARKET_YES_MINT_SEED, ccmMint.toBuffer(), idBuf], programId)[0];
|
|
542
|
+
}
|
|
543
|
+
function getPredictionNoMintPDA(ccmMint, marketId, programId = PROGRAM_ID) {
|
|
544
|
+
const idBuf = Buffer.alloc(8);
|
|
545
|
+
idBuf.writeBigUInt64LE(BigInt(marketId));
|
|
546
|
+
return PublicKey.findProgramAddressSync([MARKET_NO_MINT_SEED, ccmMint.toBuffer(), idBuf], programId)[0];
|
|
547
|
+
}
|
|
548
|
+
function getPredictionMintAuthorityPDA(ccmMint, marketId, programId = PROGRAM_ID) {
|
|
549
|
+
const idBuf = Buffer.alloc(8);
|
|
550
|
+
idBuf.writeBigUInt64LE(BigInt(marketId));
|
|
551
|
+
return PublicKey.findProgramAddressSync([MARKET_MINT_AUTH_SEED, ccmMint.toBuffer(), idBuf], programId)[0];
|
|
552
|
+
}
|
|
526
553
|
function getLegacyProtocolStatePDA(ccmMint, programId = PROGRAM_ID) {
|
|
527
554
|
return PublicKey.findProgramAddressSync([PROTOCOL_SEED, ccmMint.toBuffer()], programId)[0];
|
|
528
555
|
}
|
|
@@ -1574,3 +1601,174 @@ label, newUpdater, programId = PROGRAM_ID) {
|
|
|
1574
1601
|
data,
|
|
1575
1602
|
});
|
|
1576
1603
|
}
|
|
1604
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
1605
|
+
// PREDICTION MARKET TRADING INSTRUCTIONS
|
|
1606
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
1607
|
+
/** Standard SPL Token program (used for YES/NO outcome tokens). */
|
|
1608
|
+
const SPL_TOKEN_PROGRAM_ID = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA');
|
|
1609
|
+
/**
|
|
1610
|
+
* Build a `mint_shares` TransactionInstruction (prediction markets).
|
|
1611
|
+
*
|
|
1612
|
+
* Deposits CCM into the prediction market vault and receives 1:1 YES + NO tokens.
|
|
1613
|
+
*
|
|
1614
|
+
* Accounts (13 fixed, order matches markets.rs mint_shares):
|
|
1615
|
+
* 0. depositor (signer, writable)
|
|
1616
|
+
* 1. protocol_state (readonly)
|
|
1617
|
+
* 2. market_state (writable)
|
|
1618
|
+
* 3. ccm_mint (readonly)
|
|
1619
|
+
* 4. depositor_ccm_ata (writable)
|
|
1620
|
+
* 5. vault (writable)
|
|
1621
|
+
* 6. yes_mint (writable)
|
|
1622
|
+
* 7. no_mint (writable)
|
|
1623
|
+
* 8. depositor_yes_ata (writable)
|
|
1624
|
+
* 9. depositor_no_ata (writable)
|
|
1625
|
+
* 10. mint_authority (readonly)
|
|
1626
|
+
* 11. token_program (Token-2022, for CCM)
|
|
1627
|
+
* 12. outcome_token_prog (SPL Token, for YES/NO)
|
|
1628
|
+
*
|
|
1629
|
+
* Args: amount (u64) — CCM native units to deposit
|
|
1630
|
+
*/
|
|
1631
|
+
export async function createMintSharesIx(depositor, ccmMint, marketId, amount, programId = PROGRAM_ID) {
|
|
1632
|
+
const protocolState = getProtocolStatePDA(programId);
|
|
1633
|
+
const marketState = getMarketStatePDA(ccmMint, marketId, programId);
|
|
1634
|
+
const vault = getPredictionVaultPDA(ccmMint, marketId, programId);
|
|
1635
|
+
const yesMint = getPredictionYesMintPDA(ccmMint, marketId, programId);
|
|
1636
|
+
const noMint = getPredictionNoMintPDA(ccmMint, marketId, programId);
|
|
1637
|
+
const mintAuthority = getPredictionMintAuthorityPDA(ccmMint, marketId, programId);
|
|
1638
|
+
const depositorCcm = getAta(depositor, ccmMint, TOKEN_2022_PROGRAM_ID);
|
|
1639
|
+
const depositorYes = getAta(depositor, yesMint, SPL_TOKEN_PROGRAM_ID);
|
|
1640
|
+
const depositorNo = getAta(depositor, noMint, SPL_TOKEN_PROGRAM_ID);
|
|
1641
|
+
const disc = await anchorDisc('mint_shares');
|
|
1642
|
+
const data = Buffer.alloc(16);
|
|
1643
|
+
disc.copy(data, 0);
|
|
1644
|
+
data.writeBigUInt64LE(BigInt(amount), 8);
|
|
1645
|
+
return new TransactionInstruction({
|
|
1646
|
+
programId,
|
|
1647
|
+
keys: [
|
|
1648
|
+
{ pubkey: depositor, isSigner: true, isWritable: true },
|
|
1649
|
+
{ pubkey: protocolState, isSigner: false, isWritable: false },
|
|
1650
|
+
{ pubkey: marketState, isSigner: false, isWritable: true },
|
|
1651
|
+
{ pubkey: ccmMint, isSigner: false, isWritable: false },
|
|
1652
|
+
{ pubkey: depositorCcm, isSigner: false, isWritable: true },
|
|
1653
|
+
{ pubkey: vault, isSigner: false, isWritable: true },
|
|
1654
|
+
{ pubkey: yesMint, isSigner: false, isWritable: true },
|
|
1655
|
+
{ pubkey: noMint, isSigner: false, isWritable: true },
|
|
1656
|
+
{ pubkey: depositorYes, isSigner: false, isWritable: true },
|
|
1657
|
+
{ pubkey: depositorNo, isSigner: false, isWritable: true },
|
|
1658
|
+
{ pubkey: mintAuthority, isSigner: false, isWritable: false },
|
|
1659
|
+
{ pubkey: TOKEN_2022_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
1660
|
+
{ pubkey: SPL_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
1661
|
+
],
|
|
1662
|
+
data,
|
|
1663
|
+
});
|
|
1664
|
+
}
|
|
1665
|
+
/**
|
|
1666
|
+
* Build a `redeem_shares` TransactionInstruction (prediction markets).
|
|
1667
|
+
*
|
|
1668
|
+
* Burns equal YES + NO shares and returns CCM. Pre-resolution only.
|
|
1669
|
+
*
|
|
1670
|
+
* Accounts (13 fixed, order matches markets.rs redeem_shares):
|
|
1671
|
+
* 0. redeemer (signer, writable)
|
|
1672
|
+
* 1. protocol_state (readonly)
|
|
1673
|
+
* 2. market_state (writable)
|
|
1674
|
+
* 3. ccm_mint (readonly)
|
|
1675
|
+
* 4. vault (writable)
|
|
1676
|
+
* 5. yes_mint (writable)
|
|
1677
|
+
* 6. no_mint (writable)
|
|
1678
|
+
* 7. redeemer_yes_ata (writable)
|
|
1679
|
+
* 8. redeemer_no_ata (writable)
|
|
1680
|
+
* 9. redeemer_ccm_ata (writable)
|
|
1681
|
+
* 10. mint_authority (readonly)
|
|
1682
|
+
* 11. token_program (Token-2022, for CCM)
|
|
1683
|
+
* 12. outcome_token_prog (SPL Token, for YES/NO)
|
|
1684
|
+
*
|
|
1685
|
+
* Args: shares (u64) — number of YES+NO pairs to redeem
|
|
1686
|
+
*/
|
|
1687
|
+
export async function createRedeemSharesIx(redeemer, ccmMint, marketId, shares, programId = PROGRAM_ID) {
|
|
1688
|
+
const protocolState = getProtocolStatePDA(programId);
|
|
1689
|
+
const marketState = getMarketStatePDA(ccmMint, marketId, programId);
|
|
1690
|
+
const vault = getPredictionVaultPDA(ccmMint, marketId, programId);
|
|
1691
|
+
const yesMint = getPredictionYesMintPDA(ccmMint, marketId, programId);
|
|
1692
|
+
const noMint = getPredictionNoMintPDA(ccmMint, marketId, programId);
|
|
1693
|
+
const mintAuthority = getPredictionMintAuthorityPDA(ccmMint, marketId, programId);
|
|
1694
|
+
const redeemerYes = getAta(redeemer, yesMint, SPL_TOKEN_PROGRAM_ID);
|
|
1695
|
+
const redeemerNo = getAta(redeemer, noMint, SPL_TOKEN_PROGRAM_ID);
|
|
1696
|
+
const redeemerCcm = getAta(redeemer, ccmMint, TOKEN_2022_PROGRAM_ID);
|
|
1697
|
+
const disc = await anchorDisc('redeem_shares');
|
|
1698
|
+
const data = Buffer.alloc(16);
|
|
1699
|
+
disc.copy(data, 0);
|
|
1700
|
+
data.writeBigUInt64LE(BigInt(shares), 8);
|
|
1701
|
+
return new TransactionInstruction({
|
|
1702
|
+
programId,
|
|
1703
|
+
keys: [
|
|
1704
|
+
{ pubkey: redeemer, isSigner: true, isWritable: true },
|
|
1705
|
+
{ pubkey: protocolState, isSigner: false, isWritable: false },
|
|
1706
|
+
{ pubkey: marketState, isSigner: false, isWritable: true },
|
|
1707
|
+
{ pubkey: ccmMint, isSigner: false, isWritable: false },
|
|
1708
|
+
{ pubkey: vault, isSigner: false, isWritable: true },
|
|
1709
|
+
{ pubkey: yesMint, isSigner: false, isWritable: true },
|
|
1710
|
+
{ pubkey: noMint, isSigner: false, isWritable: true },
|
|
1711
|
+
{ pubkey: redeemerYes, isSigner: false, isWritable: true },
|
|
1712
|
+
{ pubkey: redeemerNo, isSigner: false, isWritable: true },
|
|
1713
|
+
{ pubkey: redeemerCcm, isSigner: false, isWritable: true },
|
|
1714
|
+
{ pubkey: mintAuthority, isSigner: false, isWritable: false },
|
|
1715
|
+
{ pubkey: TOKEN_2022_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
1716
|
+
{ pubkey: SPL_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
1717
|
+
],
|
|
1718
|
+
data,
|
|
1719
|
+
});
|
|
1720
|
+
}
|
|
1721
|
+
/**
|
|
1722
|
+
* Build a `settle` TransactionInstruction (prediction markets).
|
|
1723
|
+
*
|
|
1724
|
+
* Burns winning-side tokens and returns CCM 1:1. Post-resolution only.
|
|
1725
|
+
*
|
|
1726
|
+
* Accounts (11 fixed, order matches markets.rs settle):
|
|
1727
|
+
* 0. settler (signer, writable)
|
|
1728
|
+
* 1. protocol_state (readonly)
|
|
1729
|
+
* 2. market_state (readonly)
|
|
1730
|
+
* 3. ccm_mint (readonly)
|
|
1731
|
+
* 4. vault (writable)
|
|
1732
|
+
* 5. winning_mint (writable) — yes_mint if YES won, no_mint if NO
|
|
1733
|
+
* 6. settler_winning (writable) — settler's ATA for the winning mint
|
|
1734
|
+
* 7. settler_ccm (writable) — settler's CCM ATA
|
|
1735
|
+
* 8. mint_authority (readonly)
|
|
1736
|
+
* 9. token_program (Token-2022, for CCM)
|
|
1737
|
+
* 10. outcome_token_prog (SPL Token, for YES/NO)
|
|
1738
|
+
*
|
|
1739
|
+
* Args: shares (u64) — winning tokens to settle
|
|
1740
|
+
*/
|
|
1741
|
+
export async function createSettlePredictionIx(settler, ccmMint, marketId, shares,
|
|
1742
|
+
/** true = YES won, false = NO won */
|
|
1743
|
+
yesOutcome, programId = PROGRAM_ID) {
|
|
1744
|
+
const protocolState = getProtocolStatePDA(programId);
|
|
1745
|
+
const marketState = getMarketStatePDA(ccmMint, marketId, programId);
|
|
1746
|
+
const vault = getPredictionVaultPDA(ccmMint, marketId, programId);
|
|
1747
|
+
const yesMint = getPredictionYesMintPDA(ccmMint, marketId, programId);
|
|
1748
|
+
const noMint = getPredictionNoMintPDA(ccmMint, marketId, programId);
|
|
1749
|
+
const mintAuthority = getPredictionMintAuthorityPDA(ccmMint, marketId, programId);
|
|
1750
|
+
const winningMint = yesOutcome ? yesMint : noMint;
|
|
1751
|
+
const settlerWinning = getAta(settler, winningMint, SPL_TOKEN_PROGRAM_ID);
|
|
1752
|
+
const settlerCcm = getAta(settler, ccmMint, TOKEN_2022_PROGRAM_ID);
|
|
1753
|
+
const disc = await anchorDisc('settle');
|
|
1754
|
+
const data = Buffer.alloc(16);
|
|
1755
|
+
disc.copy(data, 0);
|
|
1756
|
+
data.writeBigUInt64LE(BigInt(shares), 8);
|
|
1757
|
+
return new TransactionInstruction({
|
|
1758
|
+
programId,
|
|
1759
|
+
keys: [
|
|
1760
|
+
{ pubkey: settler, isSigner: true, isWritable: true },
|
|
1761
|
+
{ pubkey: protocolState, isSigner: false, isWritable: false },
|
|
1762
|
+
{ pubkey: marketState, isSigner: false, isWritable: false },
|
|
1763
|
+
{ pubkey: ccmMint, isSigner: false, isWritable: false },
|
|
1764
|
+
{ pubkey: vault, isSigner: false, isWritable: true },
|
|
1765
|
+
{ pubkey: winningMint, isSigner: false, isWritable: true },
|
|
1766
|
+
{ pubkey: settlerWinning, isSigner: false, isWritable: true },
|
|
1767
|
+
{ pubkey: settlerCcm, isSigner: false, isWritable: true },
|
|
1768
|
+
{ pubkey: mintAuthority, isSigner: false, isWritable: false },
|
|
1769
|
+
{ pubkey: TOKEN_2022_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
1770
|
+
{ pubkey: SPL_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
1771
|
+
],
|
|
1772
|
+
data,
|
|
1773
|
+
});
|
|
1774
|
+
}
|
|
@@ -302,14 +302,14 @@ describe('parseUserMarketPosition', () => {
|
|
|
302
302
|
expect(parseUserMarketPosition(buf)).toBeNull();
|
|
303
303
|
});
|
|
304
304
|
it('reads settled flag correctly', () => {
|
|
305
|
-
const buf = Buffer.alloc(
|
|
305
|
+
const buf = Buffer.alloc(114);
|
|
306
306
|
buf[8 + 89] = 1; // settled
|
|
307
307
|
const pos = parseUserMarketPosition(buf);
|
|
308
308
|
expect(pos).not.toBeNull();
|
|
309
309
|
expect(pos.settled).toBe(true);
|
|
310
310
|
});
|
|
311
311
|
it('reads depositedAmount as u64 LE', () => {
|
|
312
|
-
const buf = Buffer.alloc(
|
|
312
|
+
const buf = Buffer.alloc(114);
|
|
313
313
|
buf.writeBigUInt64LE(1000000n, 8 + 1 + 32 + 32); // depositedAmount at offset 73 from disc
|
|
314
314
|
const pos = parseUserMarketPosition(buf);
|
|
315
315
|
expect(pos).not.toBeNull();
|
|
@@ -0,0 +1,141 @@
|
|
|
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
|
+
/** Budget tiers controlling the maximum blended price per million tokens. */
|
|
16
|
+
export type BudgetTier = 'micro' | 'budget' | 'mid' | 'premium';
|
|
17
|
+
/** Supported task types. The selector applies a task-specific boost. */
|
|
18
|
+
export type TaskType = 'chat' | 'code' | 'reasoning';
|
|
19
|
+
/** Velocity trend labels returned by the WZRD momentum API. */
|
|
20
|
+
export type VelocityTrend = 'surging' | 'accelerating' | 'stable' | 'insufficient_history' | 'decelerating' | 'cooling';
|
|
21
|
+
/** Confidence level from the WZRD momentum API. */
|
|
22
|
+
export type Confidence = 'high' | 'medium' | 'low' | 'insufficient';
|
|
23
|
+
/** A single model recommendation returned by the selector. */
|
|
24
|
+
export interface ModelRecommendation {
|
|
25
|
+
/** Full model identifier (e.g. "google/gemma-3-27b-it"). */
|
|
26
|
+
model_id: string;
|
|
27
|
+
/** Routing provider — currently always "openrouter". */
|
|
28
|
+
provider: string;
|
|
29
|
+
/** Blended price per million tokens in USD (3:1 prompt:completion weighting). */
|
|
30
|
+
price_per_m_tokens: number;
|
|
31
|
+
/** Exponential moving average of the model's velocity from WZRD. */
|
|
32
|
+
velocity_ema: number;
|
|
33
|
+
/** Composite value score: velocity_ema / (blended_price + epsilon). Higher is better. */
|
|
34
|
+
value_score: number;
|
|
35
|
+
/** Current velocity trend from the WZRD momentum signal. */
|
|
36
|
+
trend: VelocityTrend;
|
|
37
|
+
/** Confidence level of the momentum signal. */
|
|
38
|
+
confidence: Confidence;
|
|
39
|
+
}
|
|
40
|
+
/** Options for filtering and ranking models. */
|
|
41
|
+
export interface ModelSelectorOptions {
|
|
42
|
+
/** Maximum price tier. Default: "mid". */
|
|
43
|
+
budget?: BudgetTier;
|
|
44
|
+
/** Task type — applies a relevance boost to known-good models. Default: "chat". */
|
|
45
|
+
task?: TaskType;
|
|
46
|
+
/** Minimum confidence level from the momentum signal. Default: "low". */
|
|
47
|
+
min_confidence?: Confidence;
|
|
48
|
+
/** Maximum number of results to return. Default: 5. */
|
|
49
|
+
limit?: number;
|
|
50
|
+
/** Model ID substrings to exclude (case-insensitive). */
|
|
51
|
+
exclude?: string[];
|
|
52
|
+
}
|
|
53
|
+
/** Configuration for the ModelSelector class. */
|
|
54
|
+
export interface ModelSelectorConfig {
|
|
55
|
+
/** WZRD API base URL. Default: "https://api.twzrd.xyz". */
|
|
56
|
+
wzrd_base_url?: string;
|
|
57
|
+
/** OpenRouter catalog URL. Default: "https://openrouter.ai/api/v1/models". */
|
|
58
|
+
openrouter_url?: string;
|
|
59
|
+
/** Cache TTL in milliseconds. Default: 300_000 (5 minutes). */
|
|
60
|
+
cache_ttl_ms?: number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Fetches WZRD velocity data and OpenRouter pricing, caches results,
|
|
64
|
+
* and scores models by value (velocity / cost).
|
|
65
|
+
*
|
|
66
|
+
* Create one instance and reuse it — the internal cache avoids redundant
|
|
67
|
+
* HTTP requests within the TTL window.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* const selector = new ModelSelector({ wzrd_base_url: 'https://api.twzrd.xyz' });
|
|
72
|
+
* const picks = await selector.select({ task: 'code', budget: 'budget', limit: 3 });
|
|
73
|
+
* console.log(picks[0].model_id);
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export declare class ModelSelector {
|
|
77
|
+
private readonly wzrdBaseUrl;
|
|
78
|
+
private readonly openrouterUrl;
|
|
79
|
+
private readonly cacheTtlMs;
|
|
80
|
+
private cache;
|
|
81
|
+
constructor(config?: ModelSelectorConfig);
|
|
82
|
+
/**
|
|
83
|
+
* Select the best models for a given task and budget.
|
|
84
|
+
*
|
|
85
|
+
* Fetches live data from the WZRD leaderboard, momentum signal, and
|
|
86
|
+
* OpenRouter catalog (all cached for 5 minutes by default), then ranks
|
|
87
|
+
* every OpenRouter model tracked by WZRD using:
|
|
88
|
+
*
|
|
89
|
+
* value_score = velocity_ema / (blended_price_per_m_tokens + epsilon)
|
|
90
|
+
*
|
|
91
|
+
* Results are filtered by budget tier, task relevance, minimum confidence,
|
|
92
|
+
* and exclusion list, then sorted by value_score descending.
|
|
93
|
+
*
|
|
94
|
+
* @param options - Filtering and ranking options.
|
|
95
|
+
* @returns Ranked array of model recommendations (may be empty).
|
|
96
|
+
*/
|
|
97
|
+
select(options?: ModelSelectorOptions): Promise<ModelRecommendation[]>;
|
|
98
|
+
/**
|
|
99
|
+
* Invalidate the in-memory cache.
|
|
100
|
+
*
|
|
101
|
+
* Useful when you know upstream data has changed and want
|
|
102
|
+
* fresh results on the next `select()` call.
|
|
103
|
+
*/
|
|
104
|
+
clearCache(): void;
|
|
105
|
+
private fetchLeaderboard;
|
|
106
|
+
private fetchMomentum;
|
|
107
|
+
private fetchOpenRouterCatalog;
|
|
108
|
+
/**
|
|
109
|
+
* Fetch JSON from a URL with in-memory TTL caching.
|
|
110
|
+
* Returns null on any error (network, HTTP status, parse failure).
|
|
111
|
+
*/
|
|
112
|
+
private cachedFetch;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Pick the best open-source model for a task using live WZRD velocity data.
|
|
116
|
+
*
|
|
117
|
+
* This is a convenience wrapper around {@link ModelSelector.select} that
|
|
118
|
+
* uses a module-level singleton with default configuration. For custom
|
|
119
|
+
* base URLs or cache settings, instantiate {@link ModelSelector} directly.
|
|
120
|
+
*
|
|
121
|
+
* @param options - Filtering and ranking options.
|
|
122
|
+
* @returns Ranked array of model recommendations (may be empty if the API is unreachable).
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```ts
|
|
126
|
+
* import { bestModel } from '@wzrd_sol/sdk';
|
|
127
|
+
*
|
|
128
|
+
* // Cheapest model good for code
|
|
129
|
+
* const picks = await bestModel({ task: 'code', budget: 'micro' });
|
|
130
|
+
* console.log(picks[0].model_id);
|
|
131
|
+
*
|
|
132
|
+
* // Premium reasoning model, exclude specific providers
|
|
133
|
+
* const reasoning = await bestModel({
|
|
134
|
+
* task: 'reasoning',
|
|
135
|
+
* budget: 'premium',
|
|
136
|
+
* exclude: ['gpt'],
|
|
137
|
+
* limit: 3,
|
|
138
|
+
* });
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
export declare function bestModel(options?: ModelSelectorOptions): Promise<ModelRecommendation[]>;
|
|
@@ -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/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];
|