multi-chain-balance-diff 0.1.0 → 0.1.2
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 +51 -14
- package/package.json +3 -3
- package/schema/mcbd-output.schema.json +1 -0
- package/src/adapters/baseAdapter.js +1 -0
- package/src/adapters/evmAdapter.js +13 -3
- package/src/adapters/index.js +9 -2
- package/src/adapters/solanaAdapter.js +32 -2
- package/src/adapters/tonAdapter.js +95 -0
- package/src/config/networks.js +89 -0
- package/src/index.js +16 -2
- package/src/services/balanceService.js +1 -0
package/README.md
CHANGED
|
@@ -110,8 +110,13 @@ mcbd --address 0x... --alert-if-diff ">0.01" # CI threshold
|
|
|
110
110
|
| `base` | EVM | ETH | USDC, cbETH, DAI |
|
|
111
111
|
| `arbitrum` | EVM | ETH | USDC, ARB, GMX |
|
|
112
112
|
| `optimism` | EVM | ETH | USDC, OP, SNX |
|
|
113
|
+
| `bnb` | EVM | BNB | USDT, USDC, BUSD |
|
|
114
|
+
| `avalanche` | EVM | AVAX | USDC, USDT |
|
|
115
|
+
| `fantom` | EVM | FTM | USDC, USDT, DAI |
|
|
116
|
+
| `zksync` | EVM | ETH | USDC, USDT |
|
|
113
117
|
| `solana` | Solana | SOL | USDC, BONK, JUP |
|
|
114
118
|
| `helium` | Solana | SOL | HNT, MOBILE, IOT, DC |
|
|
119
|
+
| `ton` | TON | TON | — |
|
|
115
120
|
|
|
116
121
|
## Options
|
|
117
122
|
|
|
@@ -130,8 +135,9 @@ mcbd --address 0x... --alert-if-diff ">0.01" # CI threshold
|
|
|
130
135
|
| `--no-tokens` | Skip ERC-20/SPL token checks |
|
|
131
136
|
| `--alert-if-diff` | Exit 1 if diff matches condition (e.g., `">0.01"`, `"<-1"`) |
|
|
132
137
|
| `--alert-pct` | Exit 1 if diff exceeds % of balance (e.g., `">5"`, `"<-10"`) |
|
|
138
|
+
| `--timeout` | RPC request timeout in seconds (default: `30`) |
|
|
133
139
|
|
|
134
|
-
**Exit codes:** `0` OK · `1` diff triggered · `2` RPC failure · `130` SIGINT
|
|
140
|
+
**Exit codes:** `0` OK · `1` diff triggered · `2` RPC failure/timeout · `130` SIGINT
|
|
135
141
|
|
|
136
142
|
---
|
|
137
143
|
|
|
@@ -148,6 +154,39 @@ mcbd --address 0x... --alert-if-diff ">0.01" # CI threshold
|
|
|
148
154
|
|
|
149
155
|
---
|
|
150
156
|
|
|
157
|
+
## Configuration
|
|
158
|
+
|
|
159
|
+
### Custom RPC Endpoints
|
|
160
|
+
|
|
161
|
+
Override default public RPCs with environment variables:
|
|
162
|
+
|
|
163
|
+
| Variable | Network |
|
|
164
|
+
|----------|---------|
|
|
165
|
+
| `RPC_URL_ETH` | Ethereum Mainnet |
|
|
166
|
+
| `RPC_URL_POLYGON` | Polygon |
|
|
167
|
+
| `RPC_URL_BASE` | Base |
|
|
168
|
+
| `RPC_URL_ARBITRUM` | Arbitrum |
|
|
169
|
+
| `RPC_URL_OPTIMISM` | Optimism |
|
|
170
|
+
| `RPC_URL_SOLANA` | Solana / Helium |
|
|
171
|
+
| `RPC_URL_SEPOLIA` | Sepolia testnet |
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
# Use private RPC for reliability
|
|
175
|
+
export RPC_URL_ETH=https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY
|
|
176
|
+
mcbd -a 0x... -n mainnet --json
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Timeout
|
|
180
|
+
|
|
181
|
+
Default timeout is 30 seconds. Adjust for slow or unreliable RPCs:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
mcbd -a 0x... -n mainnet --timeout 60 # 60 seconds
|
|
185
|
+
mcbd -a 0x... -n solana --timeout 10 # 10 seconds (fast-fail)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
151
190
|
## Common Failure Modes
|
|
152
191
|
|
|
153
192
|
### RPC Rate Limits
|
|
@@ -155,17 +194,24 @@ mcbd --address 0x... --alert-if-diff ">0.01" # CI threshold
|
|
|
155
194
|
Public RPCs have strict rate limits. Symptoms: `429 Too Many Requests` or slow responses.
|
|
156
195
|
|
|
157
196
|
```bash
|
|
158
|
-
# Solution: Use a private RPC
|
|
159
|
-
export
|
|
197
|
+
# Solution: Use a private RPC
|
|
198
|
+
export RPC_URL_ETH=https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Timeout / Slow RPC
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
# Increase timeout for slow networks
|
|
205
|
+
mcbd -a 0x... -n mainnet --timeout 60
|
|
160
206
|
```
|
|
161
207
|
|
|
162
208
|
### Unavailable Chain / RPC Down
|
|
163
209
|
|
|
164
210
|
```json
|
|
165
|
-
{"schemaVersion":"0.1.0","error":"connect ECONNREFUSED
|
|
211
|
+
{"schemaVersion":"0.1.0","error":"connect ECONNREFUSED","code":"ECONNREFUSED","exitCode":2}
|
|
166
212
|
```
|
|
167
213
|
|
|
168
|
-
Exit code `2` indicates RPC failure. Check network connectivity and RPC URL
|
|
214
|
+
Exit code `2` indicates RPC failure. Check network connectivity and RPC URL.
|
|
169
215
|
|
|
170
216
|
### Invalid Address Format
|
|
171
217
|
|
|
@@ -176,15 +222,6 @@ $ mcbd --address invalid --network mainnet --json
|
|
|
176
222
|
|
|
177
223
|
EVM addresses must be `0x` + 40 hex chars. Solana addresses are base58 encoded.
|
|
178
224
|
|
|
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
225
|
---
|
|
189
226
|
|
|
190
227
|
## Watch Mode for CI/Cron
|
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.2",
|
|
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": {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
],
|
|
15
15
|
"scripts": {
|
|
16
16
|
"start": "node src/index.js",
|
|
17
|
-
"test": "node --test
|
|
17
|
+
"test": "node --test tests/*.test.js"
|
|
18
18
|
},
|
|
19
19
|
"keywords": [
|
|
20
20
|
"ethereum",
|
|
@@ -44,9 +44,9 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@solana/web3.js": "^1.95.0",
|
|
47
|
+
"@ton/ton": "^15.4.0",
|
|
47
48
|
"commander": "^12.1.0",
|
|
48
49
|
"dotenv": "^16.4.5",
|
|
49
50
|
"ethers": "^6.13.4"
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
|
-
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* all compatible networks.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
const { ethers } = require('ethers');
|
|
9
|
+
const { ethers, FetchRequest } = require('ethers');
|
|
10
10
|
const BaseAdapter = require('./baseAdapter');
|
|
11
11
|
|
|
12
12
|
// Minimal ERC-20 ABI for balance queries
|
|
@@ -16,10 +16,14 @@ const ERC20_ABI = [
|
|
|
16
16
|
'function symbol() view returns (string)',
|
|
17
17
|
];
|
|
18
18
|
|
|
19
|
+
// Default timeout: 30 seconds
|
|
20
|
+
const DEFAULT_TIMEOUT_MS = 30000;
|
|
21
|
+
|
|
19
22
|
class EVMAdapter extends BaseAdapter {
|
|
20
|
-
constructor(networkConfig) {
|
|
23
|
+
constructor(networkConfig, options = {}) {
|
|
21
24
|
super(networkConfig);
|
|
22
25
|
this.provider = null;
|
|
26
|
+
this.timeoutMs = options.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
getChainType() {
|
|
@@ -27,7 +31,11 @@ class EVMAdapter extends BaseAdapter {
|
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
async connect() {
|
|
30
|
-
|
|
34
|
+
// Create FetchRequest with timeout
|
|
35
|
+
const fetchRequest = new FetchRequest(this.networkConfig.rpcUrl);
|
|
36
|
+
fetchRequest.timeout = this.timeoutMs;
|
|
37
|
+
|
|
38
|
+
this.provider = new ethers.JsonRpcProvider(fetchRequest);
|
|
31
39
|
// Verify connection by fetching network
|
|
32
40
|
await this.provider.getNetwork();
|
|
33
41
|
this.connection = this.provider;
|
|
@@ -124,3 +132,5 @@ class EVMAdapter extends BaseAdapter {
|
|
|
124
132
|
}
|
|
125
133
|
|
|
126
134
|
module.exports = EVMAdapter;
|
|
135
|
+
|
|
136
|
+
|
package/src/adapters/index.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
const EVMAdapter = require('./evmAdapter');
|
|
14
14
|
const SolanaAdapter = require('./solanaAdapter');
|
|
15
|
+
const TonAdapter = require('./tonAdapter');
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Chain type to adapter class mapping.
|
|
@@ -19,16 +20,19 @@ const SolanaAdapter = require('./solanaAdapter');
|
|
|
19
20
|
const ADAPTER_MAP = {
|
|
20
21
|
evm: EVMAdapter,
|
|
21
22
|
solana: SolanaAdapter,
|
|
23
|
+
ton: TonAdapter,
|
|
22
24
|
};
|
|
23
25
|
|
|
24
26
|
/**
|
|
25
27
|
* Create an adapter for the given network configuration.
|
|
26
28
|
*
|
|
27
29
|
* @param {object} networkConfig - Network configuration from networks.js
|
|
30
|
+
* @param {object} options - Adapter options
|
|
31
|
+
* @param {number} options.timeoutMs - RPC timeout in milliseconds (default: 30000)
|
|
28
32
|
* @returns {BaseAdapter} Chain-specific adapter instance
|
|
29
33
|
* @throws {Error} If chain type is not supported
|
|
30
34
|
*/
|
|
31
|
-
function createAdapter(networkConfig) {
|
|
35
|
+
function createAdapter(networkConfig, options = {}) {
|
|
32
36
|
// Determine chain type (default to 'evm' for backwards compatibility)
|
|
33
37
|
const chainType = networkConfig.chainType || 'evm';
|
|
34
38
|
|
|
@@ -41,7 +45,7 @@ function createAdapter(networkConfig) {
|
|
|
41
45
|
);
|
|
42
46
|
}
|
|
43
47
|
|
|
44
|
-
return new AdapterClass(networkConfig);
|
|
48
|
+
return new AdapterClass(networkConfig, options);
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
/**
|
|
@@ -67,4 +71,7 @@ module.exports = {
|
|
|
67
71
|
isChainTypeSupported,
|
|
68
72
|
EVMAdapter,
|
|
69
73
|
SolanaAdapter,
|
|
74
|
+
TonAdapter,
|
|
70
75
|
};
|
|
76
|
+
|
|
77
|
+
|
|
@@ -18,20 +18,48 @@ const BaseAdapter = require('./baseAdapter');
|
|
|
18
18
|
// SPL Token Program ID
|
|
19
19
|
const TOKEN_PROGRAM_ID = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA');
|
|
20
20
|
|
|
21
|
+
// Default timeout: 30 seconds
|
|
22
|
+
const DEFAULT_TIMEOUT_MS = 30000;
|
|
23
|
+
|
|
21
24
|
class SolanaAdapter extends BaseAdapter {
|
|
22
|
-
constructor(networkConfig) {
|
|
25
|
+
constructor(networkConfig, options = {}) {
|
|
23
26
|
super(networkConfig);
|
|
24
27
|
this.conn = null;
|
|
28
|
+
this.timeoutMs = options.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
getChainType() {
|
|
28
32
|
return 'solana';
|
|
29
33
|
}
|
|
30
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Create a fetch function with timeout support.
|
|
37
|
+
*/
|
|
38
|
+
_createFetchWithTimeout() {
|
|
39
|
+
const timeoutMs = this.timeoutMs;
|
|
40
|
+
return async (url, options) => {
|
|
41
|
+
const controller = new AbortController();
|
|
42
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const response = await fetch(url, {
|
|
46
|
+
...options,
|
|
47
|
+
signal: controller.signal,
|
|
48
|
+
});
|
|
49
|
+
return response;
|
|
50
|
+
} finally {
|
|
51
|
+
clearTimeout(timeoutId);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
31
56
|
async connect() {
|
|
32
57
|
this.conn = new Connection(
|
|
33
58
|
this.networkConfig.rpcUrl,
|
|
34
|
-
{
|
|
59
|
+
{
|
|
60
|
+
commitment: 'confirmed',
|
|
61
|
+
fetch: this._createFetchWithTimeout(),
|
|
62
|
+
}
|
|
35
63
|
);
|
|
36
64
|
// Verify connection
|
|
37
65
|
await this.conn.getSlot();
|
|
@@ -177,3 +205,5 @@ class SolanaAdapter extends BaseAdapter {
|
|
|
177
205
|
}
|
|
178
206
|
|
|
179
207
|
module.exports = SolanaAdapter;
|
|
208
|
+
|
|
209
|
+
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TON (The Open Network) Adapter
|
|
3
|
+
*
|
|
4
|
+
* Handles TON blockchain balance queries.
|
|
5
|
+
* Uses @ton/ton SDK for RPC communication.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { TonClient, Address, fromNano } = require('@ton/ton');
|
|
9
|
+
const BaseAdapter = require('./baseAdapter');
|
|
10
|
+
|
|
11
|
+
// Default timeout: 30 seconds
|
|
12
|
+
const DEFAULT_TIMEOUT_MS = 30000;
|
|
13
|
+
|
|
14
|
+
class TonAdapter extends BaseAdapter {
|
|
15
|
+
constructor(networkConfig, options = {}) {
|
|
16
|
+
super(networkConfig);
|
|
17
|
+
this.client = null;
|
|
18
|
+
this.timeoutMs = options.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getChainType() {
|
|
22
|
+
return 'ton';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async connect() {
|
|
26
|
+
this.client = new TonClient({
|
|
27
|
+
endpoint: this.networkConfig.rpcUrl,
|
|
28
|
+
timeout: this.timeoutMs,
|
|
29
|
+
});
|
|
30
|
+
// Verify connection by getting masterchain info
|
|
31
|
+
await this.client.getMasterchainInfo();
|
|
32
|
+
this.connection = this.client;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async getCurrentBlock() {
|
|
36
|
+
const info = await this.client.getMasterchainInfo();
|
|
37
|
+
return info.last.seqno;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async getNativeBalance(address, blockTag = 'latest') {
|
|
41
|
+
const addr = Address.parse(address);
|
|
42
|
+
|
|
43
|
+
// TON doesn't easily support historical balance queries via standard API
|
|
44
|
+
// We fetch current balance
|
|
45
|
+
const balance = await this.client.getBalance(addr);
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
raw: balance,
|
|
49
|
+
formatted: this.formatBalance(balance, 9), // TON has 9 decimals
|
|
50
|
+
decimals: 9,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async getTokenBalances(address, tokens) {
|
|
55
|
+
// TON Jettons (tokens) require parsing wallet contract state
|
|
56
|
+
// This is more complex than EVM - simplified implementation
|
|
57
|
+
// Returns empty for now, can be extended later
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
isValidAddress(address) {
|
|
62
|
+
try {
|
|
63
|
+
Address.parse(address);
|
|
64
|
+
return true;
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
formatBalance(raw, decimals = 9) {
|
|
71
|
+
// fromNano converts from nanoTON to TON
|
|
72
|
+
return fromNano(raw);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
formatBalanceWithSymbol(raw, symbol, decimals = 9) {
|
|
76
|
+
return `${this.formatBalance(raw, decimals)} ${symbol}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
formatDiff(diff, symbol, decimals = 9) {
|
|
80
|
+
const formatted = this.formatBalance(diff < 0n ? -diff : diff, decimals);
|
|
81
|
+
const prefix = diff >= 0n ? '+' : '-';
|
|
82
|
+
return `${prefix}${formatted} ${symbol}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
getExplorerUrl(address) {
|
|
86
|
+
const isTestnet = this.networkConfig.rpcUrl.includes('testnet');
|
|
87
|
+
const baseUrl = isTestnet
|
|
88
|
+
? 'https://testnet.tonscan.org'
|
|
89
|
+
: 'https://tonscan.org';
|
|
90
|
+
return `${baseUrl}/address/${address}`;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = TonAdapter;
|
|
95
|
+
|
package/src/config/networks.js
CHANGED
|
@@ -118,6 +118,66 @@ const networks = {
|
|
|
118
118
|
],
|
|
119
119
|
},
|
|
120
120
|
|
|
121
|
+
bnb: {
|
|
122
|
+
name: 'BNB Chain',
|
|
123
|
+
chainType: 'evm',
|
|
124
|
+
chainId: 56,
|
|
125
|
+
rpcUrl: process.env.RPC_URL_BNB || 'https://bsc-dataseed.binance.org',
|
|
126
|
+
nativeSymbol: 'BNB',
|
|
127
|
+
nativeDecimals: 18,
|
|
128
|
+
blockExplorer: 'https://bscscan.com',
|
|
129
|
+
tokens: [
|
|
130
|
+
{ symbol: 'USDT', address: '0x55d398326f99059fF775485246999027B3197955', decimals: 18 },
|
|
131
|
+
{ symbol: 'USDC', address: '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', decimals: 18 },
|
|
132
|
+
{ symbol: 'BUSD', address: '0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56', decimals: 18 },
|
|
133
|
+
{ symbol: 'WBNB', address: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', decimals: 18 },
|
|
134
|
+
],
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
avalanche: {
|
|
138
|
+
name: 'Avalanche C-Chain',
|
|
139
|
+
chainType: 'evm',
|
|
140
|
+
chainId: 43114,
|
|
141
|
+
rpcUrl: process.env.RPC_URL_AVAX || 'https://api.avax.network/ext/bc/C/rpc',
|
|
142
|
+
nativeSymbol: 'AVAX',
|
|
143
|
+
nativeDecimals: 18,
|
|
144
|
+
blockExplorer: 'https://snowtrace.io',
|
|
145
|
+
tokens: [
|
|
146
|
+
{ symbol: 'USDC', address: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E', decimals: 6 },
|
|
147
|
+
{ symbol: 'USDT', address: '0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7', decimals: 6 },
|
|
148
|
+
{ symbol: 'WAVAX', address: '0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7', decimals: 18 },
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
fantom: {
|
|
153
|
+
name: 'Fantom Opera',
|
|
154
|
+
chainType: 'evm',
|
|
155
|
+
chainId: 250,
|
|
156
|
+
rpcUrl: process.env.RPC_URL_FTM || 'https://rpc.ftm.tools',
|
|
157
|
+
nativeSymbol: 'FTM',
|
|
158
|
+
nativeDecimals: 18,
|
|
159
|
+
blockExplorer: 'https://ftmscan.com',
|
|
160
|
+
tokens: [
|
|
161
|
+
{ symbol: 'USDC', address: '0x04068DA6C83AFCFA0e13ba15A6696662335D5B75', decimals: 6 },
|
|
162
|
+
{ symbol: 'USDT', address: '0x049d68029688eAbF473097a2fC38ef61633A3C7A', decimals: 6 },
|
|
163
|
+
{ symbol: 'DAI', address: '0x8D11eC38a3EB5E956B052f67Da8Bdc9bef8Abf3E', decimals: 18 },
|
|
164
|
+
],
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
zksync: {
|
|
168
|
+
name: 'zkSync Era',
|
|
169
|
+
chainType: 'evm',
|
|
170
|
+
chainId: 324,
|
|
171
|
+
rpcUrl: process.env.RPC_URL_ZKSYNC || 'https://mainnet.era.zksync.io',
|
|
172
|
+
nativeSymbol: 'ETH',
|
|
173
|
+
nativeDecimals: 18,
|
|
174
|
+
blockExplorer: 'https://explorer.zksync.io',
|
|
175
|
+
tokens: [
|
|
176
|
+
{ symbol: 'USDC', address: '0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4', decimals: 6 },
|
|
177
|
+
{ symbol: 'USDT', address: '0x493257fD37EDB34451f62EDf8D2a0C418852bA4C', decimals: 6 },
|
|
178
|
+
],
|
|
179
|
+
},
|
|
180
|
+
|
|
121
181
|
// ==========================================================================
|
|
122
182
|
// Solana Networks
|
|
123
183
|
// ==========================================================================
|
|
@@ -179,6 +239,34 @@ const networks = {
|
|
|
179
239
|
blockExplorer: 'https://explorer.solana.com',
|
|
180
240
|
tokens: [],
|
|
181
241
|
},
|
|
242
|
+
|
|
243
|
+
// ==========================================================================
|
|
244
|
+
// TON Networks
|
|
245
|
+
// ==========================================================================
|
|
246
|
+
|
|
247
|
+
ton: {
|
|
248
|
+
name: 'TON Mainnet',
|
|
249
|
+
chainType: 'ton',
|
|
250
|
+
chainId: null,
|
|
251
|
+
rpcUrl: process.env.RPC_URL_TON || 'https://toncenter.com/api/v2/jsonRPC',
|
|
252
|
+
nativeSymbol: 'TON',
|
|
253
|
+
nativeDecimals: 9,
|
|
254
|
+
blockExplorer: 'https://tonscan.org',
|
|
255
|
+
tokens: [
|
|
256
|
+
// Jettons can be added here later
|
|
257
|
+
],
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
'ton-testnet': {
|
|
261
|
+
name: 'TON Testnet',
|
|
262
|
+
chainType: 'ton',
|
|
263
|
+
chainId: null,
|
|
264
|
+
rpcUrl: process.env.RPC_URL_TON_TESTNET || 'https://testnet.toncenter.com/api/v2/jsonRPC',
|
|
265
|
+
nativeSymbol: 'TON',
|
|
266
|
+
nativeDecimals: 9,
|
|
267
|
+
blockExplorer: 'https://testnet.tonscan.org',
|
|
268
|
+
tokens: [],
|
|
269
|
+
},
|
|
182
270
|
};
|
|
183
271
|
|
|
184
272
|
// ==========================================================================
|
|
@@ -232,3 +320,4 @@ module.exports = {
|
|
|
232
320
|
getChainType,
|
|
233
321
|
};
|
|
234
322
|
|
|
323
|
+
|
package/src/index.js
CHANGED
|
@@ -58,6 +58,7 @@ program
|
|
|
58
58
|
.option('--config <path>', 'Path to config file')
|
|
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
|
+
.option('--timeout <seconds>', 'RPC request timeout in seconds', '30')
|
|
61
62
|
.parse(process.argv);
|
|
62
63
|
|
|
63
64
|
const options = program.opts();
|
|
@@ -325,6 +326,10 @@ function listNetworks() {
|
|
|
325
326
|
const net = getNetwork(key);
|
|
326
327
|
return { key, name: net.name, symbol: net.nativeSymbol };
|
|
327
328
|
}),
|
|
329
|
+
ton: getNetworksByType('ton').map(key => {
|
|
330
|
+
const net = getNetwork(key);
|
|
331
|
+
return { key, name: net.name, symbol: net.nativeSymbol };
|
|
332
|
+
}),
|
|
328
333
|
};
|
|
329
334
|
console.log(JSON.stringify(networks, null, 2));
|
|
330
335
|
return;
|
|
@@ -343,12 +348,18 @@ function listNetworks() {
|
|
|
343
348
|
const net = getNetwork(key);
|
|
344
349
|
console.log(` ${c('cyan')}${key.padEnd(12)}${c('reset')} ${net.name} (${net.nativeSymbol})`);
|
|
345
350
|
}
|
|
351
|
+
|
|
352
|
+
console.log('\n TON Chains:');
|
|
353
|
+
for (const key of getNetworksByType('ton')) {
|
|
354
|
+
const net = getNetwork(key);
|
|
355
|
+
console.log(` ${c('cyan')}${key.padEnd(12)}${c('reset')} ${net.name} (${net.nativeSymbol})`);
|
|
356
|
+
}
|
|
346
357
|
|
|
347
358
|
console.log('\nUsage:');
|
|
348
359
|
console.log(' mcbd --address <ADDR> --network mainnet');
|
|
349
360
|
console.log(' mcbd --address <ADDR> --network base');
|
|
350
361
|
console.log(' mcbd --address <ADDR> --network solana');
|
|
351
|
-
console.log(' mcbd --address <ADDR> --network
|
|
362
|
+
console.log(' mcbd --address <ADDR> --network ton --json\n');
|
|
352
363
|
}
|
|
353
364
|
|
|
354
365
|
// ==========================================================================
|
|
@@ -803,8 +814,11 @@ async function main() {
|
|
|
803
814
|
process.exit(1);
|
|
804
815
|
}
|
|
805
816
|
|
|
817
|
+
// Parse timeout (convert seconds to milliseconds)
|
|
818
|
+
const timeoutMs = parseInt(options.timeout, 10) * 1000;
|
|
819
|
+
|
|
806
820
|
// Create the appropriate adapter for this chain
|
|
807
|
-
const adapter = createAdapter(networkConfig);
|
|
821
|
+
const adapter = createAdapter(networkConfig, { timeoutMs });
|
|
808
822
|
|
|
809
823
|
// Validate all addresses before connecting (fail fast)
|
|
810
824
|
for (const addr of addresses) {
|