solana-balance-cli 1.0.1 ā 1.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 +21 -0
- package/dist/index.js +91 -5
- package/dist/price.js +44 -0
- package/dist/tokens.js +35 -0
- package/dist/watch.js +85 -0
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
A simple command-line tool to check your Solana wallet balance in both SOL and USD.
|
|
4
4
|
|
|
5
|
+
**Package:** `solana-balance-cli` | **Command:** `sol-balance`
|
|
6
|
+
|
|
5
7
|
## Features
|
|
6
8
|
|
|
7
9
|
- ā
Fetch real-time Solana wallet balance
|
|
@@ -144,6 +146,14 @@ This will prompt you to:
|
|
|
144
146
|
|
|
145
147
|
This creates a changeset file in `.changeset/` that describes your changes.
|
|
146
148
|
|
|
149
|
+
**After creating a changeset, commit and push it:**
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
git add .changeset/
|
|
153
|
+
git commit -m "chore: add changeset"
|
|
154
|
+
git push
|
|
155
|
+
```
|
|
156
|
+
|
|
147
157
|
#### Versioning and Publishing
|
|
148
158
|
|
|
149
159
|
To create a new version and update the changelog:
|
|
@@ -158,6 +168,14 @@ This will:
|
|
|
158
168
|
- Generate/update `CHANGELOG.md`
|
|
159
169
|
- Remove used changeset files
|
|
160
170
|
|
|
171
|
+
**After versioning, commit the changes:**
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
git add .
|
|
175
|
+
git commit -m "chore: version bump"
|
|
176
|
+
git push
|
|
177
|
+
```
|
|
178
|
+
|
|
161
179
|
To publish to npm:
|
|
162
180
|
|
|
163
181
|
```bash
|
|
@@ -172,6 +190,9 @@ Or you can publish manually after versioning:
|
|
|
172
190
|
|
|
173
191
|
```bash
|
|
174
192
|
npm run version
|
|
193
|
+
git add .
|
|
194
|
+
git commit -m "chore: version bump"
|
|
195
|
+
git push
|
|
175
196
|
npm publish
|
|
176
197
|
```
|
|
177
198
|
|
package/dist/index.js
CHANGED
|
@@ -4,13 +4,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
const commander_1 = require("commander");
|
|
5
5
|
const solana_1 = require("./solana");
|
|
6
6
|
const price_1 = require("./price");
|
|
7
|
+
const tokens_1 = require("./tokens");
|
|
8
|
+
const watch_1 = require("./watch");
|
|
7
9
|
const program = new commander_1.Command();
|
|
8
10
|
program
|
|
9
11
|
.name('sol-balance')
|
|
10
12
|
.description('Check Solana wallet balance in SOL and USD')
|
|
11
|
-
.version('1.0.0')
|
|
13
|
+
.version('1.0.0');
|
|
14
|
+
// Balance check command
|
|
15
|
+
program
|
|
16
|
+
.command('balance')
|
|
17
|
+
.description('Check Solana wallet balance')
|
|
12
18
|
.argument('<wallet-address>', 'Solana wallet address to check')
|
|
13
|
-
.
|
|
19
|
+
.option('-t, --tokens', 'Show SPL token balances and their USD values')
|
|
20
|
+
.action(async (walletAddress, options) => {
|
|
14
21
|
try {
|
|
15
22
|
console.log('\nš Fetching wallet balance...\n');
|
|
16
23
|
const [balance, price] = await Promise.all([
|
|
@@ -21,9 +28,46 @@ program
|
|
|
21
28
|
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
22
29
|
console.log(`š Wallet: ${walletAddress}`);
|
|
23
30
|
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
24
|
-
console.log(`š° Balance: ${balance.toFixed(4)} SOL`);
|
|
25
|
-
console.log(`šµ SOL Price:
|
|
26
|
-
console.log(`šø
|
|
31
|
+
console.log(`š° SOL Balance: ${balance.toFixed(4)} SOL`);
|
|
32
|
+
console.log(`šµ SOL Price: ${price.toFixed(2)} USD`);
|
|
33
|
+
console.log(`šø SOL Value: ${usdValue.toFixed(2)} USD`);
|
|
34
|
+
if (options.tokens) {
|
|
35
|
+
console.log('\nšŖ Fetching SPL tokens...\n');
|
|
36
|
+
const tokenAccounts = await (0, tokens_1.getTokenAccounts)(walletAddress);
|
|
37
|
+
if (tokenAccounts.length === 0) {
|
|
38
|
+
console.log('No SPL tokens found in this wallet.');
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
const mintAddresses = tokenAccounts.map(token => token.mint);
|
|
42
|
+
const tokenPrices = await (0, price_1.getTokenPrices)(mintAddresses);
|
|
43
|
+
let totalTokenValue = 0;
|
|
44
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
45
|
+
console.log('SPL TOKENS:');
|
|
46
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
47
|
+
for (const token of tokenAccounts) {
|
|
48
|
+
const tokenPrice = tokenPrices.get(token.mint) || 0;
|
|
49
|
+
const tokenValue = token.uiAmount * tokenPrice;
|
|
50
|
+
totalTokenValue += tokenValue;
|
|
51
|
+
console.log(`\nš¹ Token: ${token.mint}`);
|
|
52
|
+
console.log(` Balance: ${token.uiAmount.toFixed(token.decimals)} tokens`);
|
|
53
|
+
if (tokenPrice > 0) {
|
|
54
|
+
console.log(` Price: ${tokenPrice.toFixed(6)} USD`);
|
|
55
|
+
console.log(` Value: ${tokenValue.toFixed(2)} USD`);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
console.log(` Price: Not available`);
|
|
59
|
+
console.log(` Value: $0.00 USD`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const totalPortfolioValue = usdValue + totalTokenValue;
|
|
63
|
+
console.log('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
64
|
+
console.log('PORTFOLIO SUMMARY:');
|
|
65
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
66
|
+
console.log(`š° Total SOL Value: ${usdValue.toFixed(2)} USD`);
|
|
67
|
+
console.log(`šŖ Total Token Value: ${totalTokenValue.toFixed(2)} USD`);
|
|
68
|
+
console.log(`š Total Portfolio Value: ${totalPortfolioValue.toFixed(2)} USD`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
27
71
|
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n');
|
|
28
72
|
}
|
|
29
73
|
catch (error) {
|
|
@@ -36,4 +80,46 @@ program
|
|
|
36
80
|
process.exit(1);
|
|
37
81
|
}
|
|
38
82
|
});
|
|
83
|
+
// Watch price command
|
|
84
|
+
program
|
|
85
|
+
.command('watch')
|
|
86
|
+
.description('Watch SOL price and send notifications when it crosses target prices')
|
|
87
|
+
.option('--above <price>', 'Send notification when price goes above this value', parseFloat)
|
|
88
|
+
.option('--below <price>', 'Send notification when price goes below this value', parseFloat)
|
|
89
|
+
.option('--interval <seconds>', 'Price check interval in seconds (default: 60)', parseFloat, 60)
|
|
90
|
+
.action(async (options) => {
|
|
91
|
+
try {
|
|
92
|
+
// Validate options
|
|
93
|
+
if (!options.above && !options.below) {
|
|
94
|
+
console.error('\nā Error: You must specify at least one target price (--above or --below)\n');
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
if (options.above && isNaN(options.above)) {
|
|
98
|
+
console.error('\nā Error: --above must be a valid number\n');
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
if (options.below && isNaN(options.below)) {
|
|
102
|
+
console.error('\nā Error: --below must be a valid number\n');
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
if (isNaN(options.interval) || options.interval < 1) {
|
|
106
|
+
console.error('\nā Error: --interval must be a number >= 1\n');
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
if (options.above && options.below && options.above <= options.below) {
|
|
110
|
+
console.error('\nā Error: --above price must be greater than --below price\n');
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
await (0, watch_1.watchSolanaPrice)(options);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
if (error instanceof Error) {
|
|
117
|
+
console.error(`\nā Error: ${error.message}\n`);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
console.error('\nā An unknown error occurred\n');
|
|
121
|
+
}
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
39
125
|
program.parse();
|
package/dist/price.js
CHANGED
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.getSolanaPrice = getSolanaPrice;
|
|
7
|
+
exports.getTokenPrices = getTokenPrices;
|
|
7
8
|
const axios_1 = __importDefault(require("axios"));
|
|
8
9
|
async function getSolanaPrice() {
|
|
9
10
|
try {
|
|
@@ -21,3 +22,46 @@ async function getSolanaPrice() {
|
|
|
21
22
|
throw new Error('Failed to fetch SOL price: Unknown error');
|
|
22
23
|
}
|
|
23
24
|
}
|
|
25
|
+
async function getTokenPrices(mintAddresses) {
|
|
26
|
+
try {
|
|
27
|
+
if (mintAddresses.length === 0) {
|
|
28
|
+
return new Map();
|
|
29
|
+
}
|
|
30
|
+
const priceMap = new Map();
|
|
31
|
+
const batchSize = 50; // Limit batch size to avoid URI too long error
|
|
32
|
+
// Process tokens in batches
|
|
33
|
+
for (let i = 0; i < mintAddresses.length; i += batchSize) {
|
|
34
|
+
const batch = mintAddresses.slice(i, i + batchSize);
|
|
35
|
+
const contractAddresses = batch.join(',');
|
|
36
|
+
try {
|
|
37
|
+
const response = await axios_1.default.get(`https://api.coingecko.com/api/v3/simple/token_price/solana?contract_addresses=${contractAddresses}&vs_currencies=usd`, { timeout: 10000 });
|
|
38
|
+
for (const mint of batch) {
|
|
39
|
+
const mintLower = mint.toLowerCase();
|
|
40
|
+
if (response.data[mintLower] && typeof response.data[mintLower].usd === 'number') {
|
|
41
|
+
priceMap.set(mint, response.data[mintLower].usd);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
priceMap.set(mint, 0);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (batchError) {
|
|
49
|
+
// If batch fails, set all tokens in batch to 0 price
|
|
50
|
+
for (const mint of batch) {
|
|
51
|
+
priceMap.set(mint, 0);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Small delay between batches to avoid rate limiting
|
|
55
|
+
if (i + batchSize < mintAddresses.length) {
|
|
56
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return priceMap;
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (error instanceof Error) {
|
|
63
|
+
throw new Error(`Failed to fetch token prices: ${error.message}`);
|
|
64
|
+
}
|
|
65
|
+
throw new Error('Failed to fetch token prices: Unknown error');
|
|
66
|
+
}
|
|
67
|
+
}
|
package/dist/tokens.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getTokenAccounts = getTokenAccounts;
|
|
4
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
5
|
+
const spl_token_1 = require("@solana/spl-token");
|
|
6
|
+
async function getTokenAccounts(walletAddress) {
|
|
7
|
+
try {
|
|
8
|
+
const connection = new web3_js_1.Connection('https://api.mainnet-beta.solana.com', 'confirmed');
|
|
9
|
+
const publicKey = new web3_js_1.PublicKey(walletAddress);
|
|
10
|
+
const tokenAccounts = await connection.getParsedTokenAccountsByOwner(publicKey, { programId: spl_token_1.TOKEN_PROGRAM_ID });
|
|
11
|
+
const tokens = tokenAccounts.value
|
|
12
|
+
.map((accountInfo) => {
|
|
13
|
+
const parsedInfo = accountInfo.account.data.parsed.info;
|
|
14
|
+
const tokenAmount = parsedInfo.tokenAmount;
|
|
15
|
+
// Only include tokens with non-zero balance
|
|
16
|
+
if (tokenAmount.uiAmount > 0) {
|
|
17
|
+
return {
|
|
18
|
+
mint: parsedInfo.mint,
|
|
19
|
+
balance: parseInt(tokenAmount.amount),
|
|
20
|
+
decimals: tokenAmount.decimals,
|
|
21
|
+
uiAmount: tokenAmount.uiAmount,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
})
|
|
26
|
+
.filter((token) => token !== null);
|
|
27
|
+
return tokens;
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (error instanceof Error) {
|
|
31
|
+
throw new Error(`Failed to fetch token accounts: ${error.message}`);
|
|
32
|
+
}
|
|
33
|
+
throw new Error('Failed to fetch token accounts: Unknown error');
|
|
34
|
+
}
|
|
35
|
+
}
|
package/dist/watch.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.watchSolanaPrice = watchSolanaPrice;
|
|
7
|
+
const node_notifier_1 = __importDefault(require("node-notifier"));
|
|
8
|
+
const price_1 = require("./price");
|
|
9
|
+
async function watchSolanaPrice(options) {
|
|
10
|
+
const { above, below, interval } = options;
|
|
11
|
+
console.log('\nš Starting SOL Price Watch Mode\n');
|
|
12
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
13
|
+
console.log('āļø Configuration:');
|
|
14
|
+
if (above) {
|
|
15
|
+
console.log(` š Alert when price goes ABOVE: $${above.toFixed(2)}`);
|
|
16
|
+
}
|
|
17
|
+
if (below) {
|
|
18
|
+
console.log(` š Alert when price goes BELOW: $${below.toFixed(2)}`);
|
|
19
|
+
}
|
|
20
|
+
console.log(` ā±ļø Check interval: ${interval} seconds`);
|
|
21
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
22
|
+
console.log('\nš” Press Ctrl+C to stop watching\n');
|
|
23
|
+
const state = {
|
|
24
|
+
aboveNotified: false,
|
|
25
|
+
belowNotified: false,
|
|
26
|
+
};
|
|
27
|
+
let isRunning = true;
|
|
28
|
+
// Handle graceful shutdown
|
|
29
|
+
process.on('SIGINT', () => {
|
|
30
|
+
console.log('\n\nš Stopping price watch...\n');
|
|
31
|
+
isRunning = false;
|
|
32
|
+
process.exit(0);
|
|
33
|
+
});
|
|
34
|
+
// Main watch loop
|
|
35
|
+
while (isRunning) {
|
|
36
|
+
try {
|
|
37
|
+
const currentPrice = await (0, price_1.getSolanaPrice)();
|
|
38
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
39
|
+
console.log(`[${timestamp}] š° Current SOL Price: $${currentPrice.toFixed(2)}`);
|
|
40
|
+
// Check above threshold
|
|
41
|
+
if (above && currentPrice >= above && !state.aboveNotified) {
|
|
42
|
+
const message = `SOL crossed $${above.toFixed(2)} (currently $${currentPrice.toFixed(2)})`;
|
|
43
|
+
node_notifier_1.default.notify({
|
|
44
|
+
title: 'š SOL Price Alert - Above Target',
|
|
45
|
+
message: message,
|
|
46
|
+
sound: true,
|
|
47
|
+
wait: false,
|
|
48
|
+
});
|
|
49
|
+
console.log(`\nš ALERT: ${message}\n`);
|
|
50
|
+
state.aboveNotified = true;
|
|
51
|
+
}
|
|
52
|
+
// Check below threshold
|
|
53
|
+
if (below && currentPrice <= below && !state.belowNotified) {
|
|
54
|
+
const message = `SOL dropped below $${below.toFixed(2)} (currently $${currentPrice.toFixed(2)})`;
|
|
55
|
+
node_notifier_1.default.notify({
|
|
56
|
+
title: 'š SOL Price Alert - Below Target',
|
|
57
|
+
message: message,
|
|
58
|
+
sound: true,
|
|
59
|
+
wait: false,
|
|
60
|
+
});
|
|
61
|
+
console.log(`\nš ALERT: ${message}\n`);
|
|
62
|
+
state.belowNotified = true;
|
|
63
|
+
}
|
|
64
|
+
// Reset notification states when price moves back
|
|
65
|
+
if (above && currentPrice < above) {
|
|
66
|
+
state.aboveNotified = false;
|
|
67
|
+
}
|
|
68
|
+
if (below && currentPrice > below) {
|
|
69
|
+
state.belowNotified = false;
|
|
70
|
+
}
|
|
71
|
+
// Wait for the specified interval
|
|
72
|
+
await new Promise(resolve => setTimeout(resolve, interval * 1000));
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (error instanceof Error) {
|
|
76
|
+
console.error(`ā Error fetching price: ${error.message}`);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
console.error('ā Unknown error occurred');
|
|
80
|
+
}
|
|
81
|
+
// Wait before retrying
|
|
82
|
+
await new Promise(resolve => setTimeout(resolve, interval * 1000));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "solana-balance-cli",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "CLI tool to check Solana wallet balance in SOL and USD",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -40,9 +40,12 @@
|
|
|
40
40
|
"url": ""
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
+
"@solana/spl-token": "^0.4.14",
|
|
43
44
|
"@solana/web3.js": "^1.98.4",
|
|
45
|
+
"@types/node-notifier": "^8.0.5",
|
|
44
46
|
"axios": "^1.13.2",
|
|
45
|
-
"commander": "^14.0.2"
|
|
47
|
+
"commander": "^14.0.2",
|
|
48
|
+
"node-notifier": "^10.0.1"
|
|
46
49
|
},
|
|
47
50
|
"devDependencies": {
|
|
48
51
|
"@changesets/cli": "^2.29.8",
|