@solana-compass/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/README.md +327 -0
  2. package/bin/sol.mjs +6 -0
  3. package/dist/commands/config.d.ts +2 -0
  4. package/dist/commands/config.js +73 -0
  5. package/dist/commands/config.js.map +1 -0
  6. package/dist/commands/lend.d.ts +2 -0
  7. package/dist/commands/lend.js +262 -0
  8. package/dist/commands/lend.js.map +1 -0
  9. package/dist/commands/lp.d.ts +2 -0
  10. package/dist/commands/lp.js +158 -0
  11. package/dist/commands/lp.js.map +1 -0
  12. package/dist/commands/network.d.ts +2 -0
  13. package/dist/commands/network.js +126 -0
  14. package/dist/commands/network.js.map +1 -0
  15. package/dist/commands/portfolio.d.ts +2 -0
  16. package/dist/commands/portfolio.js +187 -0
  17. package/dist/commands/portfolio.js.map +1 -0
  18. package/dist/commands/stake.d.ts +2 -0
  19. package/dist/commands/stake.js +143 -0
  20. package/dist/commands/stake.js.map +1 -0
  21. package/dist/commands/token.d.ts +2 -0
  22. package/dist/commands/token.js +551 -0
  23. package/dist/commands/token.js.map +1 -0
  24. package/dist/commands/tx.d.ts +2 -0
  25. package/dist/commands/tx.js +131 -0
  26. package/dist/commands/tx.js.map +1 -0
  27. package/dist/commands/wallet.d.ts +2 -0
  28. package/dist/commands/wallet.js +403 -0
  29. package/dist/commands/wallet.js.map +1 -0
  30. package/dist/core/config-manager.d.ts +31 -0
  31. package/dist/core/config-manager.js +79 -0
  32. package/dist/core/config-manager.js.map +1 -0
  33. package/dist/core/kamino-compat.d.ts +17 -0
  34. package/dist/core/kamino-compat.js +38 -0
  35. package/dist/core/kamino-compat.js.map +1 -0
  36. package/dist/core/lend-service.d.ts +41 -0
  37. package/dist/core/lend-service.js +331 -0
  38. package/dist/core/lend-service.js.map +1 -0
  39. package/dist/core/lp-service.d.ts +30 -0
  40. package/dist/core/lp-service.js +21 -0
  41. package/dist/core/lp-service.js.map +1 -0
  42. package/dist/core/onramp-service.d.ts +15 -0
  43. package/dist/core/onramp-service.js +57 -0
  44. package/dist/core/onramp-service.js.map +1 -0
  45. package/dist/core/portfolio-service.d.ts +55 -0
  46. package/dist/core/portfolio-service.js +272 -0
  47. package/dist/core/portfolio-service.js.map +1 -0
  48. package/dist/core/price-service.d.ts +8 -0
  49. package/dist/core/price-service.js +116 -0
  50. package/dist/core/price-service.js.map +1 -0
  51. package/dist/core/rpc.d.ts +5 -0
  52. package/dist/core/rpc.js +69 -0
  53. package/dist/core/rpc.js.map +1 -0
  54. package/dist/core/stake-service.d.ts +42 -0
  55. package/dist/core/stake-service.js +319 -0
  56. package/dist/core/stake-service.js.map +1 -0
  57. package/dist/core/swap-service.d.ts +31 -0
  58. package/dist/core/swap-service.js +142 -0
  59. package/dist/core/swap-service.js.map +1 -0
  60. package/dist/core/token-registry.d.ts +23 -0
  61. package/dist/core/token-registry.js +174 -0
  62. package/dist/core/token-registry.js.map +1 -0
  63. package/dist/core/token-service.d.ts +20 -0
  64. package/dist/core/token-service.js +92 -0
  65. package/dist/core/token-service.js.map +1 -0
  66. package/dist/core/transaction.d.ts +55 -0
  67. package/dist/core/transaction.js +196 -0
  68. package/dist/core/transaction.js.map +1 -0
  69. package/dist/core/wallet-manager.d.ts +20 -0
  70. package/dist/core/wallet-manager.js +142 -0
  71. package/dist/core/wallet-manager.js.map +1 -0
  72. package/dist/db/database.d.ts +3 -0
  73. package/dist/db/database.js +44 -0
  74. package/dist/db/database.js.map +1 -0
  75. package/dist/db/migrations/001_initial.d.ts +1 -0
  76. package/dist/db/migrations/001_initial.js +116 -0
  77. package/dist/db/migrations/001_initial.js.map +1 -0
  78. package/dist/db/migrations/002_tx_prices.d.ts +1 -0
  79. package/dist/db/migrations/002_tx_prices.js +5 -0
  80. package/dist/db/migrations/002_tx_prices.js.map +1 -0
  81. package/dist/db/repos/price-repo.d.ts +11 -0
  82. package/dist/db/repos/price-repo.js +14 -0
  83. package/dist/db/repos/price-repo.js.map +1 -0
  84. package/dist/db/repos/snapshot-repo.d.ts +27 -0
  85. package/dist/db/repos/snapshot-repo.js +32 -0
  86. package/dist/db/repos/snapshot-repo.js.map +1 -0
  87. package/dist/db/repos/token-repo.d.ts +17 -0
  88. package/dist/db/repos/token-repo.js +53 -0
  89. package/dist/db/repos/token-repo.js.map +1 -0
  90. package/dist/db/repos/transaction-repo.d.ts +22 -0
  91. package/dist/db/repos/transaction-repo.js +28 -0
  92. package/dist/db/repos/transaction-repo.js.map +1 -0
  93. package/dist/db/repos/wallet-repo.d.ts +18 -0
  94. package/dist/db/repos/wallet-repo.js +44 -0
  95. package/dist/db/repos/wallet-repo.js.map +1 -0
  96. package/dist/index.d.ts +1 -0
  97. package/dist/index.js +84 -0
  98. package/dist/index.js.map +1 -0
  99. package/dist/output/formatter.d.ts +27 -0
  100. package/dist/output/formatter.js +76 -0
  101. package/dist/output/formatter.js.map +1 -0
  102. package/dist/output/portfolio-renderer.d.ts +3 -0
  103. package/dist/output/portfolio-renderer.js +205 -0
  104. package/dist/output/portfolio-renderer.js.map +1 -0
  105. package/dist/output/table.d.ts +8 -0
  106. package/dist/output/table.js +22 -0
  107. package/dist/output/table.js.map +1 -0
  108. package/dist/utils/fs.d.ts +7 -0
  109. package/dist/utils/fs.js +39 -0
  110. package/dist/utils/fs.js.map +1 -0
  111. package/dist/utils/retry.d.ts +17 -0
  112. package/dist/utils/retry.js +55 -0
  113. package/dist/utils/retry.js.map +1 -0
  114. package/dist/utils/solana.d.ts +9 -0
  115. package/dist/utils/solana.js +26 -0
  116. package/dist/utils/solana.js.map +1 -0
  117. package/dist/utils/token-list.d.ts +9 -0
  118. package/dist/utils/token-list.js +31 -0
  119. package/dist/utils/token-list.js.map +1 -0
  120. package/package.json +44 -0
