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 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.2",
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"
@@ -120,3 +120,4 @@ class BaseAdapter {
120
120
 
121
121
  module.exports = BaseAdapter;
122
122
 
123
+
@@ -134,3 +134,4 @@ class EVMAdapter extends BaseAdapter {
134
134
  module.exports = EVMAdapter;
135
135
 
136
136
 
137
+
@@ -207,3 +207,4 @@ class SolanaAdapter extends BaseAdapter {
207
207
  module.exports = SolanaAdapter;
208
208
 
209
209
 
210
+
@@ -93,3 +93,4 @@ class TonAdapter extends BaseAdapter {
93
93
 
94
94
  module.exports = TonAdapter;
95
95
 
96
+
@@ -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
 
@@ -155,3 +155,4 @@ module.exports = {
155
155
  formatBalanceDiff,
156
156
  };
157
157
 
158
+