- get_voters.py: scan Vexanium voters table (vexcore), retry/paginate, filter databisnisid single-vote + stake ≥1000 VEX, store weight verbatim, daily-replace SQLite snapshot (owner PK, scanned_at) - V8/V9 + test_get_voters.py (oracle, §V1-9) - SPEC.md, AGENTS.md, requirements.txt
99 lines
3.0 KiB
JavaScript
99 lines
3.0 KiB
JavaScript
// Ganti dengan nama akun Block Producer Anda di Vexanium
|
|
const TARGET_BP = 'databisnisid';
|
|
|
|
// Menggunakan API dari v2.vexascan.com
|
|
const API_NODE = 'https://v2.vexascan.com:2096';
|
|
|
|
// Fungsi pembantu untuk fetch dengan mekanisme retry (karena node ini kadang timeout)
|
|
async function fetchWithRetry(url, options, maxRetries = 3) {
|
|
for (let i = 0; i < maxRetries; i++) {
|
|
try {
|
|
const response = await fetch(url, options);
|
|
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
|
return response;
|
|
} catch (error) {
|
|
if (i === maxRetries - 1) throw error;
|
|
console.log(`[Warning] Gagal menghubungi node, mencoba lagi... (${i + 1}/${maxRetries})`);
|
|
await new Promise(res => setTimeout(res, 2000)); // tunggu 2 detik sebelum retry
|
|
}
|
|
}
|
|
}
|
|
|
|
async function getVotersForMyBP() {
|
|
let myVoters = [];
|
|
let lowerBound = '';
|
|
let hasMore = true;
|
|
let totalScanned = 0;
|
|
|
|
console.log(`Mulai memindai voters untuk BP: "${TARGET_BP}"...`);
|
|
|
|
while (hasMore) {
|
|
try {
|
|
// Menggunakan fetchWithRetry yang sudah kita buat
|
|
const response = await fetchWithRetry(`${API_NODE}/v1/chain/get_table_rows`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
json: true,
|
|
// PENTING: Di Vexanium, kontrak sistem (untuk tabel voters) adalah vexcore, bukan vexio
|
|
code: 'vexcore',
|
|
scope: 'vexcore',
|
|
table: 'voters',
|
|
lower_bound: lowerBound,
|
|
limit: 500
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
const rows = data.rows || [];
|
|
if (rows.length === 0) break;
|
|
|
|
// Hindari duplikasi elemen pertama saat pagination
|
|
if (lowerBound !== '') {
|
|
rows.shift();
|
|
}
|
|
|
|
totalScanned += rows.length;
|
|
|
|
// Saring hanya pemilih yang HANYA mem-vote BP ini (eksklusif 1 vote)
|
|
const filtered = rows.filter(voter =>
|
|
Array.isArray(voter.producers) &&
|
|
voter.producers.length === 1 &&
|
|
voter.producers[0] === TARGET_BP
|
|
);
|
|
|
|
myVoters = myVoters.concat(filtered);
|
|
hasMore = data.more;
|
|
|
|
if (hasMore && rows.length > 0) {
|
|
lowerBound = rows[rows.length - 1].owner;
|
|
}
|
|
} catch (error) {
|
|
console.error('\nError saat mengambil data:', error.message);
|
|
break;
|
|
}
|
|
}
|
|
|
|
console.log(`\n=== HASIL AKHIR ===`);
|
|
console.log(`Total akun dipindai di jaringan: ${totalScanned}`);
|
|
console.log(`Total voters untuk BP [${TARGET_BP}]: ${myVoters.length}`);
|
|
|
|
// Menampilkan daftar pemilih beserta VEX staked-nya
|
|
console.log('\nDaftar Pemilih:');
|
|
if (myVoters.length === 0) {
|
|
console.log('(Tidak ada pemilih ditemukan untuk BP ini atau scan terhenti di tengah jalan)');
|
|
} else {
|
|
myVoters.forEach((v, index) => {
|
|
// VEX stake disimpan dalam pecahan 1/10000 (4 desimal)
|
|
const vexStaked = (v.staked / 10000).toLocaleString('id-ID');
|
|
console.log(`${index + 1}. ${v.owner} - Stake: ${vexStaked} VEX`);
|
|
});
|
|
}
|
|
|
|
return myVoters;
|
|
}
|
|
|
|
getVotersForMyBP();
|