github.com/piotrnar/gocoin@v0.0.0-20240512203912-faa0448c5e96/client/usif/webui/wallets.go (about) 1 package webui 2 3 import ( 4 "archive/zip" 5 "bytes" 6 "encoding/hex" 7 "encoding/json" 8 "fmt" 9 "html" 10 "io/ioutil" 11 "net/http" 12 "sort" 13 "strconv" 14 "strings" 15 16 "github.com/piotrnar/gocoin/client/common" 17 "github.com/piotrnar/gocoin/client/network" 18 "github.com/piotrnar/gocoin/client/usif" 19 "github.com/piotrnar/gocoin/client/wallet" 20 "github.com/piotrnar/gocoin/lib/btc" 21 "github.com/piotrnar/gocoin/lib/utxo" 22 ) 23 24 const ( 25 AT_P2PKH = "P2PKH" 26 AT_P2SH = "P2SH" 27 AT_P2WSH = "P2WSH" 28 AT_P2SH_P2WPKH = "P2SH-P2WPKH" 29 AT_P2WPKH = "P2WPKH" 30 AT_P2TAP = "P2TAP" 31 ) 32 33 func p_wal(w http.ResponseWriter, r *http.Request) { 34 if !ipchecker(r) { 35 return 36 } 37 38 if !common.GetBool(&common.WalletON) { 39 p_wallet_is_off(w, r) 40 return 41 } 42 43 var str string 44 common.Last.Mutex.Lock() 45 if common.BlockChain.Consensus.Enforce_SEGWIT != 0 && 46 common.Last.Block.Height >= common.BlockChain.Consensus.Enforce_SEGWIT { 47 str = "var segwit_active=true" 48 } else { 49 str = "var segwit_active=false" 50 } 51 common.Last.Mutex.Unlock() 52 page := load_template("wallet.html") 53 page = strings.Replace(page, "/*WALLET_JS_VARS*/", str, 1) 54 write_html_head(w, r) 55 w.Write([]byte(page)) 56 write_html_tail(w) 57 } 58 59 func getaddrtype(aa *btc.BtcAddr) string { 60 if aa.SegwitProg != nil { 61 if aa.SegwitProg.Version == 0 && len(aa.SegwitProg.Program) == 20 { 62 return AT_P2WPKH 63 } 64 if aa.SegwitProg.Version == 1 && len(aa.SegwitProg.Program) == 32 { 65 return AT_P2TAP 66 } 67 } 68 if aa.Version == btc.AddrVerPubkey(common.Testnet) { 69 return AT_P2PKH 70 } 71 if aa.Version == btc.AddrVerScript(common.Testnet) { 72 return "P2SH" 73 } 74 return "unknown" 75 } 76 77 func json_balance(w http.ResponseWriter, r *http.Request) { 78 if !ipchecker(r) || !common.GetBool(&common.WalletON) { 79 return 80 } 81 82 if r.Method != "POST" { 83 return 84 } 85 86 summary := len(r.Form["summary"]) > 0 87 mempool := len(r.Form["mempool"]) > 0 88 getrawtx := len(r.Form["rawtx"]) > 0 89 90 inp, er := ioutil.ReadAll(r.Body) 91 if er != nil { 92 println(er.Error()) 93 return 94 } 95 96 var addrs []string 97 er = json.Unmarshal(inp, &addrs) 98 if er != nil { 99 println(er.Error()) 100 return 101 } 102 103 type OneOut struct { 104 TxId string 105 Vout uint32 106 Value uint64 107 Height uint32 108 Coinbase bool 109 Message string 110 Addr string 111 AddrType string 112 Spending bool // if true the spending tx is in the mempool 113 RawTx string `json:",omitempty"` 114 } 115 116 type OneOuts struct { 117 Value uint64 118 OutCnt int 119 SegWitCnt int 120 SegWitAddr string 121 SegWitNativeCnt int 122 SegWitNativeAddr string 123 SegWitTapCnt int 124 SegWitTapAddr string 125 Outs []OneOut 126 127 PendingCnt int 128 PendingValue uint64 129 PendingOuts []OneOut 130 131 SpendingValue uint64 132 SpendingCnt uint64 133 } 134 135 out := make(map[string]*OneOuts) 136 137 lck := new(usif.OneLock) 138 lck.In.Add(1) 139 lck.Out.Add(1) 140 usif.LocksChan <- lck 141 lck.In.Wait() 142 143 var addr_map map[string]string 144 145 if mempool { 146 // make addrs -> idx 147 addr_map = make(map[string]string, 2*len(addrs)) 148 } 149 150 for _, a := range addrs { 151 var aa, ab *btc.BtcAddr 152 var e error 153 var pubkey []byte 154 var as string 155 156 if len(a) == 66 && a[0] == '0' && (a[1] == '2' || a[1] == '3') { 157 pubkey, e = hex.DecodeString(a) // raw public key 158 if e != nil || len(pubkey) != 33 { 159 continue 160 } 161 if aa = btc.NewAddrFromPubkey(pubkey, btc.AddrVerPubkey(common.Testnet)); aa == nil { 162 continue 163 } 164 } else { 165 // bitcoin address (of some sort) 166 if aa, e = btc.NewAddrFromString(a); e != nil { 167 continue 168 } 169 } 170 171 unsp := wallet.GetAllUnspent(aa) 172 newrec := new(OneOuts) 173 if len(unsp) > 0 { 174 newrec.OutCnt = len(unsp) 175 as = aa.String() 176 for _, u := range unsp { 177 newrec.Value += u.Value 178 network.TxMutex.Lock() 179 _, spending := network.SpentOutputs[u.TxPrevOut.UIdx()] 180 network.TxMutex.Unlock() 181 if spending { 182 newrec.SpendingValue += u.Value 183 newrec.SpendingCnt++ 184 } 185 if !summary { 186 txid := btc.NewUint256(u.TxPrevOut.Hash[:]) 187 var rawtx string 188 if getrawtx { 189 dat, er := common.GetRawTx(uint32(u.MinedAt), txid) 190 if er == nil { 191 rawtx = hex.EncodeToString(dat) 192 } 193 } 194 newrec.Outs = append(newrec.Outs, OneOut{ 195 TxId: btc.NewUint256(u.TxPrevOut.Hash[:]).String(), Vout: u.Vout, 196 Value: u.Value, Height: u.MinedAt, Coinbase: u.Coinbase, 197 Message: html.EscapeString(string(u.Message)), Addr: as, Spending: spending, 198 RawTx: rawtx, AddrType: getaddrtype(aa)}) 199 } 200 } 201 } 202 203 out[a] = newrec 204 205 if mempool { 206 addr_map[string(aa.OutScript())] = a 207 } 208 209 /* For P2KH addr, we also check its segwit's P2SH-P2WPKH and Native P2WPKH */ 210 if aa.SegwitProg == nil && aa.Version == btc.AddrVerPubkey(common.Testnet) { 211 p2kh := aa.Hash160 212 213 // P2SH SegWit if applicable 214 h160 := btc.Rimp160AfterSha256(append([]byte{0, 20}, p2kh[:]...)) 215 ab = btc.NewAddrFromHash160(h160[:], btc.AddrVerScript(common.Testnet)) 216 as = ab.String() 217 newrec.SegWitAddr = as 218 unsp = wallet.GetAllUnspent(ab) 219 if len(unsp) > 0 { 220 newrec.OutCnt += len(unsp) 221 newrec.SegWitCnt = len(unsp) 222 for _, u := range unsp { 223 newrec.Value += u.Value 224 network.TxMutex.Lock() 225 _, spending := network.SpentOutputs[u.TxPrevOut.UIdx()] 226 network.TxMutex.Unlock() 227 if spending { 228 newrec.SpendingValue += u.Value 229 newrec.SpendingCnt++ 230 } 231 if !summary { 232 txid := btc.NewUint256(u.TxPrevOut.Hash[:]) 233 var rawtx string 234 if getrawtx { 235 dat, er := common.GetRawTx(uint32(u.MinedAt), txid) 236 if er == nil { 237 rawtx = hex.EncodeToString(dat) 238 } 239 } 240 newrec.Outs = append(newrec.Outs, OneOut{ 241 TxId: txid.String(), Vout: u.Vout, 242 Value: u.Value, Height: u.MinedAt, Coinbase: u.Coinbase, 243 Message: html.EscapeString(string(u.Message)), Addr: as, 244 Spending: spending, RawTx: rawtx, AddrType: AT_P2SH_P2WPKH}) 245 } 246 } 247 } 248 if mempool { 249 addr_map[string(ab.OutScript())] = a 250 } 251 252 // Native SegWit if applicable 253 ab = btc.NewAddrFromPkScript(append([]byte{0, 20}, p2kh[:]...), common.Testnet) 254 as = ab.String() 255 newrec.SegWitNativeAddr = as 256 unsp = wallet.GetAllUnspent(ab) 257 if len(unsp) > 0 { 258 newrec.OutCnt += len(unsp) 259 newrec.SegWitNativeCnt = len(unsp) 260 for _, u := range unsp { 261 newrec.Value += u.Value 262 network.TxMutex.Lock() 263 _, spending := network.SpentOutputs[u.TxPrevOut.UIdx()] 264 network.TxMutex.Unlock() 265 if spending { 266 newrec.SpendingValue += u.Value 267 newrec.SpendingCnt++ 268 } 269 if !summary { 270 txid := btc.NewUint256(u.TxPrevOut.Hash[:]) 271 var rawtx string 272 if getrawtx { 273 dat, er := common.GetRawTx(uint32(u.MinedAt), txid) 274 if er == nil { 275 rawtx = hex.EncodeToString(dat) 276 } 277 } 278 newrec.Outs = append(newrec.Outs, OneOut{ 279 TxId: txid.String(), Vout: u.Vout, 280 Value: u.Value, Height: u.MinedAt, Coinbase: u.Coinbase, 281 Message: html.EscapeString(string(u.Message)), Addr: as, 282 Spending: spending, RawTx: rawtx, AddrType: AT_P2WPKH}) 283 } 284 } 285 } 286 if mempool { 287 addr_map[string(ab.OutScript())] = a 288 } 289 290 // Also Check PAY2TAP, if pubkey mode... 291 if pubkey != nil { 292 if ab = btc.NewAddrFromPubkey(pubkey, btc.AddrVerPubkey(common.Testnet)); ab == nil { 293 continue 294 } 295 ab.SegwitProg = &btc.SegwitProg{HRP: btc.GetSegwitHRP(common.Testnet), Version: 1, Program: pubkey[1:]} 296 as = ab.String() 297 newrec.SegWitTapAddr = as 298 unsp = wallet.GetAllUnspent(ab) 299 if len(unsp) > 0 { 300 newrec.OutCnt += len(unsp) 301 newrec.SegWitTapCnt = len(unsp) 302 for _, u := range unsp { 303 newrec.Value += u.Value 304 network.TxMutex.Lock() 305 _, spending := network.SpentOutputs[u.TxPrevOut.UIdx()] 306 network.TxMutex.Unlock() 307 if spending { 308 newrec.SpendingValue += u.Value 309 newrec.SpendingCnt++ 310 } 311 if !summary { 312 txid := btc.NewUint256(u.TxPrevOut.Hash[:]) 313 var rawtx string 314 if getrawtx { 315 dat, er := common.GetRawTx(uint32(u.MinedAt), txid) 316 if er == nil { 317 rawtx = hex.EncodeToString(dat) 318 } 319 } 320 newrec.Outs = append(newrec.Outs, OneOut{ 321 TxId: txid.String(), Vout: u.Vout, 322 Value: u.Value, Height: u.MinedAt, Coinbase: u.Coinbase, 323 Message: html.EscapeString(string(u.Message)), Addr: as, 324 Spending: spending, RawTx: rawtx, AddrType: AT_P2TAP}) 325 } 326 } 327 } 328 if mempool { 329 addr_map[string(ab.OutScript())] = a 330 } 331 } 332 } 333 } 334 335 // check memory pool 336 if mempool { 337 network.TxMutex.Lock() 338 for _, t2s := range network.TransactionsToSend { 339 for vo, to := range t2s.TxOut { 340 if a, ok := addr_map[string(to.Pk_script)]; ok { 341 var tpo btc.TxPrevOut 342 tpo.Hash = t2s.Hash.Hash 343 tpo.Vout = uint32(vo) 344 newrec := out[a] 345 newrec.PendingValue += to.Value 346 newrec.PendingCnt++ 347 _, spending := network.SpentOutputs[tpo.UIdx()] 348 if spending { 349 newrec.SpendingValue += to.Value 350 newrec.SpendingCnt++ 351 } 352 if !summary { 353 po := &btc.TxPrevOut{Hash: t2s.Hash.Hash, Vout: uint32(vo)} 354 _, spending := network.SpentOutputs[po.UIdx()] 355 newrec.PendingOuts = append(newrec.PendingOuts, OneOut{ 356 TxId: t2s.Hash.String(), Vout: uint32(vo), 357 Value: to.Value, Spending: spending}) 358 } 359 } 360 } 361 } 362 network.TxMutex.Unlock() 363 } 364 365 lck.Out.Done() 366 367 bx, er := json.Marshal(out) 368 if er == nil { 369 w.Header()["Content-Type"] = []string{"application/json"} 370 w.Write(bx) 371 } else { 372 println(er.Error()) 373 } 374 } 375 376 func dl_balance(w http.ResponseWriter, r *http.Request) { 377 if !ipchecker(r) || !common.GetBool(&common.WalletON) { 378 return 379 } 380 381 if r.Method != "POST" { 382 return 383 } 384 385 var addrs []string 386 var labels []string 387 388 if len(r.Form["addrcnt"]) != 1 { 389 println("no addrcnt") 390 return 391 } 392 addrcnt, _ := strconv.ParseUint(r.Form["addrcnt"][0], 10, 32) 393 394 for i := 0; i < int(addrcnt); i++ { 395 is := fmt.Sprint(i) 396 if len(r.Form["addr"+is]) == 1 { 397 addrs = append(addrs, r.Form["addr"+is][0]) 398 if len(r.Form["label"+is]) == 1 { 399 labels = append(labels, r.Form["label"+is][0]) 400 } else { 401 labels = append(labels, "") 402 } 403 } 404 } 405 406 var thisbal utxo.AllUnspentTx 407 408 lck := new(usif.OneLock) 409 lck.In.Add(1) 410 lck.Out.Add(1) 411 usif.LocksChan <- lck 412 lck.In.Wait() 413 414 for idx, a := range addrs { 415 if aa, e := btc.NewAddrFromString(a); e == nil { 416 aa.Extra.Label = labels[idx] 417 newrecs := wallet.GetAllUnspent(aa) 418 if len(newrecs) > 0 { 419 thisbal = append(thisbal, newrecs...) 420 } 421 422 /* Segwit P2WPKH: */ 423 if aa.SegwitProg == nil && aa.Version == btc.AddrVerPubkey(common.Testnet) { 424 p2kh := aa.Hash160 425 426 // P2SH SegWit if applicable 427 h160 := btc.Rimp160AfterSha256(append([]byte{0, 20}, aa.Hash160[:]...)) 428 aa = btc.NewAddrFromHash160(h160[:], btc.AddrVerScript(common.Testnet)) 429 newrecs = wallet.GetAllUnspent(aa) 430 if len(newrecs) > 0 { 431 thisbal = append(thisbal, newrecs...) 432 } 433 434 // Native SegWit if applicable 435 aa = btc.NewAddrFromPkScript(append([]byte{0, 20}, p2kh[:]...), common.Testnet) 436 newrecs = wallet.GetAllUnspent(aa) 437 if len(newrecs) > 0 { 438 thisbal = append(thisbal, newrecs...) 439 } 440 } 441 } 442 } 443 lck.Out.Done() 444 445 buf := new(bytes.Buffer) 446 zi := zip.NewWriter(buf) 447 was_tx := make(map[[32]byte]bool) 448 449 sort.Sort(thisbal) 450 for i := range thisbal { 451 if was_tx[thisbal[i].TxPrevOut.Hash] { 452 continue 453 } 454 was_tx[thisbal[i].TxPrevOut.Hash] = true 455 txid := btc.NewUint256(thisbal[i].TxPrevOut.Hash[:]) 456 fz, _ := zi.Create("balance/" + txid.String() + ".tx") 457 if dat, er := common.GetRawTx(thisbal[i].MinedAt, txid); er == nil { 458 fz.Write(dat) 459 } else { 460 println(er.Error()) 461 } 462 } 463 464 fz, _ := zi.Create("balance/unspent.txt") 465 for i := range thisbal { 466 fmt.Fprintln(fz, thisbal[i].UnspentTextLine()) 467 } 468 469 zi.Close() 470 w.Header()["Content-Type"] = []string{"application/zip"} 471 w.Write(buf.Bytes()) 472 473 } 474 475 func json_wallet_status(w http.ResponseWriter, r *http.Request) { 476 if !ipchecker(r) { 477 return 478 } 479 480 var out struct { 481 WalletON bool 482 WalletProgress uint32 483 WalletOnIn uint32 484 } 485 common.LockCfg() 486 out.WalletON = common.WalletON 487 out.WalletProgress = common.WalletProgress 488 out.WalletOnIn = common.WalletOnIn 489 common.UnlockCfg() 490 491 bx, er := json.Marshal(out) 492 if er == nil { 493 w.Header()["Content-Type"] = []string{"application/json"} 494 w.Write(bx) 495 } else { 496 println(er.Error()) 497 } 498 }