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,