Created
December 21, 2017 19:36
-
-
Save solipsis/7d72c2b53bab76654ed1512d5893117a to your computer and use it in GitHub Desktop.
Get BTC or BCH balance from xpub
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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