@@ -0,0 +1,57 @@
1
+ import { getConfigValue } from './config-manager.js';
2
+ // Transak — publishable API key, safe for OSS
3
+ const TRANSAK_DEFAULT_KEY = 'pk_live_placeholder'; // Replace with actual publishable key
4
+ class TransakProvider {
5
+ name = 'transak';
6
+ generateUrl(params) {
7
+ const apiKey = getConfigValue('onramp.transakApiKey') || TRANSAK_DEFAULT_KEY;
8
+ const base = 'https://global.transak.com/';
9
+ const urlParams = new URLSearchParams({
10
+ apiKey,
11
+ walletAddress: params.walletAddress,
12
+ cryptoCurrencyCode: 'SOL',
13
+ network: 'solana',
14
+ defaultFiatCurrency: params.currency || 'USD',
15
+ });
16
+ if (params.amount) {
17
+ urlParams.set('fiatAmount', String(params.amount));
18
+ }
19
+ return `${base}?${urlParams.toString()}`;
20
+ }
21
+ }
22
+ class SphereProvider {
23
+ name = 'sphere';
24
+ generateUrl(params) {
25
+ const key = getConfigValue('onramp.sphereKey');
26
+ if (!key) {
27
+ throw new Error('Sphere API key not configured. Set it with: sol config set onramp.sphereKey sk_...');
28
+ }
29
+ // Sphere uses a different URL structure
30
+ const base = 'https://sphere.money/pay';
31
+ const urlParams = new URLSearchParams({
32
+ walletAddress: params.walletAddress,
33
+ chain: 'solana',
34
+ currency: params.currency || 'USD',
35
+ });
36
+ if (params.amount) {
37
+ urlParams.set('amount', String(params.amount));
38
+ }
39
+ return `${base}?${urlParams.toString()}`;
40
+ }
41
+ }
42
+ const providers = new Map([
43
+ ['transak', new TransakProvider()],
44
+ ['sphere', new SphereProvider()],
45
+ ]);
46
+ export function getOnrampUrl(params) {
47
+ const providerName = params.provider || getConfigValue('onramp.provider') || 'transak';
48
+ const provider = providers.get(providerName);
49
+ if (!provider) {
50
+ throw new Error(`Unknown onramp provider: ${providerName}. Available: ${[...providers.keys()].join(', ')}`);
51
+ }
52
+ return provider.generateUrl(params);
53
+ }
54
+ export function listProviders() {
55
+ return [...providers.keys()];
56
+ }
57
+ //# sourceMappingURL=onramp-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"onramp-service.js","sourceRoot":"","sources":["../../src/core/onramp-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAOrD,8CAA8C;AAC9C,MAAM,mBAAmB,GAAG,qBAAqB,CAAC,CAAC,sCAAsC;AAEzF,MAAM,eAAe;IACnB,IAAI,GAAG,SAAS,CAAC;IAEjB,WAAW,CAAC,MAAqE;QAC/E,MAAM,MAAM,GAAI,cAAc,CAAC,sBAAsB,CAAY,IAAI,mBAAmB,CAAC;QACzF,MAAM,IAAI,GAAG,6BAA6B,CAAC;QAC3C,MAAM,SAAS,GAAG,IAAI,eAAe,CAAC;YACpC,MAAM;YACN,aAAa,EAAE,MAAM,CAAC,aAAa;YACnC,kBAAkB,EAAE,KAAK;YACzB,OAAO,EAAE,QAAQ;YACjB,mBAAmB,EAAE,MAAM,CAAC,QAAQ,IAAI,KAAK;SAC9C,CAAC,CAAC;QAEH,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAClB,SAAS,CAAC,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QACrD,CAAC;QAED,OAAO,GAAG,IAAI,IAAI,SAAS,CAAC,QAAQ,EAAE,EAAE,CAAC;IAC3C,CAAC;CACF;AAED,MAAM,cAAc;IAClB,IAAI,GAAG,QAAQ,CAAC;IAEhB,WAAW,CAAC,MAAqE;QAC/E,MAAM,GAAG,GAAG,cAAc,CAAC,kBAAkB,CAAW,CAAC;QACzD,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,KAAK,CAAC,oFAAoF,CAAC,CAAC;QACxG,CAAC;QAED,wCAAwC;QACxC,MAAM,IAAI,GAAG,0BAA0B,CAAC;QACxC,MAAM,SAAS,GAAG,IAAI,eAAe,CAAC;YACpC,aAAa,EAAE,MAAM,CAAC,aAAa;YACnC,KAAK,EAAE,QAAQ;YACf,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,KAAK;SACnC,CAAC,CAAC;QAEH,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAClB,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QACjD,CAAC;QAED,OAAO,GAAG,IAAI,IAAI,SAAS,CAAC,QAAQ,EAAE,EAAE,CAAC;IAC3C,CAAC;CACF;AAED,MAAM,SAAS,GAAgC,IAAI,GAAG,CAAC;IACrD,CAAC,SAAS,EAAE,IAAI,eAAe,EAAE,CAAC;IAClC,CAAC,QAAQ,EAAE,IAAI,cAAc,EAAE,CAAC;CACjC,CAAC,CAAC;AAEH,MAAM,UAAU,YAAY,CAAC,MAK5B;IACC,MAAM,YAAY,GAAG,MAAM,CAAC,QAAQ,IAAK,cAAc,CAAC,iBAAiB,CAAY,IAAI,SAAS,CAAC;IACnG,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAC7C,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,4BAA4B,YAAY,gBAAgB,CAAC,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC9G,CAAC;IACD,OAAO,QAAQ,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO,CAAC,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,55 @@
1
+ export interface PortfolioPosition {
2
+ type: 'token' | 'stake' | 'lend' | 'lp';
3
+ protocol?: string;
4
+ wallet: string;
5
+ mint: string;
6
+ symbol: string;
7
+ amount: number;
8
+ valueUsd: number | null;
9
+ extra?: Record<string, unknown>;
10
+ }
11
+ export interface AllocationEntry {
12
+ symbol: string;
13
+ pct: number;
14
+ valueUsd: number;
15
+ }
16
+ export interface PortfolioReport {
17
+ wallets: string[];
18
+ positions: PortfolioPosition[];
19
+ allocation: AllocationEntry[];
20
+ totalValueUsd: number;
21
+ claimableMev: number;
22
+ lastSnapshot?: {
23
+ id: number;
24
+ label?: string;
25
+ ago: string;
26
+ };
27
+ }
28
+ export interface CompareResult {
29
+ snapshotId: number | 'current';
30
+ snapshotDate?: string;
31
+ diffs: CompareEntry[];
32
+ totalBefore: number;
33
+ totalAfter: number;
34
+ totalChange: number;
35
+ totalChangePct: number | null;
36
+ }
37
+ export interface CompareEntry {
38
+ wallet: string;
39
+ symbol: string;
40
+ valueBefore: number;
41
+ valueAfter: number;
42
+ change: number;
43
+ changePct: number | null;
44
+ }
45
+ export declare function getPortfolio(walletFilter?: string): Promise<PortfolioReport>;
46
+ export declare function takeSnapshot(label?: string, walletFilter?: string): Promise<{
47
+ snapshotId: number;
48
+ walletCount: number;
49
+ entryCount: number;
50
+ totalValueUsd: number;
51
+ }>;
52
+ /** Auto-snapshot if the last one is older than 24h. Returns true if taken. */
53
+ export declare function autoSnapshotIfStale(): Promise<boolean>;
54
+ export declare function compareToSnapshot(snapshotId?: number, walletFilter?: string): Promise<CompareResult>;
55
+ export declare function getPnl(sinceId?: number, walletFilter?: string): Promise<CompareResult>;
@@ -0,0 +1,272 @@
1
+ import * as walletManager from './wallet-manager.js';
2
+ import { getTokenBalances } from './token-service.js';
3
+ import { getStakeAccounts } from './stake-service.js';
4
+ import { getPositions as getLendPositions } from './lend-service.js';
5
+ import { getPrices } from './price-service.js';
6
+ import * as snapshotRepo from '../db/repos/snapshot-repo.js';
7
+ import { verbose } from '../output/formatter.js';
8
+ import { SOL_MINT } from '../utils/solana.js';
9
+ // ── Portfolio ─────────────────────────────────────────────
10
+ export async function getPortfolio(walletFilter) {
11
+ const allWallets = walletManager.listWallets();
12
+ if (allWallets.length === 0)
13
+ throw new Error('No wallets found. Create one with: sol wallet create');
14
+ const wallets = walletFilter
15
+ ? allWallets.filter(w => w.name === walletFilter)
16
+ : allWallets;
17
+ if (wallets.length === 0)
18
+ throw new Error(`Wallet "${walletFilter}" not found`);
19
+ // Fetch token balances, stake accounts, and lending positions in parallel per wallet
20
+ const walletData = await Promise.all(wallets.map(async (w) => {
21
+ const [tokens, stakes, lends] = await Promise.all([
22
+ getTokenBalances(w.address),
23
+ getStakeAccounts(w.address),
24
+ getLendPositions(w.address).catch((err) => {
25
+ verbose(`Could not fetch lending positions: ${err}`);
26
+ return [];
27
+ }),
28
+ ]);
29
+ return { wallet: w, tokens, stakes, lends };
30
+ }));
31
+ // Collect all mints for batch price fetch
32
+ const allMints = new Set();
33
+ for (const { tokens, lends } of walletData) {
34
+ for (const t of tokens)
35
+ allMints.add(t.mint);
36
+ for (const l of lends)
37
+ allMints.add(l.mint);
38
+ }
39
+ allMints.add(SOL_MINT); // Stake positions need SOL price
40
+ const prices = await getPrices([...allMints]);
41
+ // Build positions
42
+ const positions = [];
43
+ let claimableMev = 0;
44
+ for (const { wallet, tokens, stakes, lends } of walletData) {
45
+ // Token positions
46
+ for (const t of tokens) {
47
+ const price = prices.get(t.mint);
48
+ positions.push({
49
+ type: 'token',
50
+ protocol: 'native',
51
+ wallet: wallet.name,
52
+ mint: t.mint,
53
+ symbol: t.symbol,
54
+ amount: t.uiBalance,
55
+ valueUsd: price ? t.uiBalance * price.priceUsd : null,
56
+ });
57
+ }
58
+ // Stake positions
59
+ for (const s of stakes) {
60
+ const solPrice = prices.get(SOL_MINT);
61
+ positions.push({
62
+ type: 'stake',
63
+ protocol: 'native',
64
+ wallet: wallet.name,
65
+ mint: SOL_MINT,
66
+ symbol: 'SOL',
67
+ amount: s.solBalance,
68
+ valueUsd: solPrice ? s.solBalance * solPrice.priceUsd : null,
69
+ extra: {
70
+ stakeAccount: s.address,
71
+ validator: s.validator,
72
+ status: s.status,
73
+ claimableExcess: s.claimableExcess,
74
+ },
75
+ });
76
+ claimableMev += s.claimableExcess;
77
+ }
78
+ // Lending positions
79
+ for (const l of lends) {
80
+ const value = l.type === 'borrow' ? -l.valueUsd : l.valueUsd;
81
+ positions.push({
82
+ type: 'lend',
83
+ protocol: l.protocol,
84
+ wallet: wallet.name,
85
+ mint: l.mint,
86
+ symbol: l.token,
87
+ amount: l.amount,
88
+ valueUsd: value,
89
+ extra: {
90
+ side: l.type,
91
+ apy: l.apy,
92
+ healthFactor: l.healthFactor,
93
+ },
94
+ });
95
+ }
96
+ }
97
+ // Compute allocation (group by symbol, only valued positions)
98
+ const symbolTotals = new Map();
99
+ let totalValueUsd = 0;
100
+ for (const p of positions) {
101
+ if (p.valueUsd != null) {
102
+ totalValueUsd += p.valueUsd;
103
+ symbolTotals.set(p.symbol, (symbolTotals.get(p.symbol) || 0) + p.valueUsd);
104
+ }
105
+ }
106
+ const allocation = [...symbolTotals.entries()]
107
+ .map(([symbol, valueUsd]) => ({
108
+ symbol,
109
+ pct: totalValueUsd > 0 ? (valueUsd / totalValueUsd) * 100 : 0,
110
+ valueUsd,
111
+ }))
112
+ .sort((a, b) => b.valueUsd - a.valueUsd);
113
+ // Last snapshot hint
114
+ const latest = snapshotRepo.getLatestSnapshot();
115
+ let lastSnapshot;
116
+ if (latest) {
117
+ lastSnapshot = {
118
+ id: latest.id,
119
+ label: latest.label ?? undefined,
120
+ ago: timeAgo(latest.created_at),
121
+ };
122
+ }
123
+ return {
124
+ wallets: wallets.map(w => w.name),
125
+ positions,
126
+ allocation,
127
+ totalValueUsd,
128
+ claimableMev,
129
+ lastSnapshot,
130
+ };
131
+ }
132
+ // ── Snapshots ─────────────────────────────────────────────
133
+ export async function takeSnapshot(label, walletFilter) {
134
+ const portfolio = await getPortfolio(walletFilter);
135
+ const snapshotId = snapshotRepo.createSnapshot(label);
136
+ let entryCount = 0;
137
+ for (const p of portfolio.positions) {
138
+ snapshotRepo.insertSnapshotEntry({
139
+ snapshot_id: snapshotId,
140
+ wallet_name: p.wallet,
141
+ wallet_address: '', // Not needed for comparison, kept for compat
142
+ mint: p.mint,
143
+ symbol: p.symbol,
144
+ balance: String(p.amount),
145
+ price_usd: p.valueUsd != null && p.amount > 0 ? p.valueUsd / p.amount : null,
146
+ value_usd: p.valueUsd,
147
+ position_type: p.type,
148
+ protocol: p.protocol ?? null,
149
+ pool_id: p.extra?.stakeAccount ?? null,
150
+ });
151
+ entryCount++;
152
+ }
153
+ return {
154
+ snapshotId,
155
+ walletCount: portfolio.wallets.length,
156
+ entryCount,
157
+ totalValueUsd: portfolio.totalValueUsd,
158
+ };
159
+ }
160
+ /** Auto-snapshot if the last one is older than 24h. Returns true if taken. */
161
+ export async function autoSnapshotIfStale() {
162
+ const latest = snapshotRepo.getLatestSnapshot();
163
+ if (!latest)
164
+ return false;
165
+ const ageMs = Date.now() - new Date(latest.created_at).getTime();
166
+ const twentyFourHours = 24 * 60 * 60 * 1000;
167
+ if (ageMs < twentyFourHours)
168
+ return false;
169
+ verbose('Last snapshot is >24h old, taking auto-snapshot');
170
+ await takeSnapshot('auto');
171
+ return true;
172
+ }
173
+ // ── Compare ───────────────────────────────────────────────
174
+ export async function compareToSnapshot(snapshotId, walletFilter) {
175
+ // Resolve snapshot
176
+ let snap;
177
+ if (snapshotId != null) {
178
+ snap = snapshotRepo.getSnapshot(snapshotId);
179
+ if (!snap)
180
+ throw new Error(`Snapshot #${snapshotId} not found`);
181
+ }
182
+ else {
183
+ snap = snapshotRepo.getLatestSnapshot();
184
+ if (!snap)
185
+ throw new Error('No snapshots found. Take one with: sol portfolio snapshot');
186
+ }
187
+ const entries = snapshotRepo.getSnapshotEntries(snap.id);
188
+ if (entries.length === 0)
189
+ throw new Error(`Snapshot #${snap.id} is empty`);
190
+ // Current state
191
+ const portfolio = await getPortfolio(walletFilter);
192
+ // Build maps keyed by wallet:mint:type
193
+ const keyFor = (wallet, mint, type) => `${wallet}:${mint}:${type}`;
194
+ const beforeMap = new Map();
195
+ for (const e of entries) {
196
+ const k = keyFor(e.wallet_name, e.mint, e.position_type);
197
+ const existing = beforeMap.get(k);
198
+ beforeMap.set(k, {
199
+ wallet: e.wallet_name,
200
+ symbol: e.symbol || 'unknown',
201
+ valueUsd: (existing?.valueUsd ?? 0) + (e.value_usd ?? 0),
202
+ });
203
+ }
204
+ const afterMap = new Map();
205
+ for (const p of portfolio.positions) {
206
+ const k = keyFor(p.wallet, p.mint, p.type);
207
+ const existing = afterMap.get(k);
208
+ afterMap.set(k, {
209
+ wallet: p.wallet,
210
+ symbol: p.symbol,
211
+ valueUsd: (existing?.valueUsd ?? 0) + (p.valueUsd ?? 0),
212
+ });
213
+ }
214
+ const allKeys = new Set([...beforeMap.keys(), ...afterMap.keys()]);
215
+ const diffs = [];
216
+ for (const key of allKeys) {
217
+ const before = beforeMap.get(key);
218
+ const after = afterMap.get(key);
219
+ const v1 = before?.valueUsd ?? 0;
220
+ const v2 = after?.valueUsd ?? 0;
221
+ const change = v2 - v1;
222
+ if (Math.abs(change) > 0.01) {
223
+ diffs.push({
224
+ wallet: before?.wallet || after?.wallet || '',
225
+ symbol: before?.symbol || after?.symbol || 'unknown',
226
+ valueBefore: v1,
227
+ valueAfter: v2,
228
+ change,
229
+ changePct: v1 > 0 ? (change / v1) * 100 : null,
230
+ });
231
+ }
232
+ }
233
+ const totalBefore = entries.reduce((s, e) => s + (e.value_usd ?? 0), 0);
234
+ const totalAfter = portfolio.totalValueUsd;
235
+ const totalChange = totalAfter - totalBefore;
236
+ return {
237
+ snapshotId: snap.id,
238
+ snapshotDate: snap.created_at,
239
+ diffs: diffs.sort((a, b) => Math.abs(b.change) - Math.abs(a.change)),
240
+ totalBefore,
241
+ totalAfter,
242
+ totalChange,
243
+ totalChangePct: totalBefore > 0 ? (totalChange / totalBefore) * 100 : null,
244
+ };
245
+ }
246
+ // ── P&L ───────────────────────────────────────────────────
247
+ export async function getPnl(sinceId, walletFilter) {
248
+ // P&L uses the oldest snapshot by default (or the specified one)
249
+ let snapId = sinceId;
250
+ if (snapId == null) {
251
+ const all = snapshotRepo.listSnapshots(1000);
252
+ if (all.length === 0)
253
+ throw new Error('No snapshots found. Take one with: sol portfolio snapshot');
254
+ snapId = all[all.length - 1].id; // oldest
255
+ }
256
+ return compareToSnapshot(snapId, walletFilter);
257
+ }
258
+ // ── Helpers ───────────────────────────────────────────────
259
+ function timeAgo(isoDate) {
260
+ const diffMs = Date.now() - new Date(isoDate).getTime();
261
+ const mins = Math.floor(diffMs / 60_000);
262
+ if (mins < 1)
263
+ return 'just now';
264
+ if (mins < 60)
265
+ return `${mins}m ago`;
266
+ const hours = Math.floor(mins / 60);
267
+ if (hours < 24)
268
+ return `${hours}h ago`;
269
+ const days = Math.floor(hours / 24);
270
+ return `${days}d ago`;
271
+ }
272
+ //# sourceMappingURL=portfolio-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"portfolio-service.js","sourceRoot":"","sources":["../../src/core/portfolio-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export interface PriceResult {
2
+ mint: string;
3
+ priceUsd: number;
4
+ source: string;
5
+ }
6
+ export declare function getPrices(mints: string[]): Promise<Map<string, PriceResult>>;
7
+ export declare function getPrice(mint: string): Promise<PriceResult | undefined>;
8
+ export declare function getCachedPrice(mint: string): PriceResult | undefined;
@@ -0,0 +1,116 @@
1
+ import { withRetry, isRetryableHttpError, RateLimiter } from '../utils/retry.js';
2
+ import { verbose } from '../output/formatter.js';
3
+ import * as priceRepo from '../db/repos/price-repo.js';
4
+ import { SOL_MINT } from '../utils/solana.js';
5
+ const jupiterLimiter = new RateLimiter(30, 60_000); // 30 req/min
6
+ const coingeckoLimiter = new RateLimiter(25, 60_000); // 25 req/min (conservative)
7
+ // Jupiter Price API v2
8
+ async function fetchJupiterPrices(mints) {
9
+ const results = new Map();
10
+ if (mints.length === 0)
11
+ return results;
12
+ await jupiterLimiter.acquire();
13
+ const ids = mints.join(',');
14
+ const url = `https://lite-api.jup.ag/price/v3?ids=${ids}`;
15
+ verbose(`Fetching Jupiter prices for ${mints.length} tokens`);
16
+ const res = await withRetry(() => fetch(url), {
17
+ maxRetries: 2,
18
+ shouldRetry: isRetryableHttpError,
19
+ });
20
+ if (!res.ok)
21
+ throw new Error(`Jupiter price API error: ${res.status}`);
22
+ const data = await res.json();
23
+ for (const [mint, info] of Object.entries(data)) {
24
+ if (info?.usdPrice) {
25
+ results.set(mint, { mint, priceUsd: info.usdPrice, source: 'jupiter' });
26
+ }
27
+ }
28
+ return results;
29
+ }
30
+ // CoinGecko fallback — maps Solana mint to CoinGecko ID for well-known tokens
31
+ const COINGECKO_IDS = {
32
+ [SOL_MINT]: 'solana',
33
+ 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v': 'usd-coin',
34
+ 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB': 'tether',
35
+ 'JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN': 'jupiter-exchange-solana',
36
+ 'DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263': 'bonk',
37
+ };
38
+ async function fetchCoingeckoPrices(mints) {
39
+ const results = new Map();
40
+ const ids = mints.map(m => COINGECKO_IDS[m]).filter(Boolean);
41
+ if (ids.length === 0)
42
+ return results;
43
+ await coingeckoLimiter.acquire();
44
+ const url = `https://api.coingecko.com/api/v3/simple/price?ids=${ids.join(',')}&vs_currencies=usd`;
45
+ verbose(`Fetching CoinGecko prices for ${ids.length} tokens`);
46
+ const res = await withRetry(() => fetch(url), {
47
+ maxRetries: 2,
48
+ shouldRetry: isRetryableHttpError,
49
+ });
50
+ if (!res.ok)
51
+ throw new Error(`CoinGecko API error: ${res.status}`);
52
+ const data = await res.json();
53
+ // Reverse-map CoinGecko IDs to mints
54
+ const idToMint = new Map();
55
+ for (const [mint, id] of Object.entries(COINGECKO_IDS)) {
56
+ idToMint.set(id, mint);
57
+ }
58
+ for (const [id, info] of Object.entries(data)) {
59
+ const mint = idToMint.get(id);
60
+ if (mint && info?.usd) {
61
+ results.set(mint, { mint, priceUsd: info.usd, source: 'coingecko' });
62
+ }
63
+ }
64
+ return results;
65
+ }
66
+ export async function getPrices(mints) {
67
+ if (mints.length === 0)
68
+ return new Map();
69
+ const results = new Map();
70
+ // Try Jupiter first
71
+ try {
72
+ const jupiterResults = await fetchJupiterPrices(mints);
73
+ for (const [mint, price] of jupiterResults) {
74
+ results.set(mint, price);
75
+ priceRepo.insertPrice(price.mint, price.priceUsd, price.source);
76
+ }
77
+ }
78
+ catch (err) {
79
+ verbose(`Jupiter price API failed: ${err}`);
80
+ }
81
+ // Try CoinGecko for any missing mints
82
+ const missing = mints.filter(m => !results.has(m));
83
+ if (missing.length > 0) {
84
+ try {
85
+ const fallback = await fetchCoingeckoPrices(missing);
86
+ for (const [mint, price] of fallback) {
87
+ results.set(mint, price);
88
+ priceRepo.insertPrice(price.mint, price.priceUsd, price.source);
89
+ }
90
+ }
91
+ catch (err) {
92
+ verbose(`CoinGecko fallback failed: ${err}`);
93
+ }
94
+ }
95
+ // For any still-missing mints, try cached prices
96
+ const stillMissing = mints.filter(m => !results.has(m));
97
+ for (const mint of stillMissing) {
98
+ const cached = priceRepo.getLatestPrice(mint);
99
+ if (cached) {
100
+ results.set(mint, { mint: cached.mint, priceUsd: cached.price_usd, source: `${cached.source}-cached` });
101
+ }
102
+ }
103
+ return results;
104
+ }
105
+ export async function getPrice(mint) {
106
+ const results = await getPrices([mint]);
107
+ return results.get(mint);
108
+ }
109
+ // Get cached price (no network)
110
+ export function getCachedPrice(mint) {
111
+ const row = priceRepo.getLatestPrice(mint);
112
+ if (!row)
113
+ return undefined;
114
+ return { mint: row.mint, priceUsd: row.price_usd, source: row.source };
115
+ }
116
+ //# sourceMappingURL=price-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"price-service.js","sourceRoot":"","sources":["../../src/core/price-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import { type Rpc, type SolanaRpcApi } from '@solana/kit';
2
+ export declare function setRpcOverride(url: string): void;
3
+ export declare function getRpcUrl(): string;
4
+ export declare function getRpc(): Rpc<SolanaRpcApi>;
5
+ export declare function resetRpcCache(): void;
@@ -0,0 +1,69 @@
1
+ import { createSolanaRpc } from '@solana/kit';
2
+ import { execSync } from 'node:child_process';
3
+ import { getConfigValue, setConfigValue } from './config-manager.js';
4
+ import { warn, verbose } from '../output/formatter.js';
5
+ let rpcOverride;
6
+ let cachedRpc = null;
7
+ let cachedUrl = null;
8
+ export function setRpcOverride(url) {
9
+ rpcOverride = url;
10
+ cachedRpc = null;
11
+ cachedUrl = null;
12
+ }
13
+ export function getRpcUrl() {
14
+ if (cachedUrl)
15
+ return cachedUrl;
16
+ // 1. CLI flag override
17
+ if (rpcOverride) {
18
+ cachedUrl = rpcOverride;
19
+ return cachedUrl;
20
+ }
21
+ // 2. Environment variable
22
+ const envUrl = process.env.SOL_RPC_URL;
23
+ if (envUrl) {
24
+ verbose('Using RPC from SOL_RPC_URL env var');
25
+ cachedUrl = envUrl;
26
+ return cachedUrl;
27
+ }
28
+ // 3. Config file
29
+ const configUrl = getConfigValue('rpc.url');
30
+ if (configUrl) {
31
+ verbose('Using RPC from config.toml');
32
+ cachedUrl = configUrl;
33
+ return cachedUrl;
34
+ }
35
+ // 4. Auto-detect from Solana CLI
36
+ try {
37
+ const output = execSync('solana config get', { timeout: 3000, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
38
+ const match = output.match(/RPC URL:\s*(https?:\/\/\S+)/);
39
+ if (match) {
40
+ verbose(`Using RPC from Solana CLI config: ${match[1]}`);
41
+ cachedUrl = match[1];
42
+ // Persist so future runs don't need to shell out
43
+ try {
44
+ setConfigValue('rpc.url', cachedUrl);
45
+ }
46
+ catch { /* non-critical */ }
47
+ return cachedUrl;
48
+ }
49
+ }
50
+ catch {
51
+ // solana CLI not installed or config not found — continue
52
+ }
53
+ // 5. Fallback
54
+ warn('Using public RPC (slow, rate-limited). Set a better one:\n sol config set rpc.url https://your-rpc.com\n Free options: https://www.helius.dev (1M credits free)');
55
+ cachedUrl = 'https://api.mainnet-beta.solana.com';
56
+ return cachedUrl;
57
+ }
58
+ export function getRpc() {
59
+ if (cachedRpc)
60
+ return cachedRpc;
61
+ const url = getRpcUrl();
62
+ cachedRpc = createSolanaRpc(url);
63
+ return cachedRpc;
64
+ }
65
+ export function resetRpcCache() {
66
+ cachedRpc = null;
67
+ cachedUrl = null;
68
+ }
69
+ //# sourceMappingURL=rpc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc.js","sourceRoot":"","sources":["../../src/core/rpc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAA6D,MAAM,aAAa,CAAC;AACzG,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEvD,IAAI,WAA+B,CAAC;AACpC,IAAI,SAAS,GAA6B,IAAI,CAAC;AAC/C,IAAI,SAAS,GAAkB,IAAI,CAAC;AAEpC,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,WAAW,GAAG,GAAG,CAAC;IAClB,SAAS,GAAG,IAAI,CAAC;IACjB,SAAS,GAAG,IAAI,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,IAAI,SAAS;QAAE,OAAO,SAAS,CAAC;IAEhC,uBAAuB;IACvB,IAAI,WAAW,EAAE,CAAC;QAChB,SAAS,GAAG,WAAW,CAAC;QACxB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,0BAA0B;IAC1B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;IACvC,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,CAAC,oCAAoC,CAAC,CAAC;QAC9C,SAAS,GAAG,MAAM,CAAC;QACnB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,iBAAiB;IACjB,MAAM,SAAS,GAAG,cAAc,CAAC,SAAS,CAAuB,CAAC;IAClE,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,CAAC,4BAA4B,CAAC,CAAC;QACtC,SAAS,GAAG,SAAS,CAAC;QACtB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,iCAAiC;IACjC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,QAAQ,CAAC,mBAAmB,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;QACpH,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC;QAC1D,IAAI,KAAK,EAAE,CAAC;YACV,OAAO,CAAC,qCAAqC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACzD,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACrB,iDAAiD;YACjD,IAAI,CAAC;gBAAC,cAAc,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC;YAC1E,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,0DAA0D;IAC5D,CAAC;IAED,cAAc;IACd,IAAI,CAAC,mKAAmK,CAAC,CAAC;IAC1K,SAAS,GAAG,qCAAqC,CAAC;IAClD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,MAAM;IACpB,IAAI,SAAS;QAAE,OAAO,SAAS,CAAC;IAChC,MAAM,GAAG,GAAG,SAAS,EAAE,CAAC;IACxB,SAAS,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IACjC,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,SAAS,GAAG,IAAI,CAAC;IACjB,SAAS,GAAG,IAAI,CAAC;AACnB,CAAC"}