Skip to content

Instantly share code, notes, and snippets.

@mikedotexe
Created May 10, 2025 19:30
Show Gist options
  • Select an option

  • Save mikedotexe/2e1f8707cd93c419026ff1fb4f7f70f4 to your computer and use it in GitHub Desktop.

Select an option

Save mikedotexe/2e1f8707cd93c419026ff1fb4f7f70f4 to your computer and use it in GitHub Desktop.
npx serve .
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>NEAR Validator Balances</title>
<style>
:root {
--bg-gradient: linear-gradient(135deg, #131f37 0%, #1c2942 100%);
--text: #ffffff;
--accent: #64ffda;
--card-bg: rgba(13, 25, 48, 0.98);
font-size: 19px;
}
*{box-sizing:border-box}
body{
margin: 0;
font-family: "Inter", system-ui, sans-serif;
background: var(--bg-gradient);
color: var(--text);
min-height: 100vh;
}
body p {
line-height: 1.9rem;
}
@keyframes ripple {
0% { opacity: 1; }
100% { opacity: 0; }
}
#bar-wrap{position:fixed;top:0;left:0;width:100%;height:3px;background:rgba(255,255,255,.08);z-index:1000;border-bottom: 1px solid rgba(100, 255, 218, 0.1)}
#bar{width:0%;height:100%;background:var(--accent);transition:width .2s ease;box-shadow: 0 0 15px rgba(100, 255, 218, 0.15), 0 0 30px rgba(100, 255, 218, 0.07)}
main{padding:5rem 1rem 2rem;max-width:46rem;margin:0 auto;position:relative}
input,button{font:inherit;padding:.6rem 1rem;border-radius:4px;border:1px solid rgba(100, 255, 218, 0.1);background:var(--card-bg);color:var(--text)}
input:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 2px rgba(100, 255, 218, 0.2)}
button{
cursor:pointer;
border-color:var(--accent);
background:rgba(0, 0, 0, 0.2);
color:var(--accent);
transition:all 0.2s ease;
position:relative;
overflow:hidden;
}
button:hover:not(:disabled){
background:rgba(100, 255, 218, 0.1);
border-color:var(--accent);
}
button:focus{
outline:none;
box-shadow:0 0 15px rgba(100, 255, 218, 0.15);
}
button:active:not(:disabled){
transform:scale(0.98);
}
button:disabled{
opacity:0.6;
cursor:not-allowed;
background:rgba(0, 0, 0, 0.3);
}
button.active{
background:var(--accent);
color:var(--card-bg);
transform:scale(0.98);
}
button::after{
content:'';
position:absolute;
top:50%;
left:50%;
width:5px;
height:5px;
background:rgba(255,255,255,0.4);
border-radius:100%;
transform:scale(0);
opacity:0;
transition:all 0.5s;
}
button.ripple::after{
animation:ripple 0.6s linear;
transform:scale(40);
opacity:0;
}
pre{
overflow-x:auto;
background:rgba(0, 0, 0, 0.2);
border:1px solid rgba(100, 255, 218, 0.15);
padding:1rem;
border-radius:0.5rem;
white-space:pre-wrap;
word-break:break-word;
box-shadow:0 0 15px rgba(100, 255, 218, 0.15), 0 0 30px rgba(100, 255, 218, 0.07), 0 0 60px rgba(100, 255, 218, 0.04);
}
.warn{color:#ff7373}
h1 {
color: var(--accent);
margin-bottom: 0.5rem;
}
.neon-text {
text-shadow: 0 0 1px #00ffa2, 0 0 0 #00ffa2, 0 0 1px #00a2ff;
letter-spacing: 0.2rem;
}
</style>
<script src="https://unpkg.com/@fastnear/api/dist/umd/browser.global.js"></script>
<script type="module">
/* global near */
await import('https://unpkg.com/@fastnear/api/dist/umd/browser.global.js');
const { sendRpc, utils } = near;
if(!sendRpc) throw new Error('sendRpc missing');
near.config({
networkId:'mainnet',
nodeUrl:'https://rpc.mainnet.fastnear.com',
});
const td = new TextDecoder();
const encodeArgs = (o)=>utils.toBase64(JSON.stringify(o));
const decodeBytes = (bytes)=>td.decode(Uint8Array.from(bytes));
const parseAmt = (b)=>decodeBytes(b).replace(/"/g,'');
// simple concurrency limiter
const limit = async (iter, concurrency, fn) => {
const ret=[]; const exe=[];
for(const it of iter){
const p=Promise.resolve().then(()=>fn(it));
ret.push(p);
if(concurrency<=iter.length){
const e=p.then(()=>exe.splice(exe.indexOf(e),1));
exe.push(e);
if(exe.length>=concurrency) await Promise.race(exe);
}
}
return Promise.all(ret);
};
const bar = document.getElementById('bar');
const input = document.getElementById('acct');
const run = document.getElementById('run');
const count = document.getElementById('count');
const out = document.getElementById('out');
const setBar=(d,t)=>bar.style.width=`${(d/t*100).toFixed(1)}%`;
const viewCall = (v,m,a)=>sendRpc('query',{
request_type:'call_function',account_id:v,method_name:m,args_base64:encodeArgs(a),finality:'optimistic'
});
async function fetchBalances(acct){
run.disabled=true;
run.classList.add('active');
out.textContent='Fetching…';
count.textContent='0';
try{
const { result:{current_validators} } = await sendRpc('validators',[null]);
const validators=current_validators.map(v=>v.account_id);
const totalCalls=validators.length*2; let done=0; setBar(0,totalCalls);
let processed=0;
const balances=[]; const errors=[];
await limit(validators, 120, async v => {
let staked='0',unstaked='0',err=null;
const rpc=async m=>{
for(let i=0;i<3;i++){
try{const r=await viewCall(v,m,{account_id:acct});return parseAmt(r.result.result);}catch(e){if(/429|Too Many/i.test(e.message)){await new Promise(r=>setTimeout(r,500*(i+1)));continue;}throw e;}}
throw new Error('429 Too Many Requests');
};
try{staked=await rpc('get_account_staked_balance');}catch(e){err=e.message;}finally{setBar(++done,totalCalls);}
try{unstaked=await rpc('get_account_unstaked_balance');}catch(e){err=err||e.message;}finally{setBar(++done,totalCalls);}
processed++;count.textContent=processed.toString();
if(err){errors.push({validator:v,error:err});return;}
if(staked!=='0'||unstaked!=='0') balances.push({validator:v,staked,unstaked});
});
out.innerHTML=`${balances.length?JSON.stringify({balances},null,2):'<span class="warn">No non-zero balances</span>'}\n\n${errors.length?JSON.stringify({errors},null,2):''}`;
setBar(totalCalls,totalCalls);
}catch(e){ out.innerHTML=`<span class="warn">Fatal: ${e.message}</span>`; }
finally{
run.disabled=false;
run.classList.remove('active');
}
}
const trigger=()=>{ const a=input.value.trim(); if(!a){alert('Enter account_id');return;} fetchBalances(a);} ;
// Visual feedback for button interactions
run.addEventListener('mousedown', () => {
if (!run.disabled) {
run.classList.add('ripple');
setTimeout(() => run.classList.remove('ripple'), 600);
}
});
run.addEventListener('keydown', (e) => {
if ((e.key === 'Enter' || e.key === ' ') && !run.disabled) {
run.classList.add('ripple');
setTimeout(() => run.classList.remove('ripple'), 600);
}
});
// Focus handling
run.addEventListener('blur', () => {
run.classList.remove('ripple');
});
run.addEventListener('click', trigger);
input.addEventListener('keydown', e => { if(e.key === 'Enter') trigger(); });
</script>
</head>
<body>
<div id="bar-wrap"><div id="bar"></div></div>
<main>
<div class="hero-section">
<h1 class="neon-text">Validator Balances</h1>
<p>Check staking balances across all NEAR validators</p>
<p>Validators queried: <strong id="count" class="accent">0</strong></p>
<div style="display:flex;gap:.5rem;margin:1.5rem 0;position:relative;">
<input id="acct" value="root.near" style="flex:1 1 16rem" placeholder="Enter NEAR account ID" />
<button id="run">Check staking</button>
</div>
<pre id="out">Hit that button to see your staking balances across all validators.</pre>
</div>
</main>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment