Skip to content

Instantly share code, notes, and snippets.

@solipsis
Created December 21, 2017 19:36
Show Gist options
  • Save solipsis/7d72c2b53bab76654ed1512d5893117a to your computer and use it in GitHub Desktop.
Save solipsis/7d72c2b53bab76654ed1512d5893117a to your computer and use it in GitHub Desktop.
Get BTC or BCH balance from xpub
package main
import (
"encoding/json"
"errors"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil/hdkeychain"
)
const API_KEY = "REPLACE_ME"
// Index for receiving/change addresses
const (
ReceivingIndex = 0
ChangeIndex = 1
)
// the balance and whether transactions have happened are all we care about
type Response struct {
Balance int `json:"balance"`
TxIn int `json:"total_transactions_in"`
}
// TODO: More coin types
var bch = flag.Bool("bch", false, "Scan bitcoin cash instead of bitcoin")
func main() {
flag.Parse()
if len(flag.Args()) < 1 {
log.Fatal("please provide the xpub as an argument")
}
// read xpub from stdin
ek, err := xpubToEk(flag.Args()[0])
if err != nil {
log.Fatal(err)
}
receivingKey := generateChild(ek, ReceivingIndex)
changeKey := generateChild(ek, ChangeIndex)
// keep cheking balances until gaplimit exceeded
balance := 0
log.Println("Fetching receiving balances")
getBalanceUntilGap(receivingKey, &balance)
log.Println("Fetching change balances")
getBalanceUntilGap(changeKey, &balance)
fmt.Println("Final balance:", balance)
}
func getBalanceUntilGap(ek *hdkeychain.ExtendedKey, balance *int) {
// keep cheking balances until gaplimit exceeded
gap := 20
for i := 0; gap > 0; i, gap = i+1, gap-1 {
val, txs := getBalance(generateChild(ek, i))
*balance += val
// reset the gap check if this address has transactions
if txs > 0 {
gap = 20
}
}
}
// convert xpub to an extended public key
func xpubToEk(xpub string) (*hdkeychain.ExtendedKey, error) {
ek, err := hdkeychain.NewKeyFromString(xpub)
if err != nil {
return ek, errors.New("Invalid XPUB")
}
return ek, nil
}
// generate child extended key
func generateChild(ek *hdkeychain.ExtendedKey, n int) *hdkeychain.ExtendedKey {
child, err := ek.Child(uint32(n))
if err != nil {
log.Fatal("error generating child key")
}
return child
}
// get the balance at an address
func getBalance(ek *hdkeychain.ExtendedKey) (int, int) {
coinType := "BTC"
if *bch {
coinType = "BCC"
}
// TODO: adjust net configs when using coins other than BTC and BCH
net := &chaincfg.Params{
PubKeyHashAddrID: 0x00,
}
addr, err := ek.Address(net)
if err != nil {
log.Fatal("unable to convert ek to address:", err)
}
url := "https://api.blocktrail.com/v1/" + coinType + "/address/" + addr.EncodeAddress() + "?api_key=" + API_KEY
resp, err := http.Get(url)
if err != nil {
log.Fatal("Error getting balance for key:", ek, err)
}
// Read and parse the response
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
balance := Response{}
err = json.Unmarshal(body, &balance)
if err != nil {
log.Fatal(err)
}
fmt.Println("Balance at address:", addr.EncodeAddress(), "val:", balance.Balance)
return balance.Balance, balance.TxIn
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment