multi-chain-balance-diff 0.1.2 → 0.1.5
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 +35 -1
- package/package.json +6 -2
- package/schema/mcbd-output.schema.json +1 -0
- package/src/adapters/baseAdapter.js +1 -0
- package/src/adapters/evmAdapter.js +1 -0
- package/src/adapters/solanaAdapter.js +1 -0
- package/src/adapters/tonAdapter.js +1 -0
- package/src/config/networks.js +15 -0
- package/src/index.js +89 -0
- package/src/services/balanceService.js +1 -0
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ mcbd --address 0x... --network mainnet --json | jq '.native.diff'
|
|
|
11
11
|
|
|
12
12
|
## What
|
|
13
13
|
|
|
14
|
-
CLI that fetches balance at block N and block N-50, computes the difference, outputs JSON or pretty-prints. Supports Ethereum, Polygon, Base, Arbitrum, Optimism, Solana, Helium.
|
|
14
|
+
CLI that fetches balance at block N and block N-50, computes the difference, outputs JSON or pretty-prints. Supports Ethereum, Polygon, Base, Arbitrum, Optimism, BNB Chain, Avalanche, Fantom, zkSync Era, Scroll, Solana, Helium, and TON.
|
|
15
15
|
|
|
16
16
|
## Who
|
|
17
17
|
|
|
@@ -42,6 +42,8 @@ mcbd --address $FEE_WALLET --network base --alert-if-diff "<-0.1"
|
|
|
42
42
|
# exit 0 = OK, exit 1 = threshold breached, exit 2 = RPC error
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
> 📁 See [`.github/examples/`](./.github/examples/) for ready-to-use GitHub Actions workflows.
|
|
46
|
+
|
|
45
47
|
### 3. DeFi/Rewards: Staking reward accrual verification
|
|
46
48
|
|
|
47
49
|
```bash
|
|
@@ -114,6 +116,7 @@ mcbd --address 0x... --alert-if-diff ">0.01" # CI threshold
|
|
|
114
116
|
| `avalanche` | EVM | AVAX | USDC, USDT |
|
|
115
117
|
| `fantom` | EVM | FTM | USDC, USDT, DAI |
|
|
116
118
|
| `zksync` | EVM | ETH | USDC, USDT |
|
|
119
|
+
| `scroll` | EVM | ETH | USDC, USDT, WETH |
|
|
117
120
|
| `solana` | Solana | SOL | USDC, BONK, JUP |
|
|
118
121
|
| `helium` | Solana | SOL | HNT, MOBILE, IOT, DC |
|
|
119
122
|
| `ton` | TON | TON | — |
|
|
@@ -136,6 +139,7 @@ mcbd --address 0x... --alert-if-diff ">0.01" # CI threshold
|
|
|
136
139
|
| `--alert-if-diff` | Exit 1 if diff matches condition (e.g., `">0.01"`, `"<-1"`) |
|
|
137
140
|
| `--alert-pct` | Exit 1 if diff exceeds % of balance (e.g., `">5"`, `"<-10"`) |
|
|
138
141
|
| `--timeout` | RPC request timeout in seconds (default: `30`) |
|
|
142
|
+
| `--webhook` | POST JSON payload to URL when alert triggers |
|
|
139
143
|
|
|
140
144
|
**Exit codes:** `0` OK · `1` diff triggered · `2` RPC failure/timeout · `130` SIGINT
|
|
141
145
|
|
|
@@ -185,6 +189,26 @@ mcbd -a 0x... -n mainnet --timeout 60 # 60 seconds
|
|
|
185
189
|
mcbd -a 0x... -n solana --timeout 10 # 10 seconds (fast-fail)
|
|
186
190
|
```
|
|
187
191
|
|
|
192
|
+
### Webhooks
|
|
193
|
+
|
|
194
|
+
POST JSON payload to a URL when an alert triggers:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
# Slack webhook
|
|
198
|
+
mcbd -a 0xTreasury -n mainnet --alert-if-diff "<-1" \
|
|
199
|
+
--webhook https://hooks.slack.com/services/XXX/YYY/ZZZ --json
|
|
200
|
+
|
|
201
|
+
# Discord webhook
|
|
202
|
+
mcbd -a 0xTreasury -n base --alert-pct "<-5" \
|
|
203
|
+
--webhook https://discord.com/api/webhooks/XXX/YYY --json
|
|
204
|
+
|
|
205
|
+
# Custom endpoint
|
|
206
|
+
mcbd -a 0xTreasury -n polygon --alert-if-diff ">0.1" \
|
|
207
|
+
--webhook https://your-server.com/balance-alert --json
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
The webhook payload includes the full JSON output plus a `webhook.sentAt` timestamp.
|
|
211
|
+
|
|
188
212
|
---
|
|
189
213
|
|
|
190
214
|
## Common Failure Modes
|
|
@@ -360,6 +384,16 @@ MIT
|
|
|
360
384
|
|
|
361
385
|
---
|
|
362
386
|
|
|
387
|
+
## Author
|
|
388
|
+
|
|
389
|
+
Built by [Alex Alexapolsky](https://www.linkedin.com/in/alexey-a-181a614/) ([@metawake](https://github.com/metawake)). Available for Web3 infra consulting and contract work.
|
|
390
|
+
|
|
391
|
+
⭐ **If you find this useful, [star the repo](https://github.com/metawake/multi-chain-balance-diff)** — it helps others discover it.
|
|
392
|
+
|
|
393
|
+
If this tool saves you time: [GitHub Sponsors](https://github.com/sponsors/metawake) · Tips: `0x0a542565b3615e8fc934cc3cc4921a0c22e5dc5e`
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
363
397
|
<!--
|
|
364
398
|
GitHub repo description (1 sentence):
|
|
365
399
|
CLI tool to fetch and diff wallet balances across EVM and Solana chains. JSON output, CI-friendly exit codes.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "multi-chain-balance-diff",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "CLI tool to fetch and compare wallet balances across EVM and Solana chains",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -29,8 +29,12 @@
|
|
|
29
29
|
"crypto",
|
|
30
30
|
"wallet"
|
|
31
31
|
],
|
|
32
|
-
"author": "",
|
|
32
|
+
"author": "Alex Alexapolsky (https://github.com/metawake)",
|
|
33
33
|
"license": "MIT",
|
|
34
|
+
"funding": {
|
|
35
|
+
"type": "github",
|
|
36
|
+
"url": "https://github.com/sponsors/metawake"
|
|
37
|
+
},
|
|
34
38
|
"repository": {
|
|
35
39
|
"type": "git",
|
|
36
40
|
"url": "git+https://github.com/metawake/multi-chain-balance-diff.git"
|
package/src/config/networks.js
CHANGED
|
@@ -178,6 +178,21 @@ const networks = {
|
|
|
178
178
|
],
|
|
179
179
|
},
|
|
180
180
|
|
|
181
|
+
scroll: {
|
|
182
|
+
name: 'Scroll',
|
|
183
|
+
chainType: 'evm',
|
|
184
|
+
chainId: 534352,
|
|
185
|
+
rpcUrl: process.env.RPC_URL_SCROLL || 'https://rpc.scroll.io',
|
|
186
|
+
nativeSymbol: 'ETH',
|
|
187
|
+
nativeDecimals: 18,
|
|
188
|
+
blockExplorer: 'https://scrollscan.com',
|
|
189
|
+
tokens: [
|
|
190
|
+
{ symbol: 'USDC', address: '0x06eFdBFf2a14a7c8E15944D1F4A48F9F95F663A4', decimals: 6 },
|
|
191
|
+
{ symbol: 'USDT', address: '0xf55BEC9cafDbE8730f096Aa55dad6D22d44099Df', decimals: 6 },
|
|
192
|
+
{ symbol: 'WETH', address: '0x5300000000000000000000000000000000000004', decimals: 18 },
|
|
193
|
+
],
|
|
194
|
+
},
|
|
195
|
+
|
|
181
196
|
// ==========================================================================
|
|
182
197
|
// Solana Networks
|
|
183
198
|
// ==========================================================================
|
package/src/index.js
CHANGED
|
@@ -59,6 +59,7 @@ program
|
|
|
59
59
|
.option('--alert-if-diff <threshold>', 'Exit 1 if diff exceeds threshold (e.g., ">0.01", ">=1", "<-0.5")')
|
|
60
60
|
.option('--alert-pct <threshold>', 'Exit 1 if diff exceeds % of balance (e.g., ">5", "<-10")')
|
|
61
61
|
.option('--timeout <seconds>', 'RPC request timeout in seconds', '30')
|
|
62
|
+
.option('--webhook <url>', 'POST JSON payload to URL when alert triggers')
|
|
62
63
|
.parse(process.argv);
|
|
63
64
|
|
|
64
65
|
const options = program.opts();
|
|
@@ -310,6 +311,88 @@ function checkPercentageThreshold(diffRaw, previousRaw, decimals, threshold) {
|
|
|
310
311
|
}
|
|
311
312
|
}
|
|
312
313
|
|
|
314
|
+
// ==========================================================================
|
|
315
|
+
// Webhook Support
|
|
316
|
+
// ==========================================================================
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* POST JSON payload to webhook URL
|
|
320
|
+
* @param {string} url - Webhook URL
|
|
321
|
+
* @param {object} payload - JSON payload to send
|
|
322
|
+
* @returns {Promise<{success: boolean, statusCode?: number, error?: string}>}
|
|
323
|
+
*/
|
|
324
|
+
async function sendWebhook(url, payload) {
|
|
325
|
+
try {
|
|
326
|
+
const urlObj = new URL(url);
|
|
327
|
+
const isHttps = urlObj.protocol === 'https:';
|
|
328
|
+
const http = isHttps ? require('https') : require('http');
|
|
329
|
+
|
|
330
|
+
const postData = JSON.stringify(payload);
|
|
331
|
+
|
|
332
|
+
const requestOptions = {
|
|
333
|
+
hostname: urlObj.hostname,
|
|
334
|
+
port: urlObj.port || (isHttps ? 443 : 80),
|
|
335
|
+
path: urlObj.pathname + urlObj.search,
|
|
336
|
+
method: 'POST',
|
|
337
|
+
headers: {
|
|
338
|
+
'Content-Type': 'application/json',
|
|
339
|
+
'Content-Length': Buffer.byteLength(postData),
|
|
340
|
+
'User-Agent': `multi-chain-balance-diff/${VERSION}`,
|
|
341
|
+
},
|
|
342
|
+
timeout: 10000, // 10 second timeout for webhook
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
return new Promise((resolve) => {
|
|
346
|
+
const req = http.request(requestOptions, (res) => {
|
|
347
|
+
resolve({ success: res.statusCode >= 200 && res.statusCode < 300, statusCode: res.statusCode });
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
req.on('error', (error) => {
|
|
351
|
+
resolve({ success: false, error: error.message });
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
req.on('timeout', () => {
|
|
355
|
+
req.destroy();
|
|
356
|
+
resolve({ success: false, error: 'Request timeout' });
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
req.write(postData);
|
|
360
|
+
req.end();
|
|
361
|
+
});
|
|
362
|
+
} catch (error) {
|
|
363
|
+
return { success: false, error: error.message };
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Build and send webhook if configured and alert triggered
|
|
369
|
+
*/
|
|
370
|
+
async function maybeNotifyWebhook(payload, alertTriggered) {
|
|
371
|
+
if (!options.webhook || !alertTriggered) {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const webhookPayload = {
|
|
376
|
+
...payload,
|
|
377
|
+
webhook: {
|
|
378
|
+
sentAt: new Date().toISOString(),
|
|
379
|
+
trigger: 'alert',
|
|
380
|
+
},
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
const result = await sendWebhook(options.webhook, webhookPayload);
|
|
384
|
+
|
|
385
|
+
if (!options.json) {
|
|
386
|
+
if (result.success) {
|
|
387
|
+
console.log(`${c('green')}✓ Webhook sent${c('reset')}`);
|
|
388
|
+
} else {
|
|
389
|
+
console.log(`${c('red')}✗ Webhook failed: ${result.error || `HTTP ${result.statusCode}`}${c('reset')}`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return result;
|
|
394
|
+
}
|
|
395
|
+
|
|
313
396
|
// ==========================================================================
|
|
314
397
|
// Network List Command
|
|
315
398
|
// ==========================================================================
|
|
@@ -938,6 +1021,9 @@ async function main() {
|
|
|
938
1021
|
console.log(`${c('yellow')}⚠️ Alert: threshold ${which} triggered${c('reset')}\n`);
|
|
939
1022
|
}
|
|
940
1023
|
}
|
|
1024
|
+
|
|
1025
|
+
// Send webhook if configured and alert triggered
|
|
1026
|
+
await maybeNotifyWebhook(output, anyAlertTriggered);
|
|
941
1027
|
|
|
942
1028
|
process.exit(anyAlertTriggered ? EXIT_DIFF : EXIT_OK);
|
|
943
1029
|
}
|
|
@@ -999,6 +1085,9 @@ async function main() {
|
|
|
999
1085
|
}
|
|
1000
1086
|
}
|
|
1001
1087
|
|
|
1088
|
+
// Send webhook if configured and alert triggered
|
|
1089
|
+
await maybeNotifyWebhook(output, alertTriggered);
|
|
1090
|
+
|
|
1002
1091
|
// Exit with appropriate code
|
|
1003
1092
|
process.exit(alertTriggered ? EXIT_DIFF : EXIT_OK);
|
|
1004
1093
|
|