Skip to content
This repository has been archived by the owner on Jan 25, 2021. It is now read-only.

Commit

Permalink
Merge pull request #13 from O3Labs/feature/newapi
Browse files Browse the repository at this point in the history
better utxo api
  • Loading branch information
apisit committed May 9, 2018
2 parents 20c5630 + fbee9aa commit a88a688
Show file tree
Hide file tree
Showing 11 changed files with 272 additions and 60 deletions.
67 changes: 41 additions & 26 deletions neoutils/mobile.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,30 @@ package neoutils

import (
"fmt"
"strconv"
"strings"

"github.com/o3labs/neo-utils/neoutils/coz"
"github.com/o3labs/neo-utils/neoutils/o3"
"github.com/o3labs/neo-utils/neoutils/smartcontract"
)

// This class contains simplified method designed specifically for gomobile bind
// gomobile bind doesn't support slice argument or return

func utxoFromNEONWalletDB(neonWalletDBEndpoint string, address string) (smartcontract.Unspent, error) {
//"http://localhost:5000/"
cozClient := coz.NewClient(neonWalletDBEndpoint)
func utxoFromO3Platform(network string, address string) (smartcontract.Unspent, error) {

unspentCoz, err := cozClient.GetUnspentByAddress(address)
if err != nil {
return smartcontract.Unspent{}, err
unspent := smartcontract.Unspent{
Assets: map[smartcontract.NativeAsset]*smartcontract.Balance{},
}

client := o3.DefaultO3APIClient()
if network == "test" {
client = o3.APIClientWithNEOTestnet()
}

response := client.GetNEOUTXO(address)
if response.Code != 200 {
return unspent, fmt.Errorf("Error cannot get utxo")
}

gasBalance := smartcontract.Balance{
Expand All @@ -29,26 +38,32 @@ func utxoFromNEONWalletDB(neonWalletDBEndpoint string, address string) (smartcon
UTXOs: []smartcontract.UTXO{},
}

for _, v := range unspentCoz.GAS.Unspent {
gasTX1 := smartcontract.UTXO{
Index: v.Index,
TXID: v.Txid,
Value: v.Value,
for _, v := range response.Result.Data {
if strings.Contains(v.Asset, string(smartcontract.GAS)) {
value, err := strconv.ParseFloat(v.Value, 64)
if err != nil {
continue
}
gasTX1 := smartcontract.UTXO{
Index: v.Index,
TXID: v.Txid,
Value: value,
}
gasBalance.UTXOs = append(gasBalance.UTXOs, gasTX1)
}
gasBalance.UTXOs = append(gasBalance.UTXOs, gasTX1)
}

for _, v := range unspentCoz.NEO.Unspent {
tx := smartcontract.UTXO{
Index: v.Index,
TXID: v.Txid,
Value: v.Value,
if strings.Contains(v.Asset, string(smartcontract.NEO)) {
value, err := strconv.ParseFloat(v.Value, 64)
if err != nil {
continue
}
tx := smartcontract.UTXO{
Index: v.Index,
TXID: v.Txid,
Value: value,
}
neoBalance.UTXOs = append(neoBalance.UTXOs, tx)
}
neoBalance.UTXOs = append(neoBalance.UTXOs, tx)
}

unspent := smartcontract.Unspent{
Assets: map[smartcontract.NativeAsset]*smartcontract.Balance{},
}

unspent.Assets[smartcontract.GAS] = &gasBalance
Expand All @@ -61,7 +76,7 @@ type RawTransaction struct {
Data []byte
}

func MintTokensRawTransactionMobile(utxoEndpoint string, scriptHash string, wif string, sendingAssetID string, amount float64, remark string, networkFeeAmountInGAS float64) (*RawTransaction, error) {
func MintTokensRawTransactionMobile(network string, scriptHash string, wif string, sendingAssetID string, amount float64, remark string, networkFeeAmountInGAS float64) (*RawTransaction, error) {
rawTransaction := &RawTransaction{}
fee := smartcontract.NetworkFeeAmount(networkFeeAmountInGAS)
nep5 := UseNEP5WithNetworkFee(scriptHash, fee)
Expand All @@ -70,7 +85,7 @@ func MintTokensRawTransactionMobile(utxoEndpoint string, scriptHash string, wif
return nil, err
}

unspent, err := utxoFromNEONWalletDB(utxoEndpoint, wallet.Address)
unspent, err := utxoFromO3Platform(network, wallet.Address)
if err != nil {
return nil, err
}
Expand Down
5 changes: 2 additions & 3 deletions neoutils/mobile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,10 @@ func TestMintTokensFromMobile(t *testing.T) {
// gas := string(smartcontract.GAS)
amount := float64(2)
remark := "o3x"
// utxoEndpoint := "http://localhost:5000/"
utxoEndpoint := "http://testnet-api.wallet.cityofzion.io/"
network := "test"
networkFeeAmountInGAS := float64(0.0011)

tx, err := neoutils.MintTokensRawTransactionMobile(utxoEndpoint, scriptHash, wif, neo, amount, remark, networkFeeAmountInGAS)
tx, err := neoutils.MintTokensRawTransactionMobile(network, scriptHash, wif, neo, amount, remark, networkFeeAmountInGAS)
if err != nil {
log.Printf("%v", err)
t.Fail()
Expand Down
2 changes: 0 additions & 2 deletions neoutils/nep5.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package neoutils

import (
"fmt"
"log"
"strings"

"github.com/o3labs/neo-utils/neoutils/smartcontract"
Expand Down Expand Up @@ -114,7 +113,6 @@ func (n *NEP5) TransferNEP5RawTransaction(wallet Wallet, toAddress smartcontract
func (n *NEP5) MintTokensRawTransaction(wallet Wallet, assetToSend smartcontract.NativeAsset, amount float64, unspent smartcontract.Unspent, remark string) ([]byte, string, error) {

needVerification := true
log.Printf("needVerification = %v", needVerification)
operation := "mintTokens"
args := []interface{}{}
attributes := map[smartcontract.TransactionAttribute][]byte{}
Expand Down
35 changes: 21 additions & 14 deletions neoutils/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/http"
"sort"
"strings"
"sync"
"time"
)

Expand Down Expand Up @@ -112,14 +113,17 @@ func SelectBestSeedNode(commaSeparatedURLs string) *SeedNodeResponse {
urls := strings.Split(commaSeparatedURLs, ",")
ch := make(chan *FetchSeedRequest, len(urls))
fetchedList := []string{}
wg := sync.WaitGroup{}
listHighestNodes := []SeedNodeResponse{}
for _, url := range urls {
go func(url string) {
res := fetchSeedNode(url)
ch <- &FetchSeedRequest{res, url}
}(url)
}
wg.Add(1)

loop:
for {
select {
case request := <-ch:
Expand All @@ -134,21 +138,24 @@ func SelectBestSeedNode(commaSeparatedURLs string) *SeedNodeResponse {
fetchedList = append(fetchedList, request.URL)
if len(fetchedList) == len(urls) {

if len(listHighestNodes) == 0 {
return nil
}
//using sort.SliceStable to sort min response time first
sort.SliceStable(listHighestNodes, func(i, j int) bool {
return listHighestNodes[i].ResponseTime < listHighestNodes[j].ResponseTime
})
//using sort.SliceStable to sort block count and preserve the sorted position
sort.SliceStable(listHighestNodes, func(i, j int) bool {
return listHighestNodes[i].BlockCount > listHighestNodes[j].BlockCount
})

return &listHighestNodes[0]
// if len(listHighestNodes) == 0 {
// continue
// }
wg.Done()
break loop
}
}
}
return nil
//wait for the operation
wg.Wait()
//using sort.SliceStable to sort min response time first
sort.SliceStable(listHighestNodes, func(i, j int) bool {
return listHighestNodes[i].ResponseTime < listHighestNodes[j].ResponseTime
})
//using sort.SliceStable to sort block count and preserve the sorted position
sort.SliceStable(listHighestNodes, func(i, j int) bool {
return listHighestNodes[i].BlockCount > listHighestNodes[j].BlockCount
})

return &listHighestNodes[0]
}
12 changes: 12 additions & 0 deletions neoutils/network_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,17 @@ func TestBestNode(t *testing.T) {
if best != nil {
log.Printf("best node %+v %v %vms", best.URL, best.BlockCount, best.ResponseTime)
}
}

func TestGetBestO3Node(t *testing.T) {
urls := []string{
"http://seed1.o3node.org:10332",
"http://seed2.o3node.org:10332",
"http://seed3.o3node.org:10332",
}
commaSeparated := strings.Join(urls, ",")
best := SelectBestSeedNode(commaSeparated)
if best != nil {
log.Printf("best node %+v %v %vms", best.URL, best.BlockCount, best.ResponseTime)
}
}
95 changes: 95 additions & 0 deletions neoutils/o3/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package o3

import (
"encoding/json"
"fmt"
"log"
"net/http"
"net/url"
)

const apiEndpoint = "https://platform.o3.network/api"

type NEONetWork string

var NEOMainNet = "main"
var NEOTestNet = "test"

type O3APIInterface interface {
GetNEOUTXO(address string) UTXOResponse
GetNEOClimableGAS(address string) ClaimableGASResponse
}

type O3Client struct {
APIBaseEndpoint url.URL
neoNetwork string
}

func DefaultO3APIClient() *O3Client {
u, err := url.Parse(apiEndpoint)
if err != nil {
return nil
}
return &O3Client{APIBaseEndpoint: *u}
}

func APIClientWithNEOTestnet() *O3Client {
u, err := url.Parse(apiEndpoint)
if err != nil {
return nil
}
return &O3Client{APIBaseEndpoint: *u, neoNetwork: "test"}
}

//make sure all method interface is implemented
var _ O3APIInterface = (*O3Client)(nil)

func (n *O3Client) makeGETRequest(endpoint string, out interface{}) error {

fullEndpointString := fmt.Sprintf("%v%v", n.APIBaseEndpoint.String(), endpoint)
fullEndpoint, _ := url.Parse(fullEndpointString)

if n.neoNetwork == "test" {
log.Printf("network = test")
q := fullEndpoint.Query()
q.Set("network", n.neoNetwork)
fullEndpoint.RawQuery = q.Encode()
}

log.Printf("%v", fullEndpoint.String())

req, err := http.NewRequest("GET", fullEndpoint.String(), nil)
if err != nil {
return err
}
req.Header.Add("content-type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
err = json.NewDecoder(res.Body).Decode(&out)
if err != nil {
return err
}

return nil
}

func (o *O3Client) GetNEOUTXO(address string) UTXOResponse {
response := UTXOResponse{}
err := o.makeGETRequest(fmt.Sprintf("/v1/neo/%v/utxo", address), &response)
if err != nil {
return response
}
return response
}
func (o *O3Client) GetNEOClimableGAS(address string) ClaimableGASResponse {
response := ClaimableGASResponse{}

err := o.makeGETRequest(fmt.Sprintf("/v1/neo/%v/claimablegas", address), &response)
if err != nil {
return response
}
return response
}
38 changes: 38 additions & 0 deletions neoutils/o3/api_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package o3_test

import (
"log"
"testing"

"github.com/o3labs/neo-utils/neoutils/o3"
)

func TestO3NEOUTXO(t *testing.T) {
client := o3.DefaultO3APIClient()
response := client.GetNEOUTXO("ANk325vGG5kcc6Dcnk6zkoEBHY4E6es2nY")
if response.Code != 200 {
t.Fail()
}
log.Printf("%+v", response)
}

func TestO3NEOUTXOTestnet(t *testing.T) {
client := o3.APIClientWithNEOTestnet()
response := client.GetNEOUTXO("ANk325vGG5kcc6Dcnk6zkoEBHY4E6es2nY")
if response.Code != 200 {
t.Fail()
}
log.Printf("%+v", response)
}

func TestO3GetClaimableGAS(t *testing.T) {
client := o3.DefaultO3APIClient()
response := client.GetNEOClimableGAS("ANk325vGG5kcc6Dcnk6zkoEBHY4E6es2nY")
if response.Code != 200 {
t.Fail()
}
log.Printf("%+v", response.Result.Data.Gas)
for _, v := range response.Result.Data.Claims {
log.Printf("%+v", v)
}
}
45 changes: 45 additions & 0 deletions neoutils/o3/response.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package o3

type Response struct {
Code int `json:"code"`
}

type ErrorResponse struct {
Error struct {
Message string `json:"message"`
Type string `json:"type"`
} `json:"error"`
}

type UTXOResponse struct {
Response
*ErrorResponse
Result struct {
Data []UTXOResultData `json:"data"`
} `json:"result"`
}

type UTXOResultData struct {
Asset string `json:"asset"`
Index int `json:"index"`
Txid string `json:"txid"`
Value string `json:"value"`
CreatedAtBlock int `json:"createdAtBlock"`
}

type ClaimableGASResponse struct {
Response
*ErrorResponse
Result struct {
Data struct {
Gas string `json:"gas"`
Claims []struct {
Asset string `json:"asset"`
Index int `json:"index"`
Txid string `json:"txid"`
Value string `json:"value"`
CreatedAtBlock int `json:"createdAtBlock"`
} `json:"claims"`
} `json:"data"`
} `json:"result"`
}
Loading

0 comments on commit a88a688

Please sign in to comment.