package bitcoin import ( "fmt" "log" "os" "sync" ) 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 { return "Bitcoin - Blockstream.info" } // 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 { var err 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 p.client, err = newClient() if err != nil { return fmt.Errorf("failed to create new bitcoin client: %v", err) } 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 wg := sync.WaitGroup{} for _, bitcoinAddress := range p.bitcoinAddresses { wg.Add(1) go func() { defer wg.Done() addressResponse, err := p.client.getAddress(bitcoinAddress) if err != nil { log.Printf("failed to get bitcoin address '%s': %v", bitcoinAddress, err) } satoshiBalance += addressResponse.ChainStats.FundedTxoSum - addressResponse.ChainStats.SpentTxoSum }() } wg.Wait() fiatBalance, err := p.client.convertBTCToCAD(satoshiBalance) 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 }