// CalculateAddressBalance sums the amounts of all unspent transaction // outputs to a single address's pubkey hash and returns the balance // as a float64. // // If confirmations is 0, all UTXOs, even those not present in a // block (height -1), will be used to get the balance. Otherwise, // a UTXO must be in a block. If confirmations is 1 or greater, // the balance will be calculated based on how many how many blocks // include a UTXO. func (a *Account) CalculateAddressBalance(addr *btcutil.AddressPubKeyHash, confirms int) float64 { bs, err := GetCurBlock() if bs.Height == int32(btcutil.BlockHeightUnknown) || err != nil { return 0. } var bal uint64 // Measured in satoshi for _, u := range a.UtxoStore { // Utxos not yet in blocks (height -1) should only be // added if confirmations is 0. if confirmed(confirms, u.Height, bs.Height) { if bytes.Equal(addr.ScriptAddress(), u.AddrHash[:]) { bal += u.Amt } } } return float64(bal) / float64(btcutil.SatoshiPerBitcoin) }
// txToPairs creates a raw transaction sending the amounts for each // address/amount pair and fee to each address and the miner. minconf // specifies the minimum number of confirmations required before an // unspent output is eligible for spending. Leftover input funds not sent // to addr or as a fee for the miner are sent to a newly generated // address. If change is needed to return funds back to an owned // address, changeUtxo will point to a unconfirmed (height = -1, zeroed // block hash) Utxo. ErrInsufficientFunds is returned if there are not // enough eligible unspent outputs to create the transaction. func (a *Account) txToPairs(pairs map[string]int64, minconf int) (*CreatedTx, error) { // Create a new transaction which will include all input scripts. msgtx := btcwire.NewMsgTx() // Calculate minimum amount needed for inputs. var amt int64 for _, v := range pairs { // Error out if any amount is negative. if v <= 0 { return nil, ErrNonPositiveAmount } amt += v } // outputs is a tx.Pair slice representing each output that is created // by the transaction. outputs := make([]tx.Pair, 0, len(pairs)+1) // Add outputs to new tx. for addrStr, amt := range pairs { addr, err := btcutil.DecodeAddr(addrStr) if err != nil { return nil, fmt.Errorf("cannot decode address: %s", err) } // Add output to spend amt to addr. pkScript, err := btcscript.PayToAddrScript(addr) if err != nil { return nil, fmt.Errorf("cannot create txout script: %s", err) } txout := btcwire.NewTxOut(int64(amt), pkScript) msgtx.AddTxOut(txout) // Create amount, address pair and add to outputs. out := tx.Pair{ Amount: amt, PubkeyHash: addr.ScriptAddress(), } outputs = append(outputs, out) } // Get current block's height and hash. bs, err := GetCurBlock() if err != nil { return nil, err } // Make a copy of msgtx before any inputs are added. This will be // used as a starting point when trying a fee and starting over with // a higher fee if not enough was originally chosen. txNoInputs := msgtx.Copy() // These are nil/zeroed until a change address is needed, and reused // again in case a change utxo has already been chosen. var changeAddr *btcutil.AddressPubKeyHash var btcspent int64 var selectedInputs []*tx.Utxo var finalChangeUtxo *tx.Utxo // Get the number of satoshis to increment fee by when searching for // the minimum tx fee needed. fee := int64(0) for { msgtx = txNoInputs.Copy() // Select unspent outputs to be used in transaction based on the amount // neededing to sent, and the current fee estimation. inputs, btcin, err := selectInputs(a.UtxoStore, uint64(amt+fee), minconf) if err != nil { return nil, err } // Check if there are leftover unspent outputs, and return coins back to // a new address we own. var changeUtxo *tx.Utxo change := btcin - uint64(amt+fee) if change > 0 { // Create a new address to spend leftover outputs to. // Get a new change address if one has not already been found. if changeAddr == nil { changeAddr, err = a.ChangeAddress(&bs, cfg.KeypoolSize) if err != nil { return nil, fmt.Errorf("failed to get next address: %s", err) } // Mark change address as belonging to this account. MarkAddressForAccount(changeAddr.EncodeAddress(), a.Name()) } // Spend change. pkScript, err := btcscript.PayToAddrScript(changeAddr) if err != nil { return nil, fmt.Errorf("cannot create txout script: %s", err) } msgtx.AddTxOut(btcwire.NewTxOut(int64(change), pkScript)) changeUtxo = &tx.Utxo{ Amt: change, Out: tx.OutPoint{ // Hash is unset (zeroed) here and must be filled in // with the transaction hash of the complete // transaction. Index: uint32(len(pairs)), }, Height: -1, Subscript: pkScript, } copy(changeUtxo.AddrHash[:], changeAddr.ScriptAddress()) } // Selected unspent outputs become new transaction's inputs. for _, ip := range inputs { msgtx.AddTxIn(btcwire.NewTxIn((*btcwire.OutPoint)(&ip.Out), nil)) } for i, ip := range inputs { // Error is ignored as the length and network checks can never fail // for these inputs. addr, _ := btcutil.NewAddressPubKeyHash(ip.AddrHash[:], a.Wallet.Net()) privkey, err := a.AddressKey(addr) if err == wallet.ErrWalletLocked { return nil, wallet.ErrWalletLocked } else if err != nil { return nil, fmt.Errorf("cannot get address key: %v", err) } ai, err := a.AddressInfo(addr) if err != nil { return nil, fmt.Errorf("cannot get address info: %v", err) } sigscript, err := btcscript.SignatureScript(msgtx, i, ip.Subscript, btcscript.SigHashAll, privkey, ai.Compressed) if err != nil { return nil, fmt.Errorf("cannot create sigscript: %s", err) } msgtx.TxIn[i].SignatureScript = sigscript } noFeeAllowed := false if !cfg.DisallowFree { noFeeAllowed = allowFree(bs.Height, inputs, msgtx.SerializeSize()) } if minFee := minimumFee(msgtx, noFeeAllowed); fee < minFee { fee = minFee } else { // Fill Tx hash of change outpoint with transaction hash. if changeUtxo != nil { txHash, err := msgtx.TxSha() if err != nil { return nil, fmt.Errorf("cannot create transaction hash: %s", err) } copy(changeUtxo.Out.Hash[:], txHash[:]) // Add change to outputs. out := tx.Pair{ Amount: int64(change), PubkeyHash: changeAddr.ScriptAddress(), Change: true, } outputs = append(outputs, out) finalChangeUtxo = changeUtxo } selectedInputs = inputs btcspent = int64(btcin) break } } // Validate msgtx before returning the raw transaction. flags := btcscript.ScriptCanonicalSignatures bip16 := time.Now().After(btcscript.Bip16Activation) if bip16 { flags |= btcscript.ScriptBip16 } for i, txin := range msgtx.TxIn { engine, err := btcscript.NewScript(txin.SignatureScript, selectedInputs[i].Subscript, i, msgtx, flags) if err != nil { return nil, fmt.Errorf("cannot create script engine: %s", err) } if err = engine.Execute(); err != nil { return nil, fmt.Errorf("cannot validate transaction: %s", err) } } txid, err := msgtx.TxSha() if err != nil { return nil, fmt.Errorf("cannot create txid for created tx: %v", err) } buf := new(bytes.Buffer) msgtx.BtcEncode(buf, btcwire.ProtocolVersion) info := &CreatedTx{ rawTx: buf.Bytes(), txid: txid, time: time.Now(), inputs: selectedInputs, outputs: outputs, btcspent: btcspent, fee: fee, changeAddr: changeAddr, changeUtxo: finalChangeUtxo, } return info, nil }