diff --git a/acctmgr.go b/acctmgr.go index 84c72c5..9d0af21 100644 --- a/acctmgr.go +++ b/acctmgr.go @@ -496,6 +496,68 @@ func (am *AccountManager) GetTransaction(txid string) []accountTx { return accumulatedTxen } +// ListUnspent returns an array of objects representing the unspent +// wallet transactions fitting the given criteria. The confirmations will be +// more then minconf, less than maxconf and if addresses is populated only the +// addresses contained within it will be considered. +// a transaction on locally known wallets. If we know nothing about a +// transaction an empty array will be returned. +func (am *AccountManager) ListUnspent(minconf, maxconf int, + addresses map[string]bool) ([]map[string]interface{}, error) { + bs, err := GetCurBlock() + if err != nil { + return nil, err + } + + replies := []map[string]interface{}{} + for _, a := range am.AllAccounts() { + for _, u := range a.UtxoStore { + confirmations := 0 + if u.Height != -1 { + confirmations = int(bs.Height - u.Height + 1) + } + if minconf != 0 && (u.Height == -1 || + confirmations < minconf) { + continue + } + // check maxconf - doesn't apply if not confirmed. + if u.Height != -1 && confirmations > maxconf { + continue + } + + addr, err := btcutil.NewAddressPubKeyHash(u.AddrHash[:], + cfg.Net()) + if err != nil { + continue + } + + // if we hve addresses, limit to that list. + if len(addresses) > 0 { + if _, ok := addresses[addr.EncodeAddress()]; !ok { + continue + } + } + entry := map[string]interface{}{ + // check minconf/maxconf + "txid": u.Out.Hash.String(), + "vout": u.Out.Index, + "address": addr.EncodeAddress(), + "account": a.name, + "scriptPubKey": u.Subscript, + "amount": float64(u.Amt) / float64(btcutil.SatoshiPerBitcoin), + "confirmations": confirmations, + // TODO(oga) if the object is + // pay-to-script-hash we need to add the + // redeemscript. + } + + replies = append(replies, entry) + } + + } + return replies, nil +} + // RescanActiveAddresses begins a rescan for all active addresses for // each account. // diff --git a/rpcserver.go b/rpcserver.go index fabc1ad..a0dc87e 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -49,6 +49,7 @@ var rpcHandlers = map[string]cmdHandler{ "listaccounts": ListAccounts, "listsinceblock": ListSinceBlock, "listtransactions": ListTransactions, + "listunspent": ListUnspent, "sendfrom": SendFrom, "sendmany": SendMany, "sendtoaddress": SendToAddress, @@ -74,7 +75,6 @@ var rpcHandlers = map[string]cmdHandler{ "listlockunspent": Unimplemented, "listreceivedbyaccount": Unimplemented, "listreceivedbyaddress": Unimplemented, - "listunspent": Unimplemented, "lockunspent": Unimplemented, "move": Unimplemented, "setaccount": Unimplemented, @@ -1090,6 +1090,40 @@ func ListAllTransactions(icmd btcjson.Cmd) (interface{}, *btcjson.Error) { } } +// ListUnspent handles the listunspent command. +func ListUnspent(icmd btcjson.Cmd) (interface{}, *btcjson.Error) { + cmd, ok := icmd.(*btcjson.ListUnspentCmd) + if !ok { + return nil, &btcjson.ErrInternal + } + addresses := make(map[string]bool) + if len(cmd.Addresses) != 0 { + // confirm that all of them are good: + for _, as := range cmd.Addresses { + a, err := btcutil.DecodeAddr(as) + if err != nil { + return nil, &btcjson.ErrInvalidAddressOrKey + } + + if _, ok := addresses[a.EncodeAddress()]; ok { + // duplicate + return nil, &btcjson.ErrInvalidParameter + } + addresses[a.EncodeAddress()] = true + } + } + + results, err := AcctMgr.ListUnspent(cmd.MinConf, cmd.MaxConf, addresses) + if err != nil { + return nil, &btcjson.Error{ + Code: btcjson.ErrWallet.Code, + Message: err.Error(), + } + } + + return results, nil +} + // sendPairs is a helper routine to reduce duplicated code when creating and // sending payment transactions. func sendPairs(icmd btcjson.Cmd, account string, amounts map[string]int64,