replace zxq.co/ripple/hanayo
This commit is contained in:
177
modules/btcaddress/btcaddress.go
Normal file
177
modules/btcaddress/btcaddress.go
Normal file
@@ -0,0 +1,177 @@
|
||||
// Package btcaddress makes sure the Bitcoin address for the Ripple donations
|
||||
// account on Keybase is up-to-date.
|
||||
// This shows a default key if the system is not set up, or otherwise refreshes
|
||||
// the address every 30 minutes (using redis to hold the cache).
|
||||
// The wallet with the name "Ripple" will be used for getting the address.
|
||||
package btcaddress
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"strings"
|
||||
|
||||
"gopkg.in/redis.v5"
|
||||
)
|
||||
|
||||
// DefaultAddress is the address used if the package is not set up to refresh
|
||||
// from coinbase.
|
||||
const DefaultAddress = "1CKGzZqrVwKoXwHEWpTobWqqtkYuqoFNro"
|
||||
|
||||
// AccountName is the name the account on coinbase must have to be considered as
|
||||
// the actual account.
|
||||
const AccountName = "Ripple"
|
||||
|
||||
// Configuration variables.
|
||||
var (
|
||||
Redis *redis.Client
|
||||
APIKey string
|
||||
APISecret string
|
||||
)
|
||||
|
||||
// Get retrieves the Bitcoin address, using the following methods:
|
||||
//
|
||||
// First the address is requested from Redis, and if the key is available from
|
||||
// redis then that is used.
|
||||
//
|
||||
// After that, the key is requested from coinbase, and the account ID is found
|
||||
// if not already saved in Redis.
|
||||
func Get() string {
|
||||
v := Redis.Get("hanayo:btcaddress").Val()
|
||||
if v != "" {
|
||||
return v
|
||||
}
|
||||
if APIKey == "" || APISecret == "" {
|
||||
return DefaultAddress
|
||||
}
|
||||
|
||||
a, err := getFromCoinbase()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return DefaultAddress
|
||||
}
|
||||
|
||||
Redis.Set("hanayo:btcaddress", a, time.Minute*30)
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
type account struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type accountsResp struct {
|
||||
Data []account `json:"data"`
|
||||
}
|
||||
|
||||
type address struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
type createAddressResp struct {
|
||||
Data address `json:"data"`
|
||||
}
|
||||
|
||||
type errors struct {
|
||||
Errors []struct {
|
||||
ID string `json:"id"`
|
||||
Message string `json:"message"`
|
||||
} `json:"errors"`
|
||||
}
|
||||
|
||||
const coinbaseAPIDate = "2017-01-23"
|
||||
const coinbaseAPIBase = "https://api.coinbase.com/v2/"
|
||||
|
||||
var accountID string
|
||||
|
||||
func getFromCoinbase() (string, error) {
|
||||
if accountID == "" {
|
||||
var accs accountsResp
|
||||
err := req("accounts", nil, &accs)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, el := range accs.Data {
|
||||
if el.Name == AccountName {
|
||||
accountID = el.ID
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var addrResp createAddressResp
|
||||
err := req("accounts/"+accountID+"/addresses", struct{}{}, &addrResp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return addrResp.Data.Address, nil
|
||||
}
|
||||
|
||||
func req(endpoint string, data interface{}, out interface{}) error {
|
||||
var dataJSON string
|
||||
method := "GET"
|
||||
if data != nil {
|
||||
method = "POST"
|
||||
// sort of a dirty hack, but oh well
|
||||
djr, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dataJSON = string(djr)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, coinbaseAPIBase+endpoint, strings.NewReader(dataJSON))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if data != nil {
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
// Not usind .Header.Add because we want to make sure the names are kept
|
||||
// uppercase.
|
||||
ts := time.Now().Unix()
|
||||
req.Header["CB-VERSION"] = []string{coinbaseAPIDate}
|
||||
req.Header["CB-ACCESS-KEY"] = []string{APIKey}
|
||||
req.Header["CB-ACCESS-TIMESTAMP"] = []string{fmt.Sprintf("%d", ts)}
|
||||
|
||||
sig := fmt.Sprintf(
|
||||
"%d%s/v2/%s%s",
|
||||
ts,
|
||||
method,
|
||||
endpoint,
|
||||
string(dataJSON),
|
||||
)
|
||||
|
||||
hm := hmac.New(
|
||||
sha256.New,
|
||||
[]byte(APISecret),
|
||||
)
|
||||
hm.Write([]byte(sig))
|
||||
|
||||
req.Header["CB-ACCESS-SIGN"] = []string{
|
||||
fmt.Sprintf("%x", hm.Sum(nil)),
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != 200 && resp.StatusCode != 201 {
|
||||
var errs errors
|
||||
json.NewDecoder(resp.Body).Decode(&errs)
|
||||
return fmt.Errorf("btcaddress: response status code is %d - %v", resp.StatusCode, errs)
|
||||
}
|
||||
|
||||
err = json.NewDecoder(resp.Body).Decode(&out)
|
||||
return err
|
||||
}
|
Reference in New Issue
Block a user