multi-chain-balance-diff 0.1.3 โ†’ 0.1.6

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, BNB Chain, Avalanche, Fantom, zkSync Era, Solana, Helium, and TON.
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,25 @@ 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
+ ## Support Development
394
+
395
+ If this tool saves you time or money, consider supporting its development:
396
+
397
+ **ETH donations (any EVM chain):**
398
+ ```
399
+ 0x0a542565b3615e8fc934cc3cc4921a0c22e5dc5e
400
+ ```
401
+
402
+ ๐Ÿ’ก *Hint: This tool monitors balance changes โ€” you can use it to verify your own donation went through!*
403
+
404
+ ---
405
+
363
406
  <!--
364
407
  GitHub repo description (1 sentence):
365
408
  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",
3
+ "version": "0.1.6",
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,6 @@ class BaseAdapter {
120
120
 
121
121
  module.exports = BaseAdapter;
122
122
 
123
+
124
+
125
+
@@ -134,3 +134,6 @@ class EVMAdapter extends BaseAdapter {
134
134
  module.exports = EVMAdapter;
135
135
 
136
136
 
137
+
138
+
139
+
@@ -207,3 +207,6 @@ class SolanaAdapter extends BaseAdapter {
207
207
  module.exports = SolanaAdapter;
208
208
 
209
209
 
210
+
211
+
212
+
@@ -93,3 +93,6 @@ class TonAdapter extends BaseAdapter {
93
93
 
94
94
  module.exports = TonAdapter;
95
95
 
96
+
97
+
98
+
@@ -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
  // ==========================================================================
@@ -479,10 +562,15 @@ function printPrettyOutput(networkConfig, address, balanceDiff, tokenBalances, a
479
562
 
480
563
  if (!isMulti) {
481
564
  printSeparator('โ•');
565
+ printSponsorHint();
482
566
  console.log();
483
567
  }
484
568
  }
485
569
 
570
+ function printSponsorHint() {
571
+ console.log(` ${c('dim')}โ˜… Tips: 0x0a54...dc5e ยท github.com/metawake/multi-chain-balance-diff${c('reset')}`);
572
+ }
573
+
486
574
  function printMultiAddressSummary(networkConfig, results, blocksBack) {
487
575
  console.log();
488
576
  printSeparator('โ•');
@@ -512,6 +600,7 @@ function printMultiAddressSummary(networkConfig, results, blocksBack) {
512
600
  const totalDiffColored = formatDiffColored(totalDiff, networkConfig.nativeSymbol, networkConfig.nativeDecimals);
513
601
  console.log(` ${c('bright')}Total:${c('reset')} ${totalFormatted.padStart(12)} ${networkConfig.nativeSymbol} ${totalDiffColored}`);
514
602
  printSeparator('โ•');
603
+ printSponsorHint();
515
604
  console.log();
516
605
  }
517
606
 
@@ -938,6 +1027,9 @@ async function main() {
938
1027
  console.log(`${c('yellow')}โš ๏ธ Alert: threshold ${which} triggered${c('reset')}\n`);
939
1028
  }
940
1029
  }
1030
+
1031
+ // Send webhook if configured and alert triggered
1032
+ await maybeNotifyWebhook(output, anyAlertTriggered);
941
1033
 
942
1034
  process.exit(anyAlertTriggered ? EXIT_DIFF : EXIT_OK);
943
1035
  }
@@ -999,6 +1091,9 @@ async function main() {
999
1091
  }
1000
1092
  }
1001
1093
 
1094
+ // Send webhook if configured and alert triggered
1095
+ await maybeNotifyWebhook(output, alertTriggered);
1096
+
1002
1097
  // Exit with appropriate code
1003
1098
  process.exit(alertTriggered ? EXIT_DIFF : EXIT_OK);
1004
1099
 
@@ -155,3 +155,6 @@ module.exports = {
155
155
  formatBalanceDiff,
156
156
  };
157
157
 
158
+
159
+
160
+