github.com/piotrnar/gocoin@v0.0.0-20240512203912-faa0448c5e96/client/usif/textui/wallet.go (about) 1 package textui 2 3 import ( 4 "bytes" 5 "encoding/hex" 6 "fmt" 7 "sort" 8 "strconv" 9 "strings" 10 11 "github.com/piotrnar/gocoin/client/common" 12 "github.com/piotrnar/gocoin/client/network" 13 "github.com/piotrnar/gocoin/client/wallet" 14 "github.com/piotrnar/gocoin/lib/btc" 15 ) 16 17 type OneWalletAddrs struct { 18 Typ int // 0-p2kh, 1-p2sh, 2-segwit_prog, 3-taproot 19 Key []byte 20 rec *wallet.OneAllAddrBal 21 } 22 23 type SortedWalletAddrs []OneWalletAddrs 24 25 var sort_by_cnt bool 26 27 func (sk SortedWalletAddrs) Len() int { 28 return len(sk) 29 } 30 31 func (sk SortedWalletAddrs) Less(a, b int) bool { 32 if sort_by_cnt { 33 return sk[a].rec.Count() > sk[b].rec.Count() 34 } 35 return sk[a].rec.Value > sk[b].rec.Value 36 } 37 38 func (sk SortedWalletAddrs) Swap(a, b int) { 39 sk[a], sk[b] = sk[b], sk[a] 40 } 41 42 func max_outs(par string) { 43 sort_by_cnt = true 44 all_addrs(par) 45 } 46 47 func best_val(par string) { 48 sort_by_cnt = false 49 all_addrs(par) 50 } 51 52 func new_slice(in []byte) (kk []byte) { 53 kk = make([]byte, len(in)) 54 copy(kk, in) 55 return 56 } 57 58 func all_addrs(par string) { 59 var ptkh_outs, ptkh_vals, ptsh_outs, ptsh_vals uint64 60 var ptwkh_outs, ptwkh_vals, ptwsh_outs, ptwsh_vals uint64 61 var ptap_outs, ptap_vals uint64 62 var best SortedWalletAddrs 63 var cnt int = 15 64 var mode int 65 66 if !common.GetBool(&common.WalletON) { 67 fmt.Println("Wallet functionality is currently disabled.") 68 return 69 } 70 71 if par != "" { 72 prs := strings.SplitN(par, " ", 2) 73 if len(prs) > 0 { 74 if c, e := strconv.ParseUint(prs[0], 10, 32); e == nil { 75 if c > 4 { 76 cnt = int(c) 77 } else { 78 mode = int(c + 1) 79 fmt.Println("Counting only addr type", ([]string{"P2KH", "P2SH", "P2WKH", "P2WSH", "P2TAP"})[int(c)]) 80 if len(prs) > 1 { 81 if c, e := strconv.ParseUint(prs[1], 10, 32); e == nil { 82 cnt = int(c) 83 } 84 } 85 } 86 } else { 87 fmt.Println("Specify the address type or/and number of top records to display") 88 fmt.Println("Valid address types: 0-P2K, 1-P2SH, 2-P2WKH, 3-P2WSH, 4-P2TAP") 89 return 90 } 91 } 92 } 93 94 var MIN_BTC uint64 = 100e8 95 var MIN_OUTS int = 1000 96 97 if mode != 0 { 98 MIN_BTC = 0 99 MIN_OUTS = 0 100 } 101 102 if mode == 0 || mode == 1 { 103 for k, rec := range wallet.AllBalancesP2KH { 104 ptkh_vals += rec.Value 105 ptkh_outs += uint64(rec.Count()) 106 if sort_by_cnt && rec.Count() >= MIN_OUTS || !sort_by_cnt && rec.Value >= MIN_BTC { 107 best = append(best, OneWalletAddrs{Typ: 0, Key: new_slice(k[:]), rec: rec}) 108 } 109 } 110 fmt.Println(btc.UintToBtc(ptkh_vals), "BTC in", ptkh_outs, "unspent recs from", len(wallet.AllBalancesP2KH), "P2KH addresses") 111 } 112 113 if mode == 0 || mode == 2 { 114 for k, rec := range wallet.AllBalancesP2SH { 115 ptsh_vals += rec.Value 116 ptsh_outs += uint64(rec.Count()) 117 if sort_by_cnt && rec.Count() >= MIN_OUTS || !sort_by_cnt && rec.Value >= MIN_BTC { 118 best = append(best, OneWalletAddrs{Typ: 1, Key: new_slice(k[:]), rec: rec}) 119 } 120 } 121 fmt.Println(btc.UintToBtc(ptsh_vals), "BTC in", ptsh_outs, "unspent recs from", len(wallet.AllBalancesP2SH), "P2SH addresses") 122 } 123 124 if mode == 0 || mode == 3 { 125 for k, rec := range wallet.AllBalancesP2WKH { 126 ptwkh_vals += rec.Value 127 ptwkh_outs += uint64(rec.Count()) 128 if sort_by_cnt && rec.Count() >= MIN_OUTS || !sort_by_cnt && rec.Value >= MIN_BTC { 129 best = append(best, OneWalletAddrs{Typ: 2, Key: new_slice(k[:]), rec: rec}) 130 } 131 } 132 fmt.Println(btc.UintToBtc(ptwkh_vals), "BTC in", ptwkh_outs, "unspent recs from", len(wallet.AllBalancesP2WKH), "P2WKH addresses") 133 } 134 135 if mode == 0 || mode == 4 { 136 for k, rec := range wallet.AllBalancesP2WSH { 137 ptwsh_vals += rec.Value 138 ptwsh_outs += uint64(rec.Count()) 139 if sort_by_cnt && rec.Count() >= MIN_OUTS || !sort_by_cnt && rec.Value >= MIN_BTC { 140 best = append(best, OneWalletAddrs{Typ: 2, Key: new_slice(k[:]), rec: rec}) 141 } 142 } 143 fmt.Println(btc.UintToBtc(ptwsh_vals), "BTC in", ptwsh_outs, "unspent recs from", len(wallet.AllBalancesP2WSH), "P2WSH addresses") 144 } 145 146 if mode == 0 || mode == 5 { 147 for k, rec := range wallet.AllBalancesP2TAP { 148 ptap_vals += rec.Value 149 ptap_outs += uint64(rec.Count()) 150 if sort_by_cnt && rec.Count() >= MIN_OUTS || !sort_by_cnt && rec.Value >= MIN_BTC { 151 best = append(best, OneWalletAddrs{Typ: 3, Key: new_slice(k[:]), rec: rec}) 152 } 153 } 154 fmt.Println(btc.UintToBtc(ptap_vals), "BTC in", ptap_outs, "unspent recs from", len(wallet.AllBalancesP2TAP), "P2TAP addresses") 155 } 156 157 if sort_by_cnt { 158 fmt.Println("Top addresses with at least", MIN_OUTS, "unspent outputs:", len(best)) 159 } else { 160 fmt.Println("Top addresses with at least", btc.UintToBtc(MIN_BTC), "BTC:", len(best)) 161 } 162 163 sort.Sort(best) 164 165 var pkscr_p2sk [23]byte 166 var pkscr_p2kh [25]byte 167 var ad *btc.BtcAddr 168 169 pkscr_p2sk[0] = 0xa9 170 pkscr_p2sk[1] = 20 171 pkscr_p2sk[22] = 0x87 172 173 pkscr_p2kh[0] = 0x76 174 pkscr_p2kh[1] = 0xa9 175 pkscr_p2kh[2] = 20 176 pkscr_p2kh[23] = 0x88 177 pkscr_p2kh[24] = 0xac 178 179 for i := 0; i < len(best) && i < cnt; i++ { 180 switch best[i].Typ { 181 case 0: 182 copy(pkscr_p2kh[3:23], best[i].Key) 183 ad = btc.NewAddrFromPkScript(pkscr_p2kh[:], common.CFG.Testnet) 184 case 1: 185 copy(pkscr_p2sk[2:22], best[i].Key) 186 ad = btc.NewAddrFromPkScript(pkscr_p2sk[:], common.CFG.Testnet) 187 case 2, 3: 188 ad = new(btc.BtcAddr) 189 ad.SegwitProg = new(btc.SegwitProg) 190 ad.SegwitProg.HRP = btc.GetSegwitHRP(common.CFG.Testnet) 191 ad.SegwitProg.Program = best[i].Key 192 ad.SegwitProg.Version = best[i].Typ - 2 193 } 194 fmt.Println(i+1, ad.String(), btc.UintToBtc(best[i].rec.Value), "BTC in", best[i].rec.Count(), "inputs") 195 } 196 } 197 198 func list_unspent_addr(ad *btc.BtcAddr) { 199 var addr_printed bool 200 outscr := ad.OutScript() 201 202 unsp := wallet.GetAllUnspent(ad) 203 if len(unsp) != 0 { 204 var tot uint64 205 sort.Sort(unsp) 206 for i := range unsp { 207 unsp[i].BtcAddr = nil // no need to print the address here 208 tot += unsp[i].Value 209 } 210 fmt.Println(ad.String(), "has", btc.UintToBtc(tot), "BTC in", len(unsp), "records:") 211 addr_printed = true 212 for i := range unsp { 213 fmt.Println(unsp[i].String()) 214 network.TxMutex.Lock() 215 bidx, spending := network.SpentOutputs[unsp[i].TxPrevOut.UIdx()] 216 var t2s *network.OneTxToSend 217 if spending { 218 t2s, spending = network.TransactionsToSend[bidx] 219 } 220 network.TxMutex.Unlock() 221 if spending { 222 fmt.Println("\t- being spent by TxID", t2s.Hash.String()) 223 } 224 } 225 } 226 227 network.TxMutex.Lock() 228 for _, t2s := range network.TransactionsToSend { 229 for vo, to := range t2s.TxOut { 230 if bytes.Equal(to.Pk_script, outscr) { 231 if !addr_printed { 232 fmt.Println(ad.String(), "has incoming mempool tx(s):") 233 addr_printed = true 234 } 235 fmt.Printf("%15s BTC confirming as %s-%03d\n", 236 btc.UintToBtc(to.Value), t2s.Hash.String(), vo) 237 } 238 } 239 } 240 network.TxMutex.Unlock() 241 242 if !addr_printed { 243 fmt.Println(ad.String(), "has no coins") 244 } 245 } 246 247 func list_unspent(addr string) { 248 if !common.GetBool(&common.WalletON) { 249 fmt.Println("Wallet functionality is currently disabled.") 250 return 251 } 252 253 // check for raw public key... 254 pk, er := hex.DecodeString(addr) 255 if er != nil || len(pk) != 33 || pk[0] != 2 && pk[0] != 3 { 256 ad, e := btc.NewAddrFromString(addr) 257 if e != nil { 258 println(e.Error()) 259 return 260 } 261 list_unspent_addr(ad) 262 return 263 } 264 265 // if here, pk contains a valid public key 266 ad := btc.NewAddrFromPubkey(pk, btc.AddrVerPubkey(common.Testnet)) 267 if ad == nil { 268 println("Unexpected error returned by NewAddrFromPubkey()") 269 return 270 } 271 hrp := btc.GetSegwitHRP(common.Testnet) 272 list_unspent_addr(ad) 273 274 ad.Enc58str = "" 275 ad.SegwitProg = &btc.SegwitProg{HRP: hrp, Version: 1, Program: pk[1:]} 276 list_unspent_addr(ad) 277 278 ad.Enc58str = "" 279 ad.SegwitProg = &btc.SegwitProg{HRP: hrp, Version: 0, Program: ad.Hash160[:]} 280 list_unspent_addr(ad) 281 282 h160 := btc.Rimp160AfterSha256(append([]byte{0, 20}, ad.Hash160[:]...)) 283 ad = btc.NewAddrFromHash160(h160[:], btc.AddrVerScript(common.Testnet)) 284 list_unspent_addr(ad) 285 } 286 287 func all_val_stats(s string) { 288 if !common.GetBool(&common.WalletON) { 289 fmt.Println("Wallet functionality is currently disabled.") 290 return 291 } 292 293 wallet.PrintStat() 294 } 295 296 func wallet_on_off(s string) { 297 if s == "on" { 298 select { 299 case wallet.OnOff <- true: 300 default: 301 } 302 return 303 } else if s == "off" { 304 select { 305 case wallet.OnOff <- false: 306 default: 307 } 308 return 309 } 310 311 if common.GetBool(&common.WalletON) { 312 fmt.Println("Wallet functionality is currently ENABLED. Execute 'wallet off' to disable it.") 313 fmt.Println("") 314 } else { 315 if perc := common.GetUint32(&common.WalletProgress); perc != 0 { 316 fmt.Println("Enabling wallet functionality -", (perc-1)/10, "percent complete. Execute 'wallet off' to abort it.") 317 } else { 318 fmt.Println("Wallet functionality is currently DISABLED. Execute 'wallet on' to enable it.") 319 } 320 } 321 322 if pend := common.GetUint32(&common.WalletOnIn); pend > 0 { 323 fmt.Println("Wallet functionality will auto enable in", pend, "seconds") 324 } 325 } 326 327 func init() { 328 newUi("richest r", true, best_val, "Show addresses with most coins [0,1,2,3 or count]") 329 newUi("maxouts o", true, max_outs, "Show addresses with highest number of outputs [0,1,2,3 or count]") 330 newUi("balance a", true, list_unspent, "List balance of given bitcoin address (or raw public key)") 331 newUi("allbal ab", true, all_val_stats, "Show Allbalance statistics") 332 newUi("wallet w", false, wallet_on_off, "Enable (on) or disable (off) wallet functionality") 333 }