multi-chain-balance-diff 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 +332 -0
- package/package.json +52 -0
- package/schema/mcbd-output.schema.json +297 -0
- package/src/adapters/baseAdapter.js +121 -0
- package/src/adapters/evmAdapter.js +126 -0
- package/src/adapters/index.js +70 -0
- package/src/adapters/solanaAdapter.js +179 -0
- package/src/config/networks.js +234 -0
- package/src/index.js +1031 -0
- package/src/services/balanceService.js +156 -0
package/README.md
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
# multi-chain-balance-diff
|
|
2
|
+
|
|
3
|
+
[](https://github.com/metawake/multi-chain-balance-diff/actions/workflows/test.yml)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
**Compare wallet balances across EVM and Solana chains. Returns structured diffs.**
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
mcbd --address 0x... --network mainnet --json | jq '.native.diff'
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## What
|
|
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.
|
|
15
|
+
|
|
16
|
+
## Who
|
|
17
|
+
|
|
18
|
+
Infrastructure engineers, protocol developers, and operators who need scriptable balance checks without spinning up dashboards or paying for analytics platforms.
|
|
19
|
+
|
|
20
|
+
## Why
|
|
21
|
+
|
|
22
|
+
Portfolio trackers are UI-only. Analytics platforms are expensive and slow. Raw RPC calls require boilerplate. This fills the gap: a `diff`-like primitive for on-chain balances that fits into shell scripts, cron jobs, and CI pipelines.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Use Cases
|
|
27
|
+
|
|
28
|
+
### 1. Operations: Treasury monitoring
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Cron job: alert ops channel if treasury balance drops
|
|
32
|
+
mcbd --address 0xTreasury --network mainnet --json \
|
|
33
|
+
| jq -e '.native.diffSign == "negative"' \
|
|
34
|
+
&& curl -X POST $SLACK_WEBHOOK -d '{"text":"Treasury balance decreased"}'
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. CI/Monitoring: Post-deployment sanity check
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# GitHub Actions: fail pipeline if fee wallet drained unexpectedly
|
|
41
|
+
mcbd --address $FEE_WALLET --network base --alert-if-diff "<-0.1"
|
|
42
|
+
# exit 0 = OK, exit 1 = threshold breached, exit 2 = RPC error
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 3. DeFi/Rewards: Staking reward accrual verification
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Verify staking rewards are accruing over 1000 blocks
|
|
49
|
+
mcbd --address $STAKER --network mainnet --blocks 1000 --json \
|
|
50
|
+
| jq '.native.diff'
|
|
51
|
+
# Expected: positive value if rewards distributed
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Install
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install -g multi-chain-balance-diff
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**One-shot (no install):**
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npx multi-chain-balance-diff -a 0x... -n mainnet --json
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Quickstart (60 seconds)
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# 1. Check a wallet balance diff (uses public RPC)
|
|
74
|
+
mcbd --address 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --network mainnet
|
|
75
|
+
|
|
76
|
+
# 2. Get JSON output, extract the diff
|
|
77
|
+
mcbd -a 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 -n mainnet --json | jq '.native.diff'
|
|
78
|
+
|
|
79
|
+
# 3. CI check: exit 1 if balance dropped more than 0.1 ETH
|
|
80
|
+
mcbd -a 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 -n mainnet --alert-if-diff "<-0.1"
|
|
81
|
+
echo "Exit code: $?"
|
|
82
|
+
# 0 = OK, 1 = threshold triggered, 2 = RPC error
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**One-liner for npx (no install):**
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npx multi-chain-balance-diff -a 0x... -n mainnet --json
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Usage
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
mcbd --address 0x... --network mainnet # pretty output
|
|
97
|
+
mcbd --address 0x... --json # structured output
|
|
98
|
+
mcbd --address 0x... --blocks 1000 # custom lookback
|
|
99
|
+
mcbd --address 0x... --watch --interval 30 # continuous monitoring
|
|
100
|
+
mcbd --addresses 0xA,0xB,0xC --network base # batch
|
|
101
|
+
mcbd --address 0x... --alert-if-diff ">0.01" # CI threshold
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Networks
|
|
105
|
+
|
|
106
|
+
| Key | Type | Native | Tokens |
|
|
107
|
+
|-----|------|--------|--------|
|
|
108
|
+
| `mainnet` | EVM | ETH | USDC, USDT, stETH |
|
|
109
|
+
| `polygon` | EVM | MATIC | USDC, USDT, WETH |
|
|
110
|
+
| `base` | EVM | ETH | USDC, cbETH, DAI |
|
|
111
|
+
| `arbitrum` | EVM | ETH | USDC, ARB, GMX |
|
|
112
|
+
| `optimism` | EVM | ETH | USDC, OP, SNX |
|
|
113
|
+
| `solana` | Solana | SOL | USDC, BONK, JUP |
|
|
114
|
+
| `helium` | Solana | SOL | HNT, MOBILE, IOT, DC |
|
|
115
|
+
|
|
116
|
+
## Options
|
|
117
|
+
|
|
118
|
+
| Flag | Description |
|
|
119
|
+
|------|-------------|
|
|
120
|
+
| `-a, --address` | Wallet address |
|
|
121
|
+
| `-A, --addresses` | Multiple addresses (comma-sep or file path) |
|
|
122
|
+
| `-n, --network` | Target network (default: `mainnet`) |
|
|
123
|
+
| `-b, --blocks` | Lookback depth (default: `50`) |
|
|
124
|
+
| `-w, --watch` | Continuous monitoring mode |
|
|
125
|
+
| `-i, --interval` | Watch interval in seconds (default: `30`) |
|
|
126
|
+
| `-c, --count` | Exit after N polls (watch mode) |
|
|
127
|
+
| `--exit-on-error` | Exit immediately on RPC failure (watch mode) |
|
|
128
|
+
| `--exit-on-diff` | Exit immediately when threshold triggers (watch mode) |
|
|
129
|
+
| `--json` | JSON output |
|
|
130
|
+
| `--no-tokens` | Skip ERC-20/SPL token checks |
|
|
131
|
+
| `--alert-if-diff` | Exit 1 if diff matches condition (e.g., `">0.01"`, `"<-1"`) |
|
|
132
|
+
| `--alert-pct` | Exit 1 if diff exceeds % of balance (e.g., `">5"`, `"<-10"`) |
|
|
133
|
+
|
|
134
|
+
**Exit codes:** `0` OK · `1` diff triggered · `2` RPC failure · `130` SIGINT
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Exit Codes & Batch Semantics
|
|
139
|
+
|
|
140
|
+
| Code | Meaning | When |
|
|
141
|
+
|------|---------|------|
|
|
142
|
+
| `0` | OK | No threshold triggered, all queries succeeded |
|
|
143
|
+
| `1` | Diff triggered | Threshold condition matched (any address) |
|
|
144
|
+
| `2` | RPC error | Network/connection failure |
|
|
145
|
+
| `130` | SIGINT | User interrupted (Ctrl+C) |
|
|
146
|
+
|
|
147
|
+
**Batch mode (`--addresses`)**: Returns exit `1` if *any* address triggers the threshold. Partial failures are included in JSON output with `error` field per address; successful queries still return data.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Common Failure Modes
|
|
152
|
+
|
|
153
|
+
### RPC Rate Limits
|
|
154
|
+
|
|
155
|
+
Public RPCs have strict rate limits. Symptoms: `429 Too Many Requests` or slow responses.
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
# Solution: Use a private RPC (Alchemy, Infura, QuickNode)
|
|
159
|
+
export ETH_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Unavailable Chain / RPC Down
|
|
163
|
+
|
|
164
|
+
```json
|
|
165
|
+
{"schemaVersion":"0.1.0","error":"connect ECONNREFUSED 127.0.0.1:8545","code":"ECONNREFUSED","exitCode":2}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Exit code `2` indicates RPC failure. Check network connectivity and RPC URL validity.
|
|
169
|
+
|
|
170
|
+
### Invalid Address Format
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
$ mcbd --address invalid --network mainnet --json
|
|
174
|
+
{"schemaVersion":"0.1.0","error":"Invalid EVM address: invalid"}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
EVM addresses must be `0x` + 40 hex chars. Solana addresses are base58 encoded.
|
|
178
|
+
|
|
179
|
+
### Missing Environment Variables
|
|
180
|
+
|
|
181
|
+
If using custom RPC endpoints via env vars, ensure they're set before running:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
# Check if RPC env is set
|
|
185
|
+
[ -z "$ETH_RPC_URL" ] && echo "Warning: Using public RPC (rate limited)"
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Watch Mode for CI/Cron
|
|
191
|
+
|
|
192
|
+
Watch mode is designed for long-running monitoring, cron jobs, and CI pipelines.
|
|
193
|
+
|
|
194
|
+
### Patterns
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
# One-shot: poll once, exit on threshold
|
|
198
|
+
mcbd -a $ADDR -n base --count 1 --alert-if-diff ">0.01" --json
|
|
199
|
+
|
|
200
|
+
# CI health check: 5 polls, fail fast on error or diff
|
|
201
|
+
mcbd -a $ADDR -n mainnet --watch --count 5 \
|
|
202
|
+
--exit-on-error --exit-on-diff --alert-pct ">10" --json
|
|
203
|
+
|
|
204
|
+
# Cron monitor: 10 polls over 5 minutes, log NDJSON
|
|
205
|
+
mcbd -a $ADDR -n solana --watch --interval 30 --count 10 --json >> /var/log/balance.ndjson
|
|
206
|
+
|
|
207
|
+
# Infinite watch with alerts (SIGINT to stop)
|
|
208
|
+
mcbd -a $ADDR -n polygon --watch --interval 60 --alert-if-diff "<-1"
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Example Output
|
|
212
|
+
|
|
213
|
+
**Normal operation (no diff):**
|
|
214
|
+
|
|
215
|
+
```json
|
|
216
|
+
{
|
|
217
|
+
"schemaVersion": "0.1.0",
|
|
218
|
+
"network": { "key": "mainnet", "name": "Ethereum Mainnet", "chainType": "evm", "chainId": 1 },
|
|
219
|
+
"address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
|
|
220
|
+
"explorer": "https://etherscan.io/address/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
|
|
221
|
+
"block": { "current": 19234567, "previous": 19234517 },
|
|
222
|
+
"native": {
|
|
223
|
+
"symbol": "ETH",
|
|
224
|
+
"decimals": 18,
|
|
225
|
+
"balance": "1.234567",
|
|
226
|
+
"balanceRaw": "1234567000000000000",
|
|
227
|
+
"diff": "0",
|
|
228
|
+
"diffRaw": "0",
|
|
229
|
+
"diffSign": "positive"
|
|
230
|
+
},
|
|
231
|
+
"tokens": [],
|
|
232
|
+
"timestamp": "2025-01-15T10:30:00.000Z"
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Exit: `0`
|
|
237
|
+
|
|
238
|
+
**Diff detected (threshold triggered):**
|
|
239
|
+
|
|
240
|
+
```json
|
|
241
|
+
{
|
|
242
|
+
"schemaVersion": "0.1.0",
|
|
243
|
+
"network": { "key": "base", "name": "Base", "chainType": "evm", "chainId": 8453 },
|
|
244
|
+
"address": "0xTreasury...",
|
|
245
|
+
"block": { "current": 8765432, "previous": 8765382 },
|
|
246
|
+
"native": {
|
|
247
|
+
"symbol": "ETH",
|
|
248
|
+
"balance": "10.5",
|
|
249
|
+
"diff": "0.05",
|
|
250
|
+
"diffRaw": "50000000000000000",
|
|
251
|
+
"diffSign": "positive"
|
|
252
|
+
},
|
|
253
|
+
"alert": {
|
|
254
|
+
"threshold": ">0.01",
|
|
255
|
+
"thresholdPct": null,
|
|
256
|
+
"triggered": true,
|
|
257
|
+
"triggeredBy": "absolute"
|
|
258
|
+
},
|
|
259
|
+
"timestamp": "2025-01-15T10:30:00.000Z"
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Exit: `1`
|
|
264
|
+
|
|
265
|
+
**Error state (RPC unavailable):**
|
|
266
|
+
|
|
267
|
+
```json
|
|
268
|
+
{
|
|
269
|
+
"schemaVersion": "0.1.0",
|
|
270
|
+
"error": "connect ECONNREFUSED 127.0.0.1:8545",
|
|
271
|
+
"code": "ECONNREFUSED",
|
|
272
|
+
"exitCode": 2
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Exit: `2`
|
|
277
|
+
|
|
278
|
+
**Watch mode NDJSON stream:**
|
|
279
|
+
|
|
280
|
+
```json
|
|
281
|
+
{"schemaVersion":"0.1.0","type":"watch_start","timestamp":"2025-01-15T10:30:00.000Z","network":"mainnet","address":"0x...","interval":30,"count":3}
|
|
282
|
+
{"schemaVersion":"0.1.0","timestamp":"2025-01-15T10:30:00.500Z","address":"0x...","block":19234567,"balance":"1.234","diff":"0","alert":false,"poll":1}
|
|
283
|
+
{"schemaVersion":"0.1.0","timestamp":"2025-01-15T10:30:30.500Z","address":"0x...","block":19234569,"balance":"1.234","diff":"0","alert":false,"poll":2}
|
|
284
|
+
{"schemaVersion":"0.1.0","timestamp":"2025-01-15T10:31:00.500Z","address":"0x...","block":19234571,"balance":"1.235","diff":"0.001","alert":true,"poll":3}
|
|
285
|
+
{"schemaVersion":"0.1.0","type":"watch_end","timestamp":"2025-01-15T10:31:00.600Z","polls":3,"exitCode":1}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Extending
|
|
291
|
+
|
|
292
|
+
```
|
|
293
|
+
src/
|
|
294
|
+
├── index.js # CLI
|
|
295
|
+
├── config/networks.js # Chain definitions
|
|
296
|
+
├── adapters/
|
|
297
|
+
│ ├── baseAdapter.js # Interface
|
|
298
|
+
│ ├── evmAdapter.js # ethers.js
|
|
299
|
+
│ └── solanaAdapter.js # @solana/web3.js
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Add a chain: implement `BaseAdapter`, add to `networks.js`, register in `adapters/index.js`.
|
|
303
|
+
|
|
304
|
+
## JSON Schema
|
|
305
|
+
|
|
306
|
+
JSON output includes a `schemaVersion` field (e.g., `"0.1.0"`). Schema changes are versioned:
|
|
307
|
+
|
|
308
|
+
- **Patch**: Documentation, new optional fields
|
|
309
|
+
- **Minor**: New output types, additive changes
|
|
310
|
+
- **Major**: Breaking changes to existing fields
|
|
311
|
+
|
|
312
|
+
See [`schema/mcbd-output.schema.json`](./schema/mcbd-output.schema.json) for the full specification.
|
|
313
|
+
|
|
314
|
+
## Stability
|
|
315
|
+
|
|
316
|
+
- JSON schema is versioned. Breaking changes = major version bump.
|
|
317
|
+
- Adapter interface is stable. New chains can be added without breaking existing integrations.
|
|
318
|
+
- See [CHANGELOG.md](./CHANGELOG.md) for release history.
|
|
319
|
+
|
|
320
|
+
## License
|
|
321
|
+
|
|
322
|
+
MIT
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
<!--
|
|
327
|
+
GitHub repo description (1 sentence):
|
|
328
|
+
CLI tool to fetch and diff wallet balances across EVM and Solana chains. JSON output, CI-friendly exit codes.
|
|
329
|
+
|
|
330
|
+
GitHub topics:
|
|
331
|
+
ethereum, solana, blockchain, cli, devops, monitoring, web3, defi
|
|
332
|
+
-->
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "multi-chain-balance-diff",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI tool to fetch and compare wallet balances across EVM and Solana chains",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"multi-chain-balance-diff": "./src/index.js",
|
|
8
|
+
"mcbd": "./src/index.js",
|
|
9
|
+
"balance-diff": "./src/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"src/",
|
|
13
|
+
"schema/"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"start": "node src/index.js",
|
|
17
|
+
"test": "node --test 'tests/*.test.js'"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"ethereum",
|
|
21
|
+
"solana",
|
|
22
|
+
"web3",
|
|
23
|
+
"balance",
|
|
24
|
+
"multi-chain",
|
|
25
|
+
"cli",
|
|
26
|
+
"staking",
|
|
27
|
+
"defi",
|
|
28
|
+
"helium",
|
|
29
|
+
"crypto",
|
|
30
|
+
"wallet"
|
|
31
|
+
],
|
|
32
|
+
"author": "",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/metawake/multi-chain-balance-diff.git"
|
|
37
|
+
},
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/metawake/multi-chain-balance-diff/issues"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/metawake/multi-chain-balance-diff#readme",
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18.0.0"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@solana/web3.js": "^1.95.0",
|
|
47
|
+
"commander": "^12.1.0",
|
|
48
|
+
"dotenv": "^16.4.5",
|
|
49
|
+
"ethers": "^6.13.4"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://github.com/metawake/multi-chain-balance-diff/schema/mcbd-output.schema.json",
|
|
4
|
+
"title": "multi-chain-balance-diff Output Schema",
|
|
5
|
+
"description": "JSON output schema for the mcbd CLI tool",
|
|
6
|
+
|
|
7
|
+
"oneOf": [
|
|
8
|
+
{ "$ref": "#/definitions/BalanceResult" },
|
|
9
|
+
{ "$ref": "#/definitions/MultiAddressResult" },
|
|
10
|
+
{ "$ref": "#/definitions/ErrorResult" },
|
|
11
|
+
{ "$ref": "#/definitions/WatchStart" },
|
|
12
|
+
{ "$ref": "#/definitions/WatchPoll" },
|
|
13
|
+
{ "$ref": "#/definitions/WatchEnd" }
|
|
14
|
+
],
|
|
15
|
+
|
|
16
|
+
"definitions": {
|
|
17
|
+
"BalanceResult": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"description": "Single address balance diff result",
|
|
20
|
+
"required": ["schemaVersion", "network", "address", "native", "timestamp"],
|
|
21
|
+
"properties": {
|
|
22
|
+
"schemaVersion": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"description": "Schema version (semver)",
|
|
25
|
+
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
|
26
|
+
},
|
|
27
|
+
"network": { "$ref": "#/definitions/NetworkInfo" },
|
|
28
|
+
"address": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"description": "Wallet address"
|
|
31
|
+
},
|
|
32
|
+
"explorer": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"format": "uri",
|
|
35
|
+
"description": "Block explorer URL for address"
|
|
36
|
+
},
|
|
37
|
+
"block": { "$ref": "#/definitions/BlockRange" },
|
|
38
|
+
"slot": { "$ref": "#/definitions/BlockRange" },
|
|
39
|
+
"native": { "$ref": "#/definitions/NativeBalance" },
|
|
40
|
+
"tokens": {
|
|
41
|
+
"type": "array",
|
|
42
|
+
"items": { "$ref": "#/definitions/TokenBalance" }
|
|
43
|
+
},
|
|
44
|
+
"alert": { "$ref": "#/definitions/AlertInfo" },
|
|
45
|
+
"timestamp": {
|
|
46
|
+
"type": "string",
|
|
47
|
+
"format": "date-time"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
"MultiAddressResult": {
|
|
53
|
+
"type": "object",
|
|
54
|
+
"description": "Multi-address batch result",
|
|
55
|
+
"required": ["schemaVersion", "network", "addresses", "summary", "timestamp"],
|
|
56
|
+
"properties": {
|
|
57
|
+
"schemaVersion": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
|
60
|
+
},
|
|
61
|
+
"network": { "$ref": "#/definitions/NetworkInfo" },
|
|
62
|
+
"addresses": {
|
|
63
|
+
"type": "array",
|
|
64
|
+
"items": { "$ref": "#/definitions/BalanceResult" }
|
|
65
|
+
},
|
|
66
|
+
"summary": {
|
|
67
|
+
"type": "object",
|
|
68
|
+
"properties": {
|
|
69
|
+
"totalAddresses": { "type": "integer" },
|
|
70
|
+
"successCount": { "type": "integer" },
|
|
71
|
+
"errorCount": { "type": "integer" }
|
|
72
|
+
},
|
|
73
|
+
"required": ["totalAddresses", "successCount", "errorCount"]
|
|
74
|
+
},
|
|
75
|
+
"alert": { "$ref": "#/definitions/AlertInfo" },
|
|
76
|
+
"timestamp": {
|
|
77
|
+
"type": "string",
|
|
78
|
+
"format": "date-time"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
"ErrorResult": {
|
|
84
|
+
"type": "object",
|
|
85
|
+
"description": "Error response",
|
|
86
|
+
"required": ["schemaVersion", "error"],
|
|
87
|
+
"properties": {
|
|
88
|
+
"schemaVersion": {
|
|
89
|
+
"type": "string",
|
|
90
|
+
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
|
91
|
+
},
|
|
92
|
+
"error": {
|
|
93
|
+
"type": "string",
|
|
94
|
+
"description": "Error message"
|
|
95
|
+
},
|
|
96
|
+
"code": {
|
|
97
|
+
"type": ["string", "null"],
|
|
98
|
+
"description": "Error code (e.g., ECONNREFUSED)"
|
|
99
|
+
},
|
|
100
|
+
"exitCode": {
|
|
101
|
+
"type": "integer",
|
|
102
|
+
"description": "Process exit code"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
"WatchStart": {
|
|
108
|
+
"type": "object",
|
|
109
|
+
"description": "Watch mode start event (NDJSON)",
|
|
110
|
+
"required": ["schemaVersion", "type", "timestamp", "network", "address", "interval"],
|
|
111
|
+
"properties": {
|
|
112
|
+
"schemaVersion": {
|
|
113
|
+
"type": "string",
|
|
114
|
+
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
|
115
|
+
},
|
|
116
|
+
"type": {
|
|
117
|
+
"const": "watch_start"
|
|
118
|
+
},
|
|
119
|
+
"timestamp": {
|
|
120
|
+
"type": "string",
|
|
121
|
+
"format": "date-time"
|
|
122
|
+
},
|
|
123
|
+
"network": { "type": "string" },
|
|
124
|
+
"address": { "type": "string" },
|
|
125
|
+
"interval": { "type": "integer" },
|
|
126
|
+
"count": { "type": ["integer", "null"] },
|
|
127
|
+
"threshold": { "type": ["string", "null"] },
|
|
128
|
+
"thresholdPct": { "type": ["string", "null"] }
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
"WatchPoll": {
|
|
133
|
+
"type": "object",
|
|
134
|
+
"description": "Watch mode poll event (NDJSON)",
|
|
135
|
+
"required": ["schemaVersion", "timestamp", "address"],
|
|
136
|
+
"properties": {
|
|
137
|
+
"schemaVersion": {
|
|
138
|
+
"type": "string",
|
|
139
|
+
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
|
140
|
+
},
|
|
141
|
+
"timestamp": {
|
|
142
|
+
"type": "string",
|
|
143
|
+
"format": "date-time"
|
|
144
|
+
},
|
|
145
|
+
"address": { "type": "string" },
|
|
146
|
+
"block": { "type": "integer" },
|
|
147
|
+
"slot": { "type": "integer" },
|
|
148
|
+
"balance": { "type": "string" },
|
|
149
|
+
"balanceRaw": { "type": "string" },
|
|
150
|
+
"diff": { "type": "string" },
|
|
151
|
+
"diffRaw": { "type": "string" },
|
|
152
|
+
"diffSign": {
|
|
153
|
+
"type": "string",
|
|
154
|
+
"enum": ["positive", "negative"]
|
|
155
|
+
},
|
|
156
|
+
"alert": { "type": "boolean" },
|
|
157
|
+
"poll": { "type": "integer" },
|
|
158
|
+
"error": { "type": "string" },
|
|
159
|
+
"isRpcError": { "type": "boolean" }
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
"WatchEnd": {
|
|
164
|
+
"type": "object",
|
|
165
|
+
"description": "Watch mode end event (NDJSON)",
|
|
166
|
+
"required": ["schemaVersion", "type", "timestamp", "polls", "exitCode"],
|
|
167
|
+
"properties": {
|
|
168
|
+
"schemaVersion": {
|
|
169
|
+
"type": "string",
|
|
170
|
+
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
|
171
|
+
},
|
|
172
|
+
"type": {
|
|
173
|
+
"const": "watch_end"
|
|
174
|
+
},
|
|
175
|
+
"timestamp": {
|
|
176
|
+
"type": "string",
|
|
177
|
+
"format": "date-time"
|
|
178
|
+
},
|
|
179
|
+
"polls": { "type": "integer" },
|
|
180
|
+
"exitCode": { "type": "integer" },
|
|
181
|
+
"reason": { "type": "string" }
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
"NetworkInfo": {
|
|
186
|
+
"type": "object",
|
|
187
|
+
"required": ["key", "name", "chainType"],
|
|
188
|
+
"properties": {
|
|
189
|
+
"key": {
|
|
190
|
+
"type": "string",
|
|
191
|
+
"description": "Network identifier (e.g., mainnet, polygon, solana)"
|
|
192
|
+
},
|
|
193
|
+
"name": {
|
|
194
|
+
"type": "string",
|
|
195
|
+
"description": "Human-readable network name"
|
|
196
|
+
},
|
|
197
|
+
"chainType": {
|
|
198
|
+
"type": "string",
|
|
199
|
+
"enum": ["evm", "solana"]
|
|
200
|
+
},
|
|
201
|
+
"chainId": {
|
|
202
|
+
"type": "integer",
|
|
203
|
+
"description": "EVM chain ID (null for Solana)"
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
"BlockRange": {
|
|
209
|
+
"type": "object",
|
|
210
|
+
"required": ["current", "previous"],
|
|
211
|
+
"properties": {
|
|
212
|
+
"current": {
|
|
213
|
+
"type": "integer",
|
|
214
|
+
"description": "Current block/slot number"
|
|
215
|
+
},
|
|
216
|
+
"previous": {
|
|
217
|
+
"type": "integer",
|
|
218
|
+
"description": "Previous block/slot for diff comparison"
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
"NativeBalance": {
|
|
224
|
+
"type": "object",
|
|
225
|
+
"required": ["symbol", "decimals", "balance", "balanceRaw", "diff", "diffRaw", "diffSign"],
|
|
226
|
+
"properties": {
|
|
227
|
+
"symbol": {
|
|
228
|
+
"type": "string",
|
|
229
|
+
"description": "Native token symbol (ETH, MATIC, SOL, etc.)"
|
|
230
|
+
},
|
|
231
|
+
"decimals": {
|
|
232
|
+
"type": "integer",
|
|
233
|
+
"description": "Token decimals"
|
|
234
|
+
},
|
|
235
|
+
"balance": {
|
|
236
|
+
"type": "string",
|
|
237
|
+
"description": "Formatted current balance"
|
|
238
|
+
},
|
|
239
|
+
"balanceRaw": {
|
|
240
|
+
"type": "string",
|
|
241
|
+
"description": "Raw balance in smallest unit (wei, lamports, etc.)"
|
|
242
|
+
},
|
|
243
|
+
"diff": {
|
|
244
|
+
"type": "string",
|
|
245
|
+
"description": "Absolute value of balance difference (formatted)"
|
|
246
|
+
},
|
|
247
|
+
"diffRaw": {
|
|
248
|
+
"type": "string",
|
|
249
|
+
"description": "Raw diff value (signed)"
|
|
250
|
+
},
|
|
251
|
+
"diffSign": {
|
|
252
|
+
"type": "string",
|
|
253
|
+
"enum": ["positive", "negative"]
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
"TokenBalance": {
|
|
259
|
+
"type": "object",
|
|
260
|
+
"required": ["symbol", "decimals", "balance", "balanceRaw"],
|
|
261
|
+
"properties": {
|
|
262
|
+
"symbol": { "type": "string" },
|
|
263
|
+
"address": {
|
|
264
|
+
"type": "string",
|
|
265
|
+
"description": "Token contract address (EVM) or mint (Solana)"
|
|
266
|
+
},
|
|
267
|
+
"decimals": { "type": "integer" },
|
|
268
|
+
"balance": { "type": "string" },
|
|
269
|
+
"balanceRaw": { "type": "string" }
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
"AlertInfo": {
|
|
274
|
+
"type": "object",
|
|
275
|
+
"properties": {
|
|
276
|
+
"threshold": {
|
|
277
|
+
"type": ["string", "null"],
|
|
278
|
+
"description": "Absolute threshold (e.g., >0.01)"
|
|
279
|
+
},
|
|
280
|
+
"thresholdPct": {
|
|
281
|
+
"type": ["string", "null"],
|
|
282
|
+
"description": "Percentage threshold (e.g., >5)"
|
|
283
|
+
},
|
|
284
|
+
"triggered": {
|
|
285
|
+
"type": "boolean",
|
|
286
|
+
"description": "Whether alert condition was met"
|
|
287
|
+
},
|
|
288
|
+
"triggeredBy": {
|
|
289
|
+
"type": ["string", "null"],
|
|
290
|
+
"enum": ["absolute", "percentage", null]
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
"required": ["triggered"]
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|