2023-11-13 04:40:00 +00:00
|
|
|
package bitcoin
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"os"
|
2024-09-01 15:05:21 +00:00
|
|
|
"sync"
|
2023-11-13 04:40:00 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type Provider struct {
|
|
|
|
bitcoinAddresses []string // Slice of bitcoin addresses this provider monitors
|
|
|
|
ynabAccountID string // YNAB account ID this provider updates - all bitcoin addresses are summed up and mapped to this YNAB account
|
|
|
|
client *client // HTTP client for interacting with Questrade API
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Provider) Name() string {
|
2024-09-09 23:43:14 +00:00
|
|
|
return "Bitcoin - Blockstream.info / CoinGecko"
|
2023-11-13 04:40:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Configures the provider for usage via environment variables and persistentData
|
|
|
|
// If an error is returned, the provider will not be used
|
|
|
|
func (p *Provider) Configure() error {
|
|
|
|
// Load environment variables in continous series with suffix starting at 0
|
|
|
|
// Multiple addresses can be configured, (eg _1, _2)
|
|
|
|
// As soon as the series is interrupted, we assume we're done
|
|
|
|
p.bitcoinAddresses = make([]string, 0)
|
|
|
|
for i := 0; true; i++ {
|
|
|
|
bitcoinAddress := os.Getenv(fmt.Sprintf("bitcoin_address_%d", i))
|
|
|
|
if bitcoinAddress == "" {
|
|
|
|
if i == 0 {
|
|
|
|
return fmt.Errorf("this account provider is not configured")
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
p.bitcoinAddresses = append(p.bitcoinAddresses, bitcoinAddress)
|
|
|
|
}
|
|
|
|
p.ynabAccountID = os.Getenv("bitcoin_ynab_account")
|
|
|
|
|
|
|
|
// Create new HTTP client
|
2024-09-09 23:43:14 +00:00
|
|
|
p.client = newClient(os.Getenv("bitcoin_coingecko_api_key"))
|
2023-11-13 04:40:00 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns slices of account balances and mapped YNAB account IDs, along with an error
|
|
|
|
func (p *Provider) GetBalances() ([]int, []string, error) {
|
|
|
|
|
|
|
|
balances := make([]int, 0)
|
|
|
|
ynabAccountIDs := make([]string, 0)
|
|
|
|
var satoshiBalance int
|
2024-09-01 15:05:21 +00:00
|
|
|
wg := sync.WaitGroup{}
|
2024-11-08 20:17:01 +00:00
|
|
|
var goErr *error
|
2023-11-13 04:40:00 +00:00
|
|
|
|
2024-09-01 15:05:21 +00:00
|
|
|
for _, bitcoinAddress := range p.bitcoinAddresses {
|
|
|
|
wg.Add(1)
|
2024-11-08 20:17:01 +00:00
|
|
|
go func(goErr *error) {
|
2024-09-01 15:05:21 +00:00
|
|
|
defer wg.Done()
|
|
|
|
addressResponse, err := p.client.getAddress(bitcoinAddress)
|
|
|
|
if err != nil {
|
2024-11-08 20:17:01 +00:00
|
|
|
err := fmt.Errorf("failed to get bitcoin address '%s': %v", bitcoinAddress, err)
|
|
|
|
goErr = &err
|
2024-09-23 14:59:42 +00:00
|
|
|
return
|
2024-09-01 15:05:21 +00:00
|
|
|
}
|
|
|
|
satoshiBalance += addressResponse.ChainStats.FundedTxoSum - addressResponse.ChainStats.SpentTxoSum
|
2024-11-08 20:17:01 +00:00
|
|
|
}(goErr)
|
2023-11-13 04:40:00 +00:00
|
|
|
}
|
2024-09-01 15:05:21 +00:00
|
|
|
wg.Wait()
|
2024-11-11 13:25:26 +00:00
|
|
|
if *goErr != nil {
|
|
|
|
return nil, nil, *goErr
|
|
|
|
}
|
2024-09-01 15:05:21 +00:00
|
|
|
|
2024-03-23 20:06:38 +00:00
|
|
|
fiatBalance, err := p.client.convertBTCToCAD(satoshiBalance)
|
2023-11-13 04:40:00 +00:00
|
|
|
if err != nil {
|
|
|
|
return balances, ynabAccountIDs, fmt.Errorf("failed to convert satoshi balance to fiat balance: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
balances = append(balances, fiatBalance)
|
|
|
|
ynabAccountIDs = append(ynabAccountIDs, p.ynabAccountID)
|
|
|
|
return balances, ynabAccountIDs, nil
|
|
|
|
}
|