d24c6d1d9e
This includes Client struct with RPC methods and BalanceGetter implementation with NeoSCAN.
125 lines
3.3 KiB
Go
125 lines
3.3 KiB
Go
package client
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"sort"
|
|
|
|
"github.com/CityOfZion/neo-go/pkg/core/state"
|
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
|
"github.com/CityOfZion/neo-go/pkg/rpc/response/result"
|
|
"github.com/CityOfZion/neo-go/pkg/util"
|
|
errs "github.com/pkg/errors"
|
|
)
|
|
|
|
/*
|
|
Definition of types, helper functions and variables
|
|
required for calculation of transaction inputs using
|
|
NeoScan API.
|
|
*/
|
|
|
|
type (
|
|
// NeoScanServer stores NEOSCAN URL and API path.
|
|
NeoScanServer struct {
|
|
URL string // "protocol://host:port/"
|
|
Path string // path to API endpoint without wallet address
|
|
}
|
|
|
|
// Unspent stores Unspents per asset
|
|
Unspent struct {
|
|
Unspent state.UnspentBalances
|
|
Asset string // "NEO" / "GAS"
|
|
Amount util.Fixed8 // total unspent of this asset
|
|
}
|
|
|
|
// NeoScanBalance is a struct of NeoScan response to 'get_balance' request
|
|
NeoScanBalance struct {
|
|
Balance []*Unspent
|
|
Address string
|
|
}
|
|
)
|
|
|
|
// GetBalance performs a request to get balance for the address specified.
|
|
func (s NeoScanServer) GetBalance(address string) ([]*Unspent, error) {
|
|
var (
|
|
err error
|
|
req *http.Request
|
|
res *http.Response
|
|
balance NeoScanBalance
|
|
client = http.Client{}
|
|
balanceURL = s.URL + s.Path
|
|
)
|
|
|
|
if req, err = http.NewRequest(http.MethodGet, balanceURL+address, nil); err != nil {
|
|
return nil, errs.Wrap(err, "Failed to compose HTTP request")
|
|
}
|
|
|
|
if res, err = client.Do(req); err != nil {
|
|
return nil, errs.Wrap(err, "Failed to perform HTTP request")
|
|
}
|
|
|
|
defer res.Body.Close()
|
|
|
|
if err = json.NewDecoder(res.Body).Decode(&balance); err != nil {
|
|
return nil, errs.Wrap(err, "Failed to decode HTTP response")
|
|
}
|
|
return balance.Balance, nil
|
|
}
|
|
|
|
func filterSpecificAsset(asset string, balance []*Unspent, assetBalance *Unspent) {
|
|
for _, us := range balance {
|
|
if us.Asset == asset {
|
|
assetBalance.Unspent = us.Unspent
|
|
assetBalance.Asset = us.Asset
|
|
assetBalance.Amount = us.Amount
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// CalculateInputs creates input transactions for the specified amount of given asset belonging to specified address.
|
|
func (s NeoScanServer) CalculateInputs(address string, assetIDUint util.Uint256, cost util.Fixed8) ([]transaction.Input, util.Fixed8, error) {
|
|
var (
|
|
err error
|
|
us []*Unspent
|
|
assetUnspent Unspent
|
|
assetID = result.GlobalAssets[assetIDUint.StringLE()]
|
|
)
|
|
if us, err = s.GetBalance(address); err != nil {
|
|
return nil, util.Fixed8(0), errs.Wrapf(err, "Cannot get balance for address %v", address)
|
|
}
|
|
filterSpecificAsset(assetID, us, &assetUnspent)
|
|
return unspentsToInputs(assetUnspent.Unspent, cost)
|
|
}
|
|
|
|
// unspentsToInputs uses UnspentBalances to create a slice of inputs for a new
|
|
// transcation containing the required amount of asset.
|
|
func unspentsToInputs(utxos state.UnspentBalances, required util.Fixed8) ([]transaction.Input, util.Fixed8, error) {
|
|
var (
|
|
num, i uint16
|
|
selected = util.Fixed8(0)
|
|
)
|
|
sort.Sort(utxos)
|
|
|
|
for _, us := range utxos {
|
|
if selected >= required {
|
|
break
|
|
}
|
|
selected += us.Value
|
|
num++
|
|
}
|
|
if selected < required {
|
|
return nil, util.Fixed8(0), errors.New("cannot compose inputs for transaction; check sender balance")
|
|
}
|
|
|
|
inputs := make([]transaction.Input, 0, num)
|
|
for i = 0; i < num; i++ {
|
|
inputs = append(inputs, transaction.Input{
|
|
PrevHash: utxos[i].Tx,
|
|
PrevIndex: utxos[i].Index,
|
|
})
|
|
}
|
|
|
|
return inputs, selected, nil
|
|
}
|