-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathetherscan.js
More file actions
145 lines (122 loc) · 3.5 KB
/
etherscan.js
File metadata and controls
145 lines (122 loc) · 3.5 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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import { keccak256, toBytes, formatEther } from 'https://esm.sh/viem@2';
import { CONTRACT_ADDRESS, decodeScoreEvent } from './contract.js';
// ========== CONFIG ==========
// Etherscan API V2 - tek API key tüm chainlerde çalışır
const API_KEY = '8HIB9IQ9ZUIJJBE452V2G4P6J92988X3M8';
const USE_TESTNET = false;
// ============================
const ETHERSCAN_V2 = 'https://api.etherscan.io/v2/api';
const CHAIN_IDS = {
base: 8453,
baseSepolia: 84532,
};
const CHAIN_ID = USE_TESTNET ? CHAIN_IDS.baseSepolia : CHAIN_IDS.base;
// ScoreSubmitted(address,uint256,uint256) event topic
const SCORE_EVENT_TOPIC = keccak256(
toBytes('ScoreSubmitted(address,uint256,uint256)')
);
async function ethRequest(params) {
if (API_KEY === '[YOUR_ETHERSCAN_API_KEY]') return null;
const url = new URL(ETHERSCAN_V2);
url.searchParams.append('chainid', String(CHAIN_ID));
url.searchParams.append('apikey', API_KEY);
Object.entries(params).forEach(([key, value]) => {
url.searchParams.append(key, String(value));
});
try {
const res = await fetch(url);
const data = await res.json();
if (data.status === '0' && data.message === 'NOTOK') {
throw new Error(data.result);
}
return data.result;
} catch (e) {
console.warn('Etherscan API error:', e.message);
return null;
}
}
// --- Account Balance ---
export async function getBalance(address) {
return ethRequest({
module: 'account',
action: 'balance',
address,
tag: 'latest',
});
}
// --- Token Portfolio ---
export async function getTokenPortfolio(address) {
return ethRequest({
module: 'account',
action: 'addresstokenbalance',
address,
page: 1,
offset: 10,
});
}
// --- Player Transaction History ---
export async function getPlayerTxHistory(address) {
return ethRequest({
module: 'account',
action: 'txlist',
address,
sort: 'desc',
page: 1,
offset: 20,
});
}
// --- Contract Event Logs (ScoreSubmitted) ---
export async function getScoreEvents() {
if (CONTRACT_ADDRESS === '0x0000000000000000000000000000000000000000') {
return null;
}
return ethRequest({
module: 'logs',
action: 'getLogs',
address: CONTRACT_ADDRESS,
topic0: SCORE_EVENT_TOPIC,
fromBlock: '0',
toBlock: 'latest',
});
}
// --- Build Leaderboard from On-chain Events ---
export async function buildLeaderboard() {
const logs = await getScoreEvents();
if (!logs || !Array.isArray(logs) || logs.length === 0) return [];
const best = {};
for (const log of logs) {
const decoded = decodeScoreEvent(log);
if (!decoded) continue;
const player = decoded.args.player.toLowerCase();
const captured = Number(decoded.args.captured);
const ts = Number(decoded.args.timestamp);
if (!best[player] || captured > best[player].captured) {
best[player] = { player: decoded.args.player, captured, timestamp: ts };
}
}
return Object.values(best)
.sort((a, b) => b.captured - a.captured)
.slice(0, 10);
}
// --- Verify Contract Source on Etherscan ---
export async function getContractInfo() {
if (CONTRACT_ADDRESS === '0x0000000000000000000000000000000000000000') {
return null;
}
return ethRequest({
module: 'contract',
action: 'getsourcecode',
address: CONTRACT_ADDRESS,
});
}
// --- Format Wei to ETH ---
export function weiToEth(wei) {
try {
return parseFloat(formatEther(BigInt(wei))).toFixed(4);
} catch {
return '0';
}
}
export function isApiConfigured() {
return API_KEY !== '[YOUR_ETHERSCAN_API_KEY]';
}