From 1105f20387c433b8b24879a6fbc1b45de00e97bb Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Wed, 8 Apr 2026 12:42:42 -0700 Subject: [PATCH] feat: multi-chain `wallets` command + raise kidlisp editor line limit Add `node keeps.mjs wallets` to show balances across all chains (XTZ, ETH, SOL, BTC, ADA) from vault wallets.json using public RPCs. Raise kidlisp.com Monaco editor max lines from 16 to 64 so longer pieces can be pasted in. Co-Authored-By: Claude Opus 4.6 (1M context) --- system/public/kidlisp.com/index.html | 4 +- tezos/keeps.mjs | 150 +++++++++++++++++++++++++++ 2 files changed, 152 insertions(+), 2 deletions(-) diff --git a/system/public/kidlisp.com/index.html b/system/public/kidlisp.com/index.html index 016b9c03ca..619d3cba20 100644 --- a/system/public/kidlisp.com/index.html +++ b/system/public/kidlisp.com/index.html @@ -15662,8 +15662,8 @@ s("ape_breaks_3").loopAt(2) const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches; const initialTheme = isDarkMode ? 'kidlisp-dark' : 'kidlisp-light'; - // Set max lines to 16 (fixed) - const maxLines = 16; + // Set max lines for editor + const maxLines = 64; // Track if user has manually edited the editor (vs programmatic setValue) let editorUserModified = false; diff --git a/tezos/keeps.mjs b/tezos/keeps.mjs index 1cecf06c6c..c3bf030720 100644 --- a/tezos/keeps.mjs +++ b/tezos/keeps.mjs @@ -933,6 +933,150 @@ async function getContractStatus(network = 'mainnet') { } } +// ============================================================================ +// Multi-Chain Wallet Balances +// ============================================================================ + +async function getAllWalletBalances() { + const walletsPath = path.join(CONFIG.paths.vault, 'wallets/wallets.json'); + if (!fs.existsSync(walletsPath)) { + console.error('❌ wallets.json not found in vault'); + process.exit(1); + } + const { wallets } = JSON.parse(fs.readFileSync(walletsPath, 'utf8')); + + console.log('\n╔══════════════════════════════════════════════════════════════╗'); + console.log('║ 🌐 All Wallet Balances ║'); + console.log('╚══════════════════════════════════════════════════════════════╝\n'); + + // Group wallets by chain + const chains = {}; + for (const [key, w] of Object.entries(wallets)) { + (chains[w.chain] ||= []).push({ key, ...w }); + } + + // --- Tezos --- + if (chains.tezos) { + console.log('── Tezos ──────────────────────────────────────────────────────'); + const tezos = new TezosToolkit(CONFIG.mainnet.rpc); + for (const w of chains.tezos) { + try { + const bal = await tezos.tz.getBalance(w.address); + const xtz = (bal.toNumber() / 1_000_000).toFixed(6); + const label = w.domain || w.name; + console.log(` ${label.padEnd(22)} ${xtz.padStart(14)} XTZ ${w.address}`); + } catch (e) { + console.log(` ${(w.domain || w.name).padEnd(22)} ${'error'.padStart(14)} ${w.address} (${e.message})`); + } + } + // Also show keeps contract balance + try { + const contractAddr = fs.readFileSync( + CONFIG.paths.contractAddresses.mainnet, 'utf8' + ).trim(); + const bal = await tezos.tz.getBalance(contractAddr); + const xtz = (bal.toNumber() / 1_000_000).toFixed(6); + console.log(` ${'keeps contract'.padEnd(22)} ${xtz.padStart(14)} XTZ ${contractAddr}`); + } catch (e) { /* skip */ } + console.log(); + } + + // --- Ethereum --- + if (chains.ethereum) { + console.log('── Ethereum ───────────────────────────────────────────────────'); + for (const w of chains.ethereum) { + try { + const resp = await fetch('https://ethereum-rpc.publicnode.com', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ jsonrpc: '2.0', method: 'eth_getBalance', params: [w.address, 'latest'], id: 1 }), + }); + const data = await resp.json(); + const eth = (Number(BigInt(data.result)) / 1e18).toFixed(6); + const label = w.domain || w.name; + console.log(` ${label.padEnd(22)} ${eth.padStart(14)} ETH ${w.address}`); + } catch (e) { + console.log(` ${(w.domain || w.name).padEnd(22)} ${'error'.padStart(14)} ${w.address}`); + } + } + console.log(); + } + + // --- Solana --- + if (chains.solana) { + console.log('── Solana ─────────────────────────────────────────────────────'); + for (const w of chains.solana) { + try { + const resp = await fetch('https://solana-rpc.publicnode.com', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'getBalance', params: [w.address] }), + }); + const data = await resp.json(); + const sol = (data.result.value / 1e9).toFixed(6); + const label = w.name; + console.log(` ${label.padEnd(22)} ${sol.padStart(14)} SOL ${w.address}`); + } catch (e) { + console.log(` ${(w.domain || w.name).padEnd(22)} ${'error'.padStart(14)} ${w.address}`); + } + } + console.log(); + } + + // --- Bitcoin --- + if (chains.bitcoin) { + console.log('── Bitcoin ────────────────────────────────────────────────────'); + for (const w of chains.bitcoin) { + // Check both taproot and segwit addresses + const addr = w.address_taproot || w.address_segwit || w.address; + if (!addr) { + console.log(` ${w.name.padEnd(22)} ${'no address'.padStart(14)} (needs derivation)`); + continue; + } + try { + const resp = await fetch(`https://blockstream.info/api/address/${addr}`); + const data = await resp.json(); + // funded = total received, spent = total sent; chain_stats for confirmed + const confirmed = data.chain_stats || {}; + const satoshis = (confirmed.funded_txo_sum || 0) - (confirmed.spent_txo_sum || 0); + const btc = (satoshis / 1e8).toFixed(8); + const label = w.name; + console.log(` ${label.padEnd(22)} ${btc.padStart(14)} BTC ${addr}`); + } catch (e) { + console.log(` ${w.name.padEnd(22)} ${'error'.padStart(14)} ${addr}`); + } + } + console.log(); + } + + // --- Cardano --- + if (chains.cardano) { + console.log('── Cardano ────────────────────────────────────────────────────'); + for (const w of chains.cardano) { + if (!w.address) { + console.log(` ${w.name.padEnd(22)} ${'no address'.padStart(14)} (derive from mnemonic with cardano-serialization-lib)`); + continue; + } + try { + const resp = await fetch('https://api.koios.rest/api/v1/address_info', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ _addresses: [w.address] }), + }); + const data = await resp.json(); + const lovelace = data[0]?.balance || '0'; + const ada = (Number(lovelace) / 1e6).toFixed(6); + console.log(` ${w.name.padEnd(22)} ${ada.padStart(14)} ADA ${w.address}`); + } catch (e) { + console.log(` ${w.name.padEnd(22)} ${'error'.padStart(14)} ${w.address}`); + } + } + console.log(); + } + + console.log('🔗 Explorers: tzkt.io | etherscan.io | solscan.io | blockstream.info | cardanoscan.io\n'); +} + // ============================================================================ // Wallet Balance // ============================================================================ @@ -4078,6 +4222,10 @@ async function main() { await getBalance(getNetwork(1)); break; + case 'wallets': + await getAllWalletBalances(); + break; + case 'tokens': { const limitFlag = flags.find(f => f.startsWith('--limit=')); const limit = limitFlag ? Number.parseInt(limitFlag.split('=')[1], 10) : undefined; @@ -4509,6 +4657,7 @@ Commands: sync-secrets [network] Sync active contract/profile to Mongo secrets status [network] Show contract status balance [network] Check wallet balance + wallets Show all wallet balances (XTZ, ETH, SOL, BTC, ADA) tokens [network] List tokens held by current wallet market [network] Show Objkt listings/offers/sales snapshot upload Upload bundle to IPFS @@ -4635,6 +4784,7 @@ export { syncCurrentContractToSecrets, getContractStatus, getBalance, + getAllWalletBalances, listOwnedTokens, showMarketSnapshot, listTokenForSale, -- 2.51.2