Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

clients/horizonclient: Support paths/strict-send and paths/strict-receive requests. #2237

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion clients/horizonclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,8 +410,21 @@ func (c *Client) OrderBook(request OrderBookRequest) (obs hProtocol.OrderBookSum
return
}

// Paths returns the available paths to make a payment. See https://www.stellar.org/developers/horizon/reference/endpoints/path-finding.html
// Paths returns the available paths to make a strict receive path payment. See https://www.stellar.org/developers/horizon/reference/endpoints/path-finding-strict-receive.html
// This function is an alias for `client.StrictReceivePaths` and will be deprecated, use `client.StrictReceivePaths` instead.
func (c *Client) Paths(request PathsRequest) (paths hProtocol.PathsPage, err error) {
paths, err = c.StrictReceivePaths(request)
return
}

// StrictReceivePaths returns the available paths to make a strict receive path payment. See https://www.stellar.org/developers/horizon/reference/endpoints/path-finding-strict-receive.html
func (c *Client) StrictReceivePaths(request PathsRequest) (paths hProtocol.PathsPage, err error) {
err = c.sendRequest(request, &paths)
return
}

// StrictSendPaths returns the available paths to make a strict send path payment. See https://www.stellar.org/developers/horizon/reference/endpoints/path-finding-strict-send.html
func (c *Client) StrictSendPaths(request StrictSendPathsRequest) (paths hProtocol.PathsPage, err error) {
err = c.sendRequest(request, &paths)
return
}
Expand Down
20 changes: 19 additions & 1 deletion clients/horizonclient/examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,25 @@ func ExampleClient_Paths() {
DestinationAssetType: horizonclient.AssetType4,
SourceAccount: "GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM",
}
paths, err := client.Paths(pr)
paths, err := client.StrictReceivePaths(pr)
if err != nil {
fmt.Println(err)
return
}
fmt.Print(paths)
}

