github.com/piotrnar/gocoin@v0.0.0-20240512203912-faa0448c5e96/lib/others/utils/unspent.go (about) 1 package utils 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "io" 8 "net/http" 9 "os" 10 "time" 11 12 "github.com/piotrnar/gocoin/lib/btc" 13 "github.com/piotrnar/gocoin/lib/utxo" 14 ) 15 16 func GetUnspentFromExplorer(addr *btc.BtcAddr, testnet bool) (res utxo.AllUnspentTx, er error) { 17 var r *http.Response 18 if testnet { 19 r, er = http.Get("https://testnet.blockexplorer.com/api/addr/" + addr.String() + "/utxo") 20 } else { 21 r, er = http.Get("https://blockexplorer.com/api/addr/" + addr.String() + "/utxo") 22 } 23 if er != nil { 24 return 25 } 26 if r.StatusCode != 200 { 27 er = errors.New(fmt.Sprint("HTTP StatusCode ", r.StatusCode)) 28 return 29 } 30 31 c, _ := io.ReadAll(r.Body) 32 r.Body.Close() 33 34 var result []struct { 35 Addr string `json:"address"` 36 TxID string `json:"txid"` 37 Vout uint32 `json:"vout"` 38 Value uint64 `json:"satoshis"` 39 Height uint32 `json:"height"` 40 } 41 42 er = json.Unmarshal(c, &result) 43 if er != nil { 44 return 45 } 46 47 for _, r := range result { 48 ur := new(utxo.OneUnspentTx) 49 id := btc.NewUint256FromString(r.TxID) 50 if id == nil { 51 er = errors.New(fmt.Sprint("Bad TXID:", r.TxID)) 52 return 53 } 54 copy(ur.TxPrevOut.Hash[:], id.Hash[:]) 55 ur.TxPrevOut.Vout = r.Vout 56 ur.Value = r.Value 57 ur.MinedAt = r.Height 58 ur.BtcAddr = addr 59 res = append(res, ur) 60 } 61 62 return 63 } 64 65 func GetUnspentFromBlockchainInfo(addr *btc.BtcAddr) (res utxo.AllUnspentTx, er error) { 66 var r *http.Response 67 r, er = http.Get("https://blockchain.info/unspent?active=" + addr.String()) 68 if er != nil { 69 return 70 } 71 if r.StatusCode != 200 { 72 er = errors.New(fmt.Sprint("HTTP StatusCode ", r.StatusCode)) 73 return 74 } 75 76 c, _ := io.ReadAll(r.Body) 77 r.Body.Close() 78 79 var result struct { 80 U []struct { 81 TxID string `json:"tx_hash_big_endian"` 82 Vout uint32 `json:"tx_output_n"` 83 Value uint64 `json:"value"` 84 } `json:"unspent_outputs"` 85 } 86 87 er = json.Unmarshal(c, &result) 88 if er != nil { 89 return 90 } 91 92 for _, r := range result.U { 93 ur := new(utxo.OneUnspentTx) 94 id := btc.NewUint256FromString(r.TxID) 95 if id == nil { 96 er = errors.New(fmt.Sprint("Bad TXID:", r.TxID)) 97 return 98 } 99 copy(ur.TxPrevOut.Hash[:], id.Hash[:]) 100 ur.TxPrevOut.Vout = r.Vout 101 ur.Value = r.Value 102 //ur.MinedAt = r.Height 103 ur.BtcAddr = addr 104 res = append(res, ur) 105 } 106 107 return 108 } 109 110 func GetUnspentFromBlockcypher(addr *btc.BtcAddr, currency string) (res utxo.AllUnspentTx, er error) { 111 var r *http.Response 112 var try_cnt int 113 114 token := os.Getenv("BLOCKCYPHER_TOKEN") 115 if token == "" { 116 println("WARNING: BLOCKCYPHER_TOKEN envirionment variable not set (get it from blockcypher.com)") 117 } else { 118 token = "&token=" + token 119 } 120 121 for { 122 r, er = http.Get("https://api.blockcypher.com/v1/" + currency + "/main/addrs/" + addr.String() + "?unspentOnly=true" + token) 123 124 if er != nil { 125 return 126 } 127 if r.StatusCode == 429 && try_cnt < 5 { 128 try_cnt++ 129 println("Retry blockcypher.com in", try_cnt, "seconds...") 130 time.Sleep(time.Duration(try_cnt) * time.Second) 131 continue 132 } 133 134 if r.StatusCode != 200 { 135 er = errors.New(fmt.Sprint("HTTP StatusCode ", r.StatusCode)) 136 return 137 } 138 139 break 140 } 141 142 c, _ := io.ReadAll(r.Body) 143 r.Body.Close() 144 145 var result struct { 146 Addr string `json:"address"` 147 Outs []struct { 148 TxID string `json:"tx_hash"` 149 Vout uint32 `json:"tx_output_n"` 150 Value uint64 `json:"value"` 151 Height uint32 `json:"block_height"` 152 } `json:"txrefs"` 153 } 154 155 er = json.Unmarshal(c, &result) 156 if er != nil { 157 return 158 } 159 160 for _, r := range result.Outs { 161 ur := new(utxo.OneUnspentTx) 162 id := btc.NewUint256FromString(r.TxID) 163 if id == nil { 164 er = errors.New(fmt.Sprint("Bad TXID:", r.TxID)) 165 return 166 } 167 copy(ur.TxPrevOut.Hash[:], id.Hash[:]) 168 ur.TxPrevOut.Vout = r.Vout 169 ur.Value = r.Value 170 ur.MinedAt = r.Height 171 ur.BtcAddr = addr 172 res = append(res, ur) 173 } 174 175 return 176 } 177 178 // currency is either "bitcoin" or "bitcoin-cash" 179 func GetUnspentFromBlockchair(addr *btc.BtcAddr, currency string) (res utxo.AllUnspentTx, er error) { 180 var r *http.Response 181 var try_cnt int 182 183 for { 184 // https://api.blockchair.com/bitcoin/outputs?q=is_spent(false),recipient(bc1qdvpxmyvyu9urhadl6sk69gcjsfqsvrjsqfk5aq) 185 r, er = http.Get("https://api.blockchair.com/" + currency + "/outputs?q=is_spent(false),recipient(" + addr.String() + ")") 186 187 if er != nil { 188 return 189 } 190 if (r.StatusCode == 402 || r.StatusCode == 429) && try_cnt < 5 { 191 try_cnt++ 192 println("Retry blockchair.com in", try_cnt, "seconds...") 193 time.Sleep(time.Duration(try_cnt) * time.Second) 194 continue 195 } 196 if r.StatusCode != 200 { 197 er = errors.New(fmt.Sprint("HTTP StatusCode ", r.StatusCode)) 198 return 199 } 200 break 201 } 202 203 c, _ := io.ReadAll(r.Body) 204 r.Body.Close() 205 206 var result struct { 207 Outs []struct { 208 TxID string `json:"transaction_hash"` 209 Vout uint32 `json:"index"` 210 Value uint64 `json:"value"` 211 Height uint32 `json:"block_id"` 212 Spent bool `json:"is_spent"` 213 } `json:"data"` 214 } 215 216 er = json.Unmarshal(c, &result) 217 if er != nil { 218 return 219 } 220 221 for _, r := range result.Outs { 222 if r.Spent { 223 continue 224 } 225 ur := new(utxo.OneUnspentTx) 226 id := btc.NewUint256FromString(r.TxID) 227 if id == nil { 228 er = errors.New(fmt.Sprint("Bad TXID:", r.TxID)) 229 return 230 } 231 copy(ur.TxPrevOut.Hash[:], id.Hash[:]) 232 ur.TxPrevOut.Vout = r.Vout 233 ur.Value = r.Value 234 ur.MinedAt = r.Height 235 ur.BtcAddr = addr 236 res = append(res, ur) 237 } 238 239 return 240 } 241 242 func GetUnspentFromBlockstream(addr *btc.BtcAddr, api_url string) (res utxo.AllUnspentTx, er error) { 243 var r *http.Response 244 245 r, er = http.Get(api_url + addr.String() + "/utxo") 246 247 if er != nil { 248 return 249 } 250 if r.StatusCode != 200 { 251 er = errors.New(fmt.Sprint("HTTP StatusCode ", r.StatusCode)) 252 return 253 } 254 255 c, _ := io.ReadAll(r.Body) 256 r.Body.Close() 257 258 var result []struct { 259 TxID string `json:"txid"` 260 Vout uint32 `json:"vout"` 261 Status struct { 262 Confirmed bool `json:"confirmed"` 263 Height uint32 `json:"block_height"` 264 } `json:"status"` 265 Value uint64 `json:"value"` 266 } 267 268 er = json.Unmarshal(c, &result) 269 if er != nil { 270 return 271 } 272 273 for _, r := range result { 274 if !r.Status.Confirmed { 275 continue 276 } 277 ur := new(utxo.OneUnspentTx) 278 id := btc.NewUint256FromString(r.TxID) 279 if id == nil { 280 er = errors.New(fmt.Sprint("Bad TXID:", r.TxID)) 281 return 282 } 283 copy(ur.TxPrevOut.Hash[:], id.Hash[:]) 284 ur.TxPrevOut.Vout = r.Vout 285 ur.Value = r.Value 286 ur.MinedAt = r.Status.Height 287 ur.BtcAddr = addr 288 res = append(res, ur) 289 } 290 291 return 292 } 293 294 func GetUnspent(addr *btc.BtcAddr) (res utxo.AllUnspentTx) { 295 var er error 296 297 res, er = GetUnspentFromBlockstream(addr, "https://blockstream.info/api/address/") 298 if er == nil { 299 return 300 } 301 println("GetUnspentFromBlockstream:", er.Error()) 302 303 res, er = GetUnspentFromBlockchair(addr, "bitcoin") 304 if er == nil { 305 return 306 } 307 println("GetUnspentFromBlockchair:", er.Error()) 308 309 res, er = GetUnspentFromBlockcypher(addr, "btc") 310 if er == nil { 311 return 312 } 313 println("GetUnspentFromBlockcypher:", er.Error()) 314 315 res, er = GetUnspentFromBlockchainInfo(addr) 316 if er == nil { 317 return 318 } 319 println("GetUnspentFromBlockchainInfo:", er.Error()) 320 321 res, er = GetUnspentFromExplorer(addr, false) 322 if er == nil { 323 return 324 } 325 println("GetUnspentFromExplorer:", er.Error()) 326 return 327 } 328 329 func GetUnspentTestnet(addr *btc.BtcAddr) (res utxo.AllUnspentTx) { 330 var er error 331 332 res, er = GetUnspentFromBlockstream(addr, "https://blockstream.info/testnet/api/address/") 333 if er == nil { 334 return 335 } 336 println("GetUnspentFromBlockstream:", er.Error()) 337 338 res, er = GetUnspentFromExplorer(addr, true) 339 if er == nil { 340 return 341 } 342 println("GetUnspentFromExplorer:", er.Error()) 343 344 res, er = GetUnspentFromBlockcypher(addr, "btc-testnet") 345 if er == nil { 346 return 347 } 348 println("GetUnspentFromBlockcypher:", er.Error()) 349 350 return 351 }