|
| 1 | +#!/usr/bin/env node |
| 2 | + |
| 3 | +/** |
| 4 | + * Proper Per-Operator BTC Analysis |
| 5 | + * |
| 6 | + * Calculates actual BTC share per operator/provider |
| 7 | + */ |
| 8 | + |
| 9 | +const fs = require('fs'); |
| 10 | +const path = require('path'); |
| 11 | + |
| 12 | +const MAPPING_FILE = path.join(__dirname, 'wallet-operator-mapping.json'); |
| 13 | +const OPERATORS_FILE = path.join(__dirname, 'operators.json'); |
| 14 | + |
| 15 | +const data = JSON.parse(fs.readFileSync(MAPPING_FILE)); |
| 16 | +const operatorsConfig = JSON.parse(fs.readFileSync(OPERATORS_FILE)); |
| 17 | + |
| 18 | +console.log('🔍 Proper BTC Analysis by Operator/Provider\n'); |
| 19 | +console.log('='.repeat(80)); |
| 20 | + |
| 21 | +// Get wallets with operator data |
| 22 | +const walletsWithOps = data.wallets.filter(w => w.memberCount > 0); |
| 23 | + |
| 24 | +console.log(`\nTotal wallets with operator data: ${walletsWithOps.length}`); |
| 25 | +console.log(`Total BTC in these wallets: ${walletsWithOps.reduce((s, w) => s + w.btcBalance, 0).toFixed(8)} BTC`); |
| 26 | + |
| 27 | +// Method 1: Per-wallet breakdown showing which providers are involved |
| 28 | +console.log('\n' + '='.repeat(80)); |
| 29 | +console.log('METHOD 1: Per-Wallet Provider Involvement'); |
| 30 | +console.log('='.repeat(80)); |
| 31 | + |
| 32 | +const walletBreakdown = walletsWithOps.map(wallet => { |
| 33 | + const deprecatedOps = wallet.operators.filter(op => op.status === 'DISABLE'); |
| 34 | + const providers = new Set(deprecatedOps.map(op => op.provider)); |
| 35 | + |
| 36 | + return { |
| 37 | + walletPKH: wallet.walletPKH, |
| 38 | + btcBalance: wallet.btcBalance, |
| 39 | + totalOperators: wallet.memberCount, |
| 40 | + deprecatedCount: deprecatedOps.length, |
| 41 | + providersInvolved: Array.from(providers).sort(), |
| 42 | + activeOperators: wallet.operators.filter(op => op.status === 'KEEP').length |
| 43 | + }; |
| 44 | +}); |
| 45 | + |
| 46 | +// Group by provider combination |
| 47 | +const providerGroups = {}; |
| 48 | +walletBreakdown.forEach(w => { |
| 49 | + const key = w.providersInvolved.join('+'); |
| 50 | + if (!providerGroups[key]) { |
| 51 | + providerGroups[key] = { |
| 52 | + providers: w.providersInvolved, |
| 53 | + wallets: [], |
| 54 | + totalBTC: 0 |
| 55 | + }; |
| 56 | + } |
| 57 | + providerGroups[key].wallets.push(w); |
| 58 | + providerGroups[key].totalBTC += w.btcBalance; |
| 59 | +}); |
| 60 | + |
| 61 | +console.log('\nWallets grouped by provider involvement:\n'); |
| 62 | +Object.entries(providerGroups).forEach(([key, group]) => { |
| 63 | + console.log(`Providers: ${group.providers.join(', ') || 'None'}`); |
| 64 | + console.log(` Wallets: ${group.wallets.length}`); |
| 65 | + console.log(` Total BTC: ${group.totalBTC.toFixed(8)} BTC`); |
| 66 | + console.log(` Average BTC per wallet: ${(group.totalBTC / group.wallets.length).toFixed(2)} BTC`); |
| 67 | + console.log(); |
| 68 | +}); |
| 69 | + |
| 70 | +// Method 2: Equal-split calculation (BTC / operators in wallet) |
| 71 | +console.log('='.repeat(80)); |
| 72 | +console.log('METHOD 2: Equal-Split Per-Operator Share'); |
| 73 | +console.log('='.repeat(80)); |
| 74 | +console.log('\nAssumption: BTC is split equally among all 100 operators in each wallet\n'); |
| 75 | + |
| 76 | +const operatorShares = {}; |
| 77 | + |
| 78 | +// Initialize |
| 79 | +operatorsConfig.operators.keep.forEach(op => { |
| 80 | + operatorShares[op.address.toLowerCase()] = { |
| 81 | + provider: op.provider, |
| 82 | + status: 'KEEP', |
| 83 | + totalShare: 0, |
| 84 | + walletCount: 0 |
| 85 | + }; |
| 86 | +}); |
| 87 | + |
| 88 | +operatorsConfig.operators.disable.forEach(op => { |
| 89 | + operatorShares[op.address.toLowerCase()] = { |
| 90 | + provider: op.provider, |
| 91 | + status: 'DISABLE', |
| 92 | + totalShare: 0, |
| 93 | + walletCount: 0 |
| 94 | + }; |
| 95 | +}); |
| 96 | + |
| 97 | +// Calculate shares |
| 98 | +walletsWithOps.forEach(wallet => { |
| 99 | + const sharePerOperator = wallet.btcBalance / wallet.memberCount; |
| 100 | + |
| 101 | + wallet.operators.forEach(op => { |
| 102 | + const addr = op.address.toLowerCase(); |
| 103 | + if (operatorShares[addr]) { |
| 104 | + operatorShares[addr].totalShare += sharePerOperator; |
| 105 | + operatorShares[addr].walletCount++; |
| 106 | + } |
| 107 | + }); |
| 108 | +}); |
| 109 | + |
| 110 | +// Group by provider |
| 111 | +const providerShares = { |
| 112 | + STAKED: { keep: 0, disable: 0, keepWallets: 0, disableWallets: 0 }, |
| 113 | + P2P: { keep: 0, disable: 0, keepWallets: 0, disableWallets: 0 }, |
| 114 | + BOAR: { keep: 0, disable: 0, keepWallets: 0, disableWallets: 0 }, |
| 115 | + NUCO: { keep: 0, disable: 0, keepWallets: 0, disableWallets: 0 } |
| 116 | +}; |
| 117 | + |
| 118 | +Object.entries(operatorShares).forEach(([addr, data]) => { |
| 119 | + if (providerShares[data.provider]) { |
| 120 | + if (data.status === 'KEEP') { |
| 121 | + providerShares[data.provider].keep += data.totalShare; |
| 122 | + providerShares[data.provider].keepWallets = data.walletCount; |
| 123 | + } else { |
| 124 | + providerShares[data.provider].disable += data.totalShare; |
| 125 | + providerShares[data.provider].disableWallets = data.walletCount; |
| 126 | + } |
| 127 | + } |
| 128 | +}); |
| 129 | + |
| 130 | +console.log('Per-Provider BTC Shares (Equal-Split Method):\n'); |
| 131 | +console.log('Provider | KEEP Ops Share | DISABLE Ops Share | Total Share'); |
| 132 | +console.log('-'.repeat(80)); |
| 133 | + |
| 134 | +Object.entries(providerShares).forEach(([provider, shares]) => { |
| 135 | + const total = shares.keep + shares.disable; |
| 136 | + console.log(`${provider.padEnd(8)} | ${shares.keep.toFixed(2).padStart(13)} BTC | ${shares.disable.toFixed(2).padStart(17)} BTC | ${total.toFixed(2)} BTC`); |
| 137 | +}); |
| 138 | + |
| 139 | +console.log('\n' + '='.repeat(80)); |
| 140 | +console.log('METHOD 3: Deprecated Operator BTC (What needs to be "moved")'); |
| 141 | +console.log('='.repeat(80)); |
| 142 | +console.log('\nThis shows the BTC share held by deprecated operators that'); |
| 143 | +console.log('needs to remain accessible during the draining period.\n'); |
| 144 | + |
| 145 | +Object.entries(providerShares).forEach(([provider, shares]) => { |
| 146 | + console.log(`${provider}:`); |
| 147 | + console.log(` Deprecated operator share: ${shares.disable.toFixed(2)} BTC`); |
| 148 | + console.log(` Number of wallets involved: ${shares.disableWallets}`); |
| 149 | + console.log(` Average per wallet: ${shares.disableWallets > 0 ? (shares.disable / shares.disableWallets).toFixed(2) : 0} BTC`); |
| 150 | + console.log(); |
| 151 | +}); |
| 152 | + |
| 153 | +// Method 4: Detailed operator-by-operator breakdown |
| 154 | +console.log('='.repeat(80)); |
| 155 | +console.log('METHOD 4: Individual Operator Breakdown (Top 20 by BTC)'); |
| 156 | +console.log('='.repeat(80)); |
| 157 | + |
| 158 | +const operatorList = Object.entries(operatorShares) |
| 159 | + .map(([addr, data]) => ({ |
| 160 | + address: addr, |
| 161 | + ...data |
| 162 | + })) |
| 163 | + .sort((a, b) => b.totalShare - a.totalShare) |
| 164 | + .slice(0, 20); |
| 165 | + |
| 166 | +console.log('\nOperator Address | Provider | Status | BTC Share | Wallets'); |
| 167 | +console.log('-'.repeat(80)); |
| 168 | +operatorList.forEach(op => { |
| 169 | + const addrShort = op.address.slice(0, 10) + '...' + op.address.slice(-6); |
| 170 | + console.log(`${addrShort} | ${op.provider.padEnd(7)} | ${op.status.padEnd(7)} | ${op.totalShare.toFixed(2).padStart(8)} BTC | ${op.walletCount}`); |
| 171 | +}); |
| 172 | + |
| 173 | +// Summary |
| 174 | +console.log('\n' + '='.repeat(80)); |
| 175 | +console.log('SUMMARY & INTERPRETATION'); |
| 176 | +console.log('='.repeat(80)); |
| 177 | + |
| 178 | +const totalBTC = walletsWithOps.reduce((s, w) => s + w.btcBalance, 0); |
| 179 | +const totalDeprecatedShare = Object.values(providerShares).reduce((s, p) => s + p.disable, 0); |
| 180 | + |
| 181 | +console.log(`\nTotal BTC in analyzed wallets: ${totalBTC.toFixed(8)} BTC`); |
| 182 | +console.log(`Total BTC share of deprecated operators: ${totalDeprecatedShare.toFixed(2)} BTC`); |
| 183 | +console.log(`Percentage held by deprecated operators: ${(totalDeprecatedShare / totalBTC * 100).toFixed(2)}%`); |
| 184 | + |
| 185 | +console.log('\n⚠️ IMPORTANT NOTES:'); |
| 186 | +console.log('1. The "equal-split" is a CALCULATION METHOD, not how threshold signatures work'); |
| 187 | +console.log('2. In reality, ALL 100 operators must participate for wallet actions (51/100 threshold)'); |
| 188 | +console.log('3. The deprecated operators do not individually "own" their share'); |
| 189 | +console.log('4. What matters: which WALLETS contain deprecated operators (all 24 do)'); |
| 190 | +console.log('5. For sweeps: need active operators to coordinate, not individual BTC shares'); |
| 191 | + |
| 192 | +console.log('\n✅ CORRECT INTERPRETATION:'); |
| 193 | +console.log('- All 24 wallets (5,923.91 BTC) contain deprecated operators'); |
| 194 | +console.log('- Natural draining or manual sweeps affect the ENTIRE wallet, not per-operator'); |
| 195 | +console.log('- Coordination needed: active operators from STAKED, P2P, BOAR (and NUCO for 20 wallets)'); |
0 commit comments