@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.
- package/README.md +327 -0
- package/bin/sol.mjs +6 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +73 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/lend.d.ts +2 -0
- package/dist/commands/lend.js +262 -0
- package/dist/commands/lend.js.map +1 -0
- package/dist/commands/lp.d.ts +2 -0
- package/dist/commands/lp.js +158 -0
- package/dist/commands/lp.js.map +1 -0
- package/dist/commands/network.d.ts +2 -0
- package/dist/commands/network.js +126 -0
- package/dist/commands/network.js.map +1 -0
- package/dist/commands/portfolio.d.ts +2 -0
- package/dist/commands/portfolio.js +187 -0
- package/dist/commands/portfolio.js.map +1 -0
- package/dist/commands/stake.d.ts +2 -0
- package/dist/commands/stake.js +143 -0
- package/dist/commands/stake.js.map +1 -0
- package/dist/commands/token.d.ts +2 -0
- package/dist/commands/token.js +551 -0
- package/dist/commands/token.js.map +1 -0
- package/dist/commands/tx.d.ts +2 -0
- package/dist/commands/tx.js +131 -0
- package/dist/commands/tx.js.map +1 -0
- package/dist/commands/wallet.d.ts +2 -0
- package/dist/commands/wallet.js +403 -0
- package/dist/commands/wallet.js.map +1 -0
- package/dist/core/config-manager.d.ts +31 -0
- package/dist/core/config-manager.js +79 -0
- package/dist/core/config-manager.js.map +1 -0
- package/dist/core/kamino-compat.d.ts +17 -0
- package/dist/core/kamino-compat.js +38 -0
- package/dist/core/kamino-compat.js.map +1 -0
- package/dist/core/lend-service.d.ts +41 -0
- package/dist/core/lend-service.js +331 -0
- package/dist/core/lend-service.js.map +1 -0
- package/dist/core/lp-service.d.ts +30 -0
- package/dist/core/lp-service.js +21 -0
- package/dist/core/lp-service.js.map +1 -0
- package/dist/core/onramp-service.d.ts +15 -0
- package/dist/core/onramp-service.js +57 -0
- package/dist/core/onramp-service.js.map +1 -0
- package/dist/core/portfolio-service.d.ts +55 -0
- package/dist/core/portfolio-service.js +272 -0
- package/dist/core/portfolio-service.js.map +1 -0
- package/dist/core/price-service.d.ts +8 -0
- package/dist/core/price-service.js +116 -0
- package/dist/core/price-service.js.map +1 -0
- package/dist/core/rpc.d.ts +5 -0
- package/dist/core/rpc.js +69 -0
- package/dist/core/rpc.js.map +1 -0
- package/dist/core/stake-service.d.ts +42 -0
- package/dist/core/stake-service.js +319 -0
- package/dist/core/stake-service.js.map +1 -0
- package/dist/core/swap-service.d.ts +31 -0
- package/dist/core/swap-service.js +142 -0
- package/dist/core/swap-service.js.map +1 -0
- package/dist/core/token-registry.d.ts +23 -0
- package/dist/core/token-registry.js +174 -0
- package/dist/core/token-registry.js.map +1 -0
- package/dist/core/token-service.d.ts +20 -0
- package/dist/core/token-service.js +92 -0
- package/dist/core/token-service.js.map +1 -0
- package/dist/core/transaction.d.ts +55 -0
- package/dist/core/transaction.js +196 -0
- package/dist/core/transaction.js.map +1 -0
- package/dist/core/wallet-manager.d.ts +20 -0
- package/dist/core/wallet-manager.js +142 -0
- package/dist/core/wallet-manager.js.map +1 -0
- package/dist/db/database.d.ts +3 -0
- package/dist/db/database.js +44 -0
- package/dist/db/database.js.map +1 -0
- package/dist/db/migrations/001_initial.d.ts +1 -0
- package/dist/db/migrations/001_initial.js +116 -0
- package/dist/db/migrations/001_initial.js.map +1 -0
- package/dist/db/migrations/002_tx_prices.d.ts +1 -0
- package/dist/db/migrations/002_tx_prices.js +5 -0
- package/dist/db/migrations/002_tx_prices.js.map +1 -0
- package/dist/db/repos/price-repo.d.ts +11 -0
- package/dist/db/repos/price-repo.js +14 -0
- package/dist/db/repos/price-repo.js.map +1 -0
- package/dist/db/repos/snapshot-repo.d.ts +27 -0
- package/dist/db/repos/snapshot-repo.js +32 -0
- package/dist/db/repos/snapshot-repo.js.map +1 -0
- package/dist/db/repos/token-repo.d.ts +17 -0
- package/dist/db/repos/token-repo.js +53 -0
- package/dist/db/repos/token-repo.js.map +1 -0
- package/dist/db/repos/transaction-repo.d.ts +22 -0
- package/dist/db/repos/transaction-repo.js +28 -0
- package/dist/db/repos/transaction-repo.js.map +1 -0
- package/dist/db/repos/wallet-repo.d.ts +18 -0
- package/dist/db/repos/wallet-repo.js +44 -0
- package/dist/db/repos/wallet-repo.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +84 -0
- package/dist/index.js.map +1 -0
- package/dist/output/formatter.d.ts +27 -0
- package/dist/output/formatter.js +76 -0
- package/dist/output/formatter.js.map +1 -0
- package/dist/output/portfolio-renderer.d.ts +3 -0
- package/dist/output/portfolio-renderer.js +205 -0
- package/dist/output/portfolio-renderer.js.map +1 -0
- package/dist/output/table.d.ts +8 -0
- package/dist/output/table.js +22 -0
- package/dist/output/table.js.map +1 -0
- package/dist/utils/fs.d.ts +7 -0
- package/dist/utils/fs.js +39 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/retry.d.ts +17 -0
- package/dist/utils/retry.js +55 -0
- package/dist/utils/retry.js.map +1 -0
- package/dist/utils/solana.d.ts +9 -0
- package/dist/utils/solana.js +26 -0
- package/dist/utils/solana.js.map +1 -0
- package/dist/utils/token-list.d.ts +9 -0
- package/dist/utils/token-list.js +31 -0
- package/dist/utils/token-list.js.map +1 -0
- 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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"}
|
package/dist/core/rpc.js
ADDED
|
@@ -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"}
|