-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtreasury-tracker.js
More file actions
123 lines (105 loc) · 3.36 KB
/
treasury-tracker.js
File metadata and controls
123 lines (105 loc) · 3.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// Treasury Tracker - Fetches live balances from Polygon RPC
// Replaces Dune SQL queries with direct on-chain reads
// Original dashboard reference: https://dune.com/eitri/aavegotchi-dao-treasury
const { TREASURY_WALLETS, TRACKED_TOKENS } = require('./treasury-config');
const ERC20_BALANCE_OF_ABI = '0x70a08231';
async function getTokenBalance(rpcUrl, tokenAddress, walletAddress) {
const paddedWallet = walletAddress.toLowerCase().replace('0x', '').padStart(64, '0');
const data = ERC20_BALANCE_OF_ABI + paddedWallet;
const response = await fetch(rpcUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'eth_call',
params: [{ to: tokenAddress, data }, 'latest'],
}),
});
const result = await response.json();
if (result.error) {
throw new Error(`RPC error: ${result.error.message}`);
}
return BigInt(result.result || '0x0');
}
function formatBalance(rawBalance, decimals) {
const divisor = BigInt(10 ** decimals);
const whole = rawBalance / divisor;
const fraction = rawBalance % divisor;
const fractionStr = fraction.toString().padStart(decimals, '0').slice(0, 4);
return `${whole}.${fractionStr}`;
}
async function getTreasuryBalances(rpcUrl) {
const results = {};
for (const [walletKey, wallet] of Object.entries(TREASURY_WALLETS)) {
results[walletKey] = {
label: wallet.label,
address: wallet.address,
chain: wallet.chain,
tokens: {},
};
const chainTokens = TRACKED_TOKENS[wallet.chain] || {};
for (const [symbol, token] of Object.entries(chainTokens)) {
try {
const rawBalance = await getTokenBalance(rpcUrl, token.address, wallet.address);
results[walletKey].tokens[symbol] = {
address: token.address,
rawBalance: rawBalance.toString(),
balance: formatBalance(rawBalance, token.decimals),
decimals: token.decimals,
};
} catch (err) {
results[walletKey].tokens[symbol] = {
address: token.address,
error: err.message,
};
}
}
}
return results;
}
async function getNativeBalance(rpcUrl, walletAddress) {
const response = await fetch(rpcUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'eth_getBalance',
params: [walletAddress, 'latest'],
}),
});
const result = await response.json();
return BigInt(result.result || '0x0');
}
async function getFullTreasurySnapshot(rpcUrl) {
const balances = await getTreasuryBalances(rpcUrl);
// Also fetch native MATIC/POL balances
for (const [walletKey, wallet] of Object.entries(TREASURY_WALLETS)) {
try {
const nativeBalance = await getNativeBalance(rpcUrl, wallet.address);
balances[walletKey].tokens['MATIC'] = {
address: 'native',
rawBalance: nativeBalance.toString(),
balance: formatBalance(nativeBalance, 18),
decimals: 18,
};
} catch (err) {
balances[walletKey].tokens['MATIC'] = {
address: 'native',
error: err.message,
};
}
}
return {
timestamp: new Date().toISOString(),
wallets: balances,
};
}
module.exports = {
getTokenBalance,
getTreasuryBalances,
getNativeBalance,
getFullTreasurySnapshot,
formatBalance,
};