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  }