func ExampleClient_StrictSendPaths() {
client := horizonclient.DefaultPublicNetClient
// Find paths for USD->EUR
pr := horizonclient.StrictSendPathsRequest{
SourceAmount: "20",
SourceAssetCode: "USD",
SourceAssetIssuer: "GDUKMGUGDZQK6YHYA5Z6AY2G4XDSZPSZ3SW5UN3ARVMO6QSRDWP5YLEX",
SourceAssetType: horizonclient.AssetType4,
DestinationAssets: "EURT:GAP5LETOV6YIE62YAM56STDANPRDO7ZFDBGSNHJQIYGGKSMOZAHOOS2S",
}
paths, err := client.StrictSendPaths(pr)
if err != nil {
fmt.Println(err)
return
Expand Down
20 changes: 18 additions & 2 deletions clients/horizonclient/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,15 +335,31 @@ type OrderBookRequest struct {
Limit uint
}

// PathsRequest struct contains data for getting available payment paths from a horizon server.
// All parameters are required.
// PathsRequest struct contains data for getting available strict receive path payments from a horizon server.
// All the Destination related parameters are required and you need to include either
// SourceAccount or SourceAssets.
// See https://www.stellar.org/developers/horizon/reference/endpoints/path-finding-strict-receive.html
type PathsRequest struct {
DestinationAccount string
DestinationAssetType AssetType
DestinationAssetCode string
DestinationAssetIssuer string
DestinationAmount string
SourceAccount string
SourceAssets string
}

// StrictSendPathsRequest struct contains data for getting available strict send path payments from a horizon server.
// All the Source related parameters are required and you need to include either
// DestinationAccount or DestinationAssets.
// See https://www.stellar.org/developers/horizon/reference/endpoints/path-finding-strict-send.html
type StrictSendPathsRequest struct {
DestinationAccount string
DestinationAssets string
SourceAssetType AssetType
SourceAssetCode string
SourceAssetIssuer string
SourceAmount string
}

// TradeRequest struct contains data for getting trade details from a horizon server.
Expand Down
1 change: 1 addition & 0 deletions clients/horizonclient/paths_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func (pr PathsRequest) BuildURL() (endpoint string, err error) {
paramMap["destination_asset_issuer"] = pr.DestinationAssetIssuer
paramMap["destination_amount"] = pr.DestinationAmount
paramMap["source_account"] = pr.SourceAccount
paramMap["source_assets"] = pr.SourceAssets

queryParams := addQueryParams(paramMap)
if queryParams != "" {
Expand Down
59 changes: 56 additions & 3 deletions clients/horizonclient/paths_request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,18 @@ func TestPathsRequestBuildUrl(t *testing.T) {
DestinationAssetIssuer: "GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM",
DestinationAssetType: AssetType4,
SourceAccount: "GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM",
SourceAssets: "COP:GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM",
}

endpoint, err = pr.BuildURL()

// It should return valid assets endpoint and no errors
require.NoError(t, err)
assert.Equal(t, "paths?destination_account=GCLWGQPMKXQSPF776IU33AH4PZNOOWNAWGGKVTBQMIC5IMKUNP3E6NVU&destination_amount=100&destination_asset_code=NGN&destination_asset_issuer=GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM&destination_asset_type=credit_alphanum4&source_account=GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM", endpoint)
assert.Equal(
t,
"paths?destination_account=GCLWGQPMKXQSPF776IU33AH4PZNOOWNAWGGKVTBQMIC5IMKUNP3E6NVU&destination_amount=100&destination_asset_code=NGN&destination_asset_issuer=GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM&destination_asset_type=credit_alphanum4&source_account=GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM&source_assets=COP%3AGDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM",
endpoint,
)

}

Expand All @@ -57,7 +62,7 @@ func TestPathsRequest(t *testing.T) {
"https://localhost/paths?destination_account=GCLWGQPMKXQSPF776IU33AH4PZNOOWNAWGGKVTBQMIC5IMKUNP3E6NVU&destination_amount=100&destination_asset_code=NGN&destination_asset_issuer=GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM&destination_asset_type=credit_alphanum4&source_account=GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM",
).ReturnString(200, pathsResponse)

paths, err := client.Paths(pr)
paths, err := client.StrictReceivePaths(pr)
if assert.NoError(t, err) {
assert.IsType(t, paths, hProtocol.PathsPage{})
record := paths.Embedded.Records[0]
Expand All @@ -73,7 +78,55 @@ func TestPathsRequest(t *testing.T) {
"https://localhost/paths",
).ReturnString(400, badRequestResponse)

_, err = client.Paths(pr)
_, err = client.StrictReceivePaths(pr)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "horizon error")
horizonError, ok := err.(*Error)
assert.Equal(t, ok, true)
assert.Equal(t, horizonError.Problem.Title, "Bad Request")
}

}

func TestStrictReceivePathsRequest(t *testing.T) {
hmock := httptest.NewClient()
client := &Client{
HorizonURL: "https://localhost/",
HTTP: hmock,
}

pr := PathsRequest{
DestinationAccount: "GCLWGQPMKXQSPF776IU33AH4PZNOOWNAWGGKVTBQMIC5IMKUNP3E6NVU",
DestinationAmount: "100",
DestinationAssetCode: "NGN",
DestinationAssetIssuer: "GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM",
DestinationAssetType: AssetType4,
SourceAccount: "GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM",
}

// orderbook for XLM/USD
hmock.On(
"GET",
"https://localhost/paths?destination_account=GCLWGQPMKXQSPF776IU33AH4PZNOOWNAWGGKVTBQMIC5IMKUNP3E6NVU&destination_amount=100&destination_asset_code=NGN&destination_asset_issuer=GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM&destination_asset_type=credit_alphanum4&source_account=GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM",
).ReturnString(200, pathsResponse)

paths, err := client.StrictReceivePaths(pr)
if assert.NoError(t, err) {
assert.IsType(t, paths, hProtocol.PathsPage{})
record := paths.Embedded.Records[0]
assert.Equal(t, record.DestinationAmount, "20.0000000")
assert.Equal(t, record.DestinationAssetCode, "EUR")
assert.Equal(t, record.SourceAmount, "30.0000000")
}

// failure response
pr = PathsRequest{}
hmock.On(
"GET",
"https://localhost/paths",
).ReturnString(400, badRequestResponse)

_, err = client.StrictReceivePaths(pr)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "horizon error")
horizonError, ok := err.(*Error)
Expand Down
35 changes: 35 additions & 0 deletions clients/horizonclient/strict_send_paths_request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package horizonclient

import (
"fmt"
"net/url"

"github.com/stellar/go/support/errors"
)

// BuildURL creates the endpoint to be queried based on the data in the PathsRequest struct.
func (pr StrictSendPathsRequest) BuildURL() (endpoint string, err error) {
endpoint = "paths/strict-send"

// add the parameters to a map here so it is easier for addQueryParams to populate the parameter list
// We can't use assetCode and assetIssuer types here because the parameter names are different
paramMap := make(map[string]string)
paramMap["destination_assets"] = pr.DestinationAssets
paramMap["destination_account"] = pr.DestinationAccount
paramMap["source_asset_type"] = string(pr.SourceAssetType)
paramMap["source_asset_code"] = pr.SourceAssetCode
paramMap["source_asset_issuer"] = pr.SourceAssetIssuer
paramMap["source_amount"] = pr.SourceAmount

queryParams := addQueryParams(paramMap)
if queryParams != "" {
endpoint = fmt.Sprintf("%s?%s", endpoint, queryParams)
}

_, err = url.Parse(endpoint)
if err != nil {
err = errors.Wrap(err, "failed to parse endpoint")
}

return endpoint, err
}
117 changes: 117 additions & 0 deletions clients/horizonclient/strict_send_paths_request_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package horizonclient

import (
"testing"

"github.com/stellar/go/support/http/httptest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestStrictSendPathsRequestBuildUrl(t *testing.T) {
pr := StrictSendPathsRequest{}
endpoint, err := pr.BuildURL()

// It should return no errors and paths endpoint
// Horizon will return an error though because there are no parameters
require.NoError(t, err)
assert.Equal(t, "paths/strict-send", endpoint)

pr = StrictSendPathsRequest{
SourceAmount: "100",
SourceAssetCode: "NGN",
SourceAssetIssuer: "GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM",
SourceAssetType: AssetType4,
DestinationAccount: "GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM",
}

endpoint, err = pr.BuildURL()

// It should return a valid endpoint and no errors
require.NoError(t, err)
assert.Equal(
t,
"paths/strict-send?destination_account=GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM&source_amount=100&source_asset_code=NGN&source_asset_issuer=GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM&source_asset_type=credit_alphanum4",
endpoint,
)

pr = StrictSendPathsRequest{
SourceAmount: "100",
SourceAssetCode: "USD",
SourceAssetIssuer: "GDUKMGUGDZQK6YHYA5Z6AY2G4XDSZPSZ3SW5UN3ARVMO6QSRDWP5YLEX",
SourceAssetType: AssetType4,
DestinationAssets: "EURT:GAP5LETOV6YIE62YAM56STDANPRDO7ZFDBGSNHJQIYGGKSMOZAHOOS2S,native",
}

endpoint, err = pr.BuildURL()

require.NoError(t, err)
assert.Equal(
t,
"paths/strict-send?destination_assets=EURT%3AGAP5LETOV6YIE62YAM56STDANPRDO7ZFDBGSNHJQIYGGKSMOZAHOOS2S%2Cnative&source_amount=100&source_asset_code=USD&source_asset_issuer=GDUKMGUGDZQK6YHYA5Z6AY2G4XDSZPSZ3SW5UN3ARVMO6QSRDWP5YLEX&source_asset_type=credit_alphanum4",
endpoint,
)
}
func TestStrictSendPathsRequest(t *testing.T) {
hmock := httptest.NewClient()
client := &Client{
HorizonURL: "https://localhost/",
HTTP: hmock,
}

pr := StrictSendPathsRequest{
SourceAmount: "20",
SourceAssetCode: "USD",
SourceAssetIssuer: "GDSBCQO34HWPGUGQSP3QBFEXVTSR2PW46UIGTHVWGWJGQKH3AFNHXHXN",
SourceAssetType: AssetType4,
DestinationAccount: "GCLWGQPMKXQSPF776IU33AH4PZNOOWNAWGGKVTBQMIC5IMKUNP3E6NVU",
}

hmock.On(
"GET",
"https://localhost/paths/strict-send?destination_account=GCLWGQPMKXQSPF776IU33AH4PZNOOWNAWGGKVTBQMIC5IMKUNP3E6NVU&source_amount=20&source_asset_code=USD&source_asset_issuer=GDSBCQO34HWPGUGQSP3QBFEXVTSR2PW46UIGTHVWGWJGQKH3AFNHXHXN&source_asset_type=credit_alphanum4",
).ReturnString(200, pathsResponse)

paths, err := client.StrictSendPaths(pr)
assert.NoError(t, err)
assert.Len(t, paths.Embedded.Records, 3)

pr = StrictSendPathsRequest{
SourceAmount: "20",
SourceAssetCode: "USD",
SourceAssetIssuer: "GDSBCQO34HWPGUGQSP3QBFEXVTSR2PW46UIGTHVWGWJGQKH3AFNHXHXN",
SourceAssetType: AssetType4,
DestinationAssets: "EUR:GDSBCQO34HWPGUGQSP3QBFEXVTSR2PW46UIGTHVWGWJGQKH3AFNHXHXN",
}

hmock.On(
"GET",
"https://localhost/paths/strict-send?destination_assets=EUR%3AGDSBCQO34HWPGUGQSP3QBFEXVTSR2PW46UIGTHVWGWJGQKH3AFNHXHXN&source_amount=20&source_asset_code=USD&source_asset_issuer=GDSBCQO34HWPGUGQSP3QBFEXVTSR2PW46UIGTHVWGWJGQKH3AFNHXHXN&source_asset_type=credit_alphanum4",
).ReturnString(200, pathsResponse)

paths, err = client.StrictSendPaths(pr)
assert.NoError(t, err)
assert.Len(t, paths.Embedded.Records, 3)

pr = StrictSendPathsRequest{
SourceAmount: "20",
SourceAssetCode: "USD",
SourceAssetIssuer: "GDSBCQO34HWPGUGQSP3QBFEXVTSR2PW46UIGTHVWGWJGQKH3AFNHXHXN",
SourceAssetType: AssetType4,
DestinationAssets: "EUR:GDSBCQO34HWPGUGQSP3QBFEXVTSR2PW46UIGTHVWGWJGQKH3AFNHXHXN",
DestinationAccount: "GDSBCQO34HWPGUGQSP3QBFEXVTSR2PW46UIGTHVWGWJGQKH3AFNHXHXN",
}

hmock.On(
"GET",
"https://localhost/paths/strict-send?destination_account=GDSBCQO34HWPGUGQSP3QBFEXVTSR2PW46UIGTHVWGWJGQKH3AFNHXHXN&destination_assets=EUR%3AGDSBCQO34HWPGUGQSP3QBFEXVTSR2PW46UIGTHVWGWJGQKH3AFNHXHXN&source_amount=20&source_asset_code=USD&source_asset_issuer=GDSBCQO34HWPGUGQSP3QBFEXVTSR2PW46UIGTHVWGWJGQKH3AFNHXHXN&source_asset_type=credit_alphanum4",
).ReturnString(400, badRequestResponse)

_, err = client.StrictSendPaths(pr)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "horizon error")
horizonError, ok := err.(*Error)
assert.Equal(t, ok, true)
assert.Equal(t, horizonError.Problem.Title, "Bad Request")
}
}