func showWalletBalanceV2() error {
	c := newClient(*skipVerify)
	route := *host + v2.WalletBalanceRoute
	if *debug {
		fmt.Println(route)
	}
	url := fmt.Sprintf("%s?apitoken=%s", route, *apiToken)
	fmt.Println(url)
	request, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		return err
	}
	response, err := c.Do(request)
	if err != nil {
		return err
	}
	defer response.Body.Close()
	if *printJSON {
		io.Copy(os.Stdout, response.Body)
		fmt.Printf("\n")
		return nil
	}
	if response.StatusCode != http.StatusOK {
		e, err := getError(response.Body)
		if err != nil {
			return fmt.Errorf("Retrieve wallet balance failed: %v",
				response.Status)
		}
		return fmt.Errorf("Retrieve wallet balance failed - %v: %v",
			response.Status, e)
	}
	var balance v2.WalletBalanceReply
	jsonDecoder := json.NewDecoder(response.Body)
	if err := jsonDecoder.Decode(&balance); err != nil {
		return fmt.Errorf("Could not decode WalletBalanceReply: %v", err)
	}
	if *verbose {
		fmt.Printf(
			"Wallet balance (atoms)\n"+
				"Spendable:   %v\n"+
				"Total:       %v\n"+
				"Unconfirmed: %v\n",
			balance.Spendable, balance.Total, balance.Unconfirmed)
	} else {
		fmt.Printf("Spendable wallet balance (atoms): %v\n", balance.Spendable)
	}
	return nil
}
showWalletBalanceV2 returns the total balance of the primary dcrtimed wallet,
in atoms.
package main
import (
	"bytes"
	"crypto/sha256"
	"crypto/tls"
	"encoding/hex"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/url"
	"os"
	"reflect"
	"strconv"
	v1 "github.com/decred/dcrtime/api/v1"
	v2 "github.com/decred/dcrtime/api/v2"
	"github.com/decred/dcrtime/merkle"
	"github.com/decred/dcrtime/util"
)
const (
	dcrtimeClientID = "dcrtime cli"
)
var (
	testnet   = flag.Bool("testnet", false, "Use testnet port")
	debug     = flag.Bool("debug", false, "Print JSON that is sent to server")
	printJSON = flag.Bool("json", false, "Print JSON response from server")
	fileOnly  = flag.Bool("file", false, "Treat digests and timestamps "+
		"as file names")
	host     = flag.String("h", "", "Timestamping host")
	port     = flag.String("p", "", "Timestamping host port")
	trial    = flag.Bool("t", false, "Trial run, don't contact server")
	verbose  = flag.Bool("v", false, "Verbose")
	digest   = flag.String("digest", "", "Submit a raw 256 bit digest to anchor")
	apiToken = flag.String("apitoken", "", "Token for accessing privileged API"+
		" resources")
	balance = flag.Bool("balance", false, "Display the connected server's"+
		" wallet balance. An API Token is required")
	lastAnchor = flag.Bool("lastanchor", false, "Display last anchored"+
		" timestamp details")
	apiVersion = flag.Int("api", v2.APIVersion,
		"Inform the API version to be used by the cli (1 or 2)")
	skipVerify = flag.Bool("skipverify", false, "Skip TLS certificates"+
		"verification (not recommended)")
)
func normalizeAddress(addr, defaultPort string) string {
	_, _, err := net.SplitHostPort(addr)
	if err != nil {
		return net.JoinHostPort(addr, defaultPort)
	}
	return addr
}
func isDir(filename string) bool {
	fi, err := os.Stat(filename)
	if err != nil {
		return false
	}
	return fi.Mode().IsDir()
}
func isFile(filename string) bool {
	fi, err := os.Stat(filename)
	if err != nil {
		return false
	}
	return fi.Mode().IsRegular()
}
func isDigest(digest string) bool {
	return v2.RegexpSHA256.MatchString(digest)
}
func isTimestamp(timestamp string) bool {
	return v2.RegexpTimestamp.MatchString(timestamp)
}
func getError(r io.Reader) (string, error) {
	var e interface{}
	decoder := json.NewDecoder(r)
	if err := decoder.Decode(&e); err != nil {
		return "", err
	}
	m, ok := e.(map[string]interface{})
	if !ok {
		return "", fmt.Errorf("could not decode response")
	}
	rError, ok := m["error"]
	if !ok {
		return "", fmt.Errorf("no error response")
	}
	return fmt.Sprintf("%v", rError), nil
}
func convertTimestamp(t string) (int64, bool) {
	if !isTimestamp(t) {
		return 0, false
	}
	ts, err := strconv.ParseInt(t, 10, 64)
	if err != nil {
		return 0, false
	}
	return ts, true
}
func convertDigest(d string) ([sha256.Size]byte, bool) {
	var digest [sha256.Size]byte
	if !isDigest(d) {
		return digest, false
	}
	dd, err := hex.DecodeString(d)
	if err != nil {
		return digest, false
	}
	copy(digest[:], dd)
	return digest, true
}
func newClient(skipVerify bool) *http.Client {
	tlsConfig := &tls.Config{
		InsecureSkipVerify: skipVerify,
	}
	tr := &http.Transport{
		TLSClientConfig: tlsConfig,
	}
	return &http.Client{Transport: tr}
}
func downloadV1(questions []string) error {
	ver := v1.Verify{
		ID: dcrtimeClientID,
	}
	for _, question := range questions {
		if ts, ok := convertTimestamp(question); ok {
			ver.Timestamps = append(ver.Timestamps, ts)
			continue
		}
		if isDigest(question) {
			ver.Digests = append(ver.Digests, question)
			continue
		}
		return fmt.Errorf("not a digest or timestamp: %v", question)
	}
	b, err := json.Marshal(ver)
	if err != nil {
		return err
	}
	if *trial {
		return nil
	}
	route := *host + v1.VerifyRoute
	if *debug {
		fmt.Println(string(b))
		fmt.Println(route)
	}
	c := newClient(*skipVerify)
	r, err := c.Post(route, "application/json",
		bytes.NewReader(b))
	if err != nil {
		return err
	}
	defer r.Body.Close()
	if r.StatusCode != http.StatusOK {
		e, err := getError(r.Body)
		if err != nil {
			return fmt.Errorf("%v", r.Status)
		}
		return fmt.Errorf("%v: %v", r.Status, e)
	}
	if *printJSON {
		io.Copy(os.Stdout, r.Body)
		fmt.Printf("\n")
		return nil
	}
	var vr v1.VerifyReply
	decoder := json.NewDecoder(r.Body)
	if err := decoder.Decode(&vr); err != nil {
		return fmt.Errorf("could node decode VerifyReply: %v", err)
	}
	for _, v := range vr.Timestamps {
		result, ok := v1.Result[v.Result]
		if !ok {
			fmt.Printf("%v invalid error code %v\n", v.ServerTimestamp,
				v.Result)
			continue
		}
		if v.CollectionInformation.ChainTimestamp != 0 {
			digests := make([]*[sha256.Size]byte, 0,
				len(v.CollectionInformation.Digests))
			for _, digest := range v.CollectionInformation.Digests {
				d, ok := convertDigest(digest)
				if !ok {
					return fmt.Errorf("Invalid digest "+
						"server response for "+
						"timestamp: %v",
						v.ServerTimestamp)
				}
				digests = append(digests, &d)
			}
			root := merkle.Root(digests)
			if hex.EncodeToString(root[:]) !=
				v.CollectionInformation.MerkleRoot {
				fmt.Printf("invalid merkle root: %v\n", err)
			}
		}
		if v.CollectionInformation.ChainTimestamp == 0 &&
			v.Result == v1.ResultOK {
			result = "Not anchored"
		}
		fmt.Printf("%v %v\n", v.ServerTimestamp, result)
		if !*verbose {
			continue
		}
		prefix := "Digests"
		for _, digest := range v.CollectionInformation.Digests {
			fmt.Printf("  %-15v: %v\n", prefix, digest)
			prefix = ""
		}
		if v.CollectionInformation.ChainTimestamp == 0 {
			continue
		}
		fmt.Printf("  %-15v: %v\n", "Chain Timestamp",
			v.CollectionInformation.ChainTimestamp)
		fmt.Printf("  %-15v: %v\n", "Merkle Root",
			v.CollectionInformation.MerkleRoot)
		fmt.Printf("  %-15v: %v\n", "TxID",
			v.CollectionInformation.Transaction)
	}
	for _, v := range vr.Digests {
		result, ok := v1.Result[v.Result]
		if !ok {
			fmt.Printf("%v invalid error code %v\n", v.Digest,
				v.Result)
			continue
		}
		root, err := merkle.VerifyAuthPath(&v.ChainInformation.MerklePath)
		if err != nil {
			if err != merkle.ErrEmpty {
				fmt.Printf("%v invalid auth path %v\n",
					v.Digest, err)
				continue
			}
			fmt.Printf("%v Not anchored\n", v.Digest)
			continue
		}
		merkleRoot, err := hex.DecodeString(v.ChainInformation.MerkleRoot)
		if err != nil {
			fmt.Printf("invalid merkle root: %v\n", err)
			continue
		}
		if !bytes.Equal(root[:], merkleRoot) {
			fmt.Printf("%v invalid merkle root\n", v.Digest)
			continue
		}
		fmt.Printf("%v %v\n", v.Digest, result)
		if !*verbose {
			continue
		}
		fmt.Printf("  %-15v: %v\n", "Chain Timestamp",
			v.ChainInformation.ChainTimestamp)
		fmt.Printf("  %-15v: %v\n", "Merkle Root",
			v.ChainInformation.MerkleRoot)
		fmt.Printf("  %-15v: %v\n", "TxID",
			v.ChainInformation.Transaction)
	}
	return nil
}
func downloadV2Batch(questions []string) error {
	ver := v2.VerifyBatch{
		ID: dcrtimeClientID,
	}
	for _, question := range questions {
		if ts, ok := convertTimestamp(question); ok {
			ver.Timestamps = append(ver.Timestamps, ts)
			continue
		}
		if isDigest(question) {
			ver.Digests = append(ver.Digests, question)
			continue
		}
		return fmt.Errorf("not a digest or timestamp: %v", question)
	}
	b, err := json.Marshal(ver)
	if err != nil {
		return err
	}
	if *trial {
		return nil
	}
	route := *host + v2.VerifyBatchRoute
	if *debug {
		fmt.Println(string(b))
		fmt.Println(route)
	}
	c := newClient(*skipVerify)
	r, err := c.Post(route, "application/json",
		bytes.NewReader(b))
	if err != nil {
		return err
	}
	defer r.Body.Close()
	if r.StatusCode != http.StatusOK {
		e, err := getError(r.Body)
		if err != nil {
			return fmt.Errorf("%v", r.Status)
		}
		return fmt.Errorf("%v: %v", r.Status, e)
	}
	if *printJSON {
		io.Copy(os.Stdout, r.Body)
		fmt.Printf("\n")
		return nil
	}
	var vbr v2.VerifyBatchReply
	decoder := json.NewDecoder(r.Body)
	if err := decoder.Decode(&vbr); err != nil {
		return fmt.Errorf("could node decode VerifyReply: %v", err)
	}
	verifyDigests(vbr.Digests)
	err = verifyTimestamps(vbr.Timestamps)
	if err != nil {
		return err
	}
	return nil
}
func downloadV2Single(question string) error {
	ver := v2.Verify{
		ID: dcrtimeClientID,
	}
	formParam := url.Values{}
	if ts, ok := convertTimestamp(question); ok {
		ver.Timestamp = ts
		formParam.Set("timestamp", strconv.FormatInt(ver.Timestamp, 10))
	} else if isDigest(question) {
		ver.Digest = question
		formParam.Set("digest", ver.Digest)
	} else {
		return fmt.Errorf("not a digest or timestamp: %v", question)
	}
	if *trial {
		return nil
	}
	route := *host + v2.VerifyRoute
	if *debug {
		fmt.Println(ver)
		fmt.Println(route)
	}
	c := newClient(*skipVerify)
	r, err := c.PostForm(route, formParam)
	if err != nil {
		return err
	}
	defer r.Body.Close()
	if r.StatusCode != http.StatusOK {
		e, err := getError(r.Body)
		if err != nil {
			return fmt.Errorf("%v", r.Status)
		}
		return fmt.Errorf("%v: %v", r.Status, e)
	}
	if *printJSON {
		io.Copy(os.Stdout, r.Body)
		fmt.Printf("\n")
		return nil
	}
	var vr v2.VerifyReply
	decoder := json.NewDecoder(r.Body)
	if err := decoder.Decode(&vr); err != nil {
		return fmt.Errorf("could node decode VerifyReply: %v", err)
	}
	if !reflect.DeepEqual(vr.Digest, v2.VerifyDigest{}) {
		verifyDigests([]v2.VerifyDigest{vr.Digest})
	}
	if !reflect.DeepEqual(vr.Timestamp, v2.VerifyTimestamp{}) {
		err = verifyTimestamps([]v2.VerifyTimestamp{vr.Timestamp})
		if err != nil {
			return err
		}
	}
	return nil
}
func downloadV2(questions []string) error {
	var err error
	switch len(questions) {
	case 1:
		err = downloadV2Single(questions[0])
	default:
		err = downloadV2Batch(questions)
	}
	if err != nil {
		return err
	}
	return nil
}
func verifyDigests(vd []v2.VerifyDigest) {
	for _, d := range vd {
		result, ok := v2.Result[d.Result]
		if !ok {
			fmt.Printf("%v invalid error code %v\n", d.Digest,
				d.Result)
			continue
		}
		root, err := merkle.VerifyAuthPath(&d.ChainInformation.MerklePath)
		if err != nil {
			if err != merkle.ErrEmpty {
				fmt.Printf("%v invalid auth path %v\n",
					d.Digest, err)
				continue
			}
			fmt.Printf("%v Not anchored\n", d.Digest)
			continue
		}
		merkleRoot, err := hex.DecodeString(d.ChainInformation.MerkleRoot)
		if err != nil {
			fmt.Printf("invalid merkle root: %v\n", err)
			continue
		}
		if !bytes.Equal(root[:], merkleRoot) {
			fmt.Printf("%v invalid merkle root\n", d.Digest)
			continue
		}
		fmt.Printf("%v %v\n", d.Digest, result)
		if !*verbose {
			continue
		}
		fmt.Printf("  %-15v: %v\n", "Chain Timestamp",
			d.ChainInformation.ChainTimestamp)
		fmt.Printf("  %-15v: %v\n", "Merkle Root",
			d.ChainInformation.MerkleRoot)
		fmt.Printf("  %-15v: %v\n", "TxID",
			d.ChainInformation.Transaction)
	}
}
func verifyTimestamps(vt []v2.VerifyTimestamp) error {
	for _, t := range vt {
		result, ok := v2.Result[t.Result]
		if !ok {
			fmt.Printf("%v invalid error code %v\n", t.ServerTimestamp,
				t.Result)
			continue
		}
		if t.CollectionInformation.ChainTimestamp != 0 {
			digests := make([]*[sha256.Size]byte, 0,
				len(t.CollectionInformation.Digests))
			for _, digest := range t.CollectionInformation.Digests {
				d, ok := convertDigest(digest)
				if !ok {
					return fmt.Errorf("Invalid digest "+
						"server response for "+
						"timestamp: %v",
						t.ServerTimestamp)
				}
				digests = append(digests, &d)
			}
			root := merkle.Root(digests)
			if hex.EncodeToString(root[:]) !=
				t.CollectionInformation.MerkleRoot {
				fmt.Printf("invalid merkle root\n")
			}
		}
		if t.CollectionInformation.ChainTimestamp == 0 &&
			t.Result == v2.ResultOK {
			result = "Not anchored"
		}
		fmt.Printf("%v %v\n", t.ServerTimestamp, result)
		if !*verbose {
			continue
		}
		prefix := "Digests"
		for _, digest := range t.CollectionInformation.Digests {
			fmt.Printf("  %-15v: %v\n", prefix, digest)
			prefix = ""
		}
		if t.CollectionInformation.ChainTimestamp == 0 {
			continue
		}
		fmt.Printf("  %-15v: %v\n", "Chain Timestamp",
			t.CollectionInformation.ChainTimestamp)
		fmt.Printf("  %-15v: %v\n", "Merkle Root",
			t.CollectionInformation.MerkleRoot)
		fmt.Printf("  %-15v: %v\n", "TxID",
			t.CollectionInformation.Transaction)
	}
	return nil
}
func uploadV1(digests []string, exists map[string]string) error {
	ts := v1.Timestamp{
		ID:      dcrtimeClientID,
		Digests: digests,
	}
	b, err := json.Marshal(ts)
	if err != nil {
		return err
	}
	if *trial {
		return nil
	}
	route := *host + v1.TimestampRoute
	if *debug {
		fmt.Println(string(b))
		fmt.Println(route)
	}
	c := newClient(*skipVerify)
	r, err := c.Post(route, "application/json",
		bytes.NewReader(b))
	if err != nil {
		return err
	}
	defer r.Body.Close()
	if r.StatusCode != http.StatusOK {
		e, err := getError(r.Body)
		if err != nil {
			return fmt.Errorf("%v", r.Status)
		}
		return fmt.Errorf("%v: %v", r.Status, e)
	}
	if *printJSON {
		io.Copy(os.Stdout, r.Body)
		fmt.Printf("\n")
		return nil
	}
	var tsReply v1.TimestampReply
	decoder := json.NewDecoder(r.Body)
	if err := decoder.Decode(&tsReply); err != nil {
		return fmt.Errorf("Could node decode TimestampReply: %v", err)
	}
	for k, v := range tsReply.Results {
		filename := exists[tsReply.Digests[k]]
		if v == v1.ResultOK {
			fmt.Printf("%v OK %v\n", tsReply.Digests[k], filename)
			continue
		}
		fmt.Printf("%v Exists %v\n", tsReply.Digests[k], filename)
	}
	if *verbose {
		fmt.Printf("Collection timestamp: %v\n", tsReply.ServerTimestamp)
	}
	return nil
}
func uploadV2Batch(digests []string, exists map[string]string) error {
	ts := v2.TimestampBatch{
		ID:      dcrtimeClientID,
		Digests: digests,
	}
	b, err := json.Marshal(ts)
	if err != nil {
		return err
	}
	if *trial {
		return nil
	}
	route := *host + v2.TimestampBatchRoute
	if *debug {
		fmt.Println(string(b))
		fmt.Println(route)
	}
	c := newClient(*skipVerify)
	r, err := c.Post(route, "application/json",
		bytes.NewReader(b))
	if err != nil {
		return err
	}
	defer r.Body.Close()
	if r.StatusCode != http.StatusOK {
		e, err := getError(r.Body)
		if err != nil {
			return fmt.Errorf("%v", r.Status)
		}
		return fmt.Errorf("%v: %v", r.Status, e)
	}
	if *printJSON {
		io.Copy(os.Stdout, r.Body)
		fmt.Printf("\n")
		return nil
	}
	var tsReply v2.TimestampBatchReply
	decoder := json.NewDecoder(r.Body)
	if err := decoder.Decode(&tsReply); err != nil {
		return fmt.Errorf("Could node decode TimestampReply: %v", err)
	}
	for k, v := range tsReply.Results {
		filename := exists[tsReply.Digests[k]]
		if v == v2.ResultOK {
			fmt.Printf("%v OK %v\n", tsReply.Digests[k], filename)
			continue
		}
		fmt.Printf("%v Exists %v\n", tsReply.Digests[k], filename)
	}
	if *verbose {
		fmt.Printf("Collection timestamp: %v\n", tsReply.ServerTimestamp)
	}
	return nil
}
func uploadV2Single(digest string, exists map[string]string) error {
	ts := v2.Timestamp{
		ID:     dcrtimeClientID,
		Digest: digest,
	}
	formParam := url.Values{}
	formParam.Set("digest", digest)
	if *trial {
		return nil
	}
	route := *host + v2.TimestampRoute
	if *debug {
		fmt.Println(ts)
		fmt.Println(route)
	}
	c := newClient(*skipVerify)
	r, err := c.PostForm(route, formParam)
	if err != nil {
		return err
	}
	defer r.Body.Close()
	if r.StatusCode != http.StatusOK {
		e, err := getError(r.Body)
		if err != nil {
			return fmt.Errorf("%v", r.Status)
		}
		return fmt.Errorf("%v: %v", r.Status, e)
	}
	if *printJSON {
		io.Copy(os.Stdout, r.Body)
		fmt.Printf("\n")
		return nil
	}
	var tsReply v2.TimestampReply
	decoder := json.NewDecoder(r.Body)
	if err := decoder.Decode(&tsReply); err != nil {
		return fmt.Errorf("Could node decode TimestampReply: %v", err)
	}
	filename := exists[tsReply.Digest]
	if tsReply.Result == v2.ResultOK {
		fmt.Printf("%v OK %v\n", tsReply.Digest, filename)
	} else {
		fmt.Printf("%v Exists %v\n", tsReply.Digest, filename)
	}
	if *verbose {
		fmt.Printf("Collection timestamp: %v\n", tsReply.ServerTimestamp)
	}
	return nil
}
func uploadV2(digests []string, exists map[string]string) error {
	var err error
	switch len(digests) {
	case 1:
		err = uploadV2Single(digests[0], exists)
	default:
		err = uploadV2Batch(digests, exists)
	}
	if err != nil {
		return err
	}
	return nil
}
func showWalletBalanceV1() error {
	c := newClient(*skipVerify)
	route := *host + v1.WalletBalanceRoute
	if *debug {
		fmt.Println(route)
	}
	url := fmt.Sprintf("%s?apitoken=%s", route, *apiToken)
	request, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		return err
	}
	response, err := c.Do(request)
	if err != nil {
		return err
	}
	defer response.Body.Close()
	if *printJSON {
		io.Copy(os.Stdout, response.Body)
		fmt.Printf("\n")
		return nil
	}
	if response.StatusCode != http.StatusOK {
		e, err := getError(response.Body)
		if err != nil {
			return fmt.Errorf("Retrieve wallet balance failed: %v",
				response.Status)
		}
		return fmt.Errorf("Retrieve wallet balance failed - %v: %v",
			response.Status, e)
	}
	var balance v1.WalletBalanceReply
	jsonDecoder := json.NewDecoder(response.Body)
	if err := jsonDecoder.Decode(&balance); err != nil {
		return fmt.Errorf("Could not decode WalletBalanceReply: %v", err)
	}
	if *verbose {
		fmt.Printf(
			"Wallet balance (atoms)\n"+
				"Spendable:   %v\n"+
				"Total:       %v\n"+
				"Unconfirmed: %v\n",
			balance.Spendable, balance.Total, balance.Unconfirmed)
	} else {
		fmt.Printf("Spendable wallet balance (atoms): %v\n", balance.Spendable)
	}
	return nil
}