Skip to content
This repository has been archived by the owner on Sep 12, 2019. It is now read-only.

PathPayment #126

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
30 changes: 27 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,19 +68,43 @@ Then you can start the server:

### POST /payment

Builds and submits a transaction with a single [`payment`](https://www.stellar.org/developers/learn/concepts/list-of-operations.html#payment) operation built from following parameters.
Builds and submits a transaction with a single [`payment`](https://www.stellar.org/developers/learn/concepts/list-of-operations.html#payment), [`path_payment`](https://www.stellar.org/developers/learn/concepts/list-of-operations.html#path-payment) or [`create_account`](https://www.stellar.org/developers/learn/concepts/list-of-operations.html#create-account) (when sending native asset to account that does not exist) operation built from following parameters.

#### Request Parameters

Every request must contain required parameters from the following list. Additionally, depending on `type` parameter, every request must contain required parameters for equivalent operation type (check tables below).

name | | description
--- | --- | ---
`source` | required | Secret seed of transaction source account
`destination` | required | Account ID or Stellar address (ex. `bob*stellar.org`) of payment destination account
`type` | optional | Operation type: `payment` (default) or `path_payment`.
`memo_type` | optional | Memo type, one of: `id`, `text`, `hash`
`memo` | optional | Memo value, when `memo_type` is `id` it must be uint64, when `hash` it must be 32 bytes hex value

##### CreateAccount / Payment operation parameters

name | | description
--- | --- | ---
`amount` | required | Amount to send
`asset_code` | optional | Asset code (XLM when empty)
`asset_issuer` | optional | Account ID of asset issuer (XLM when empty)
`memo_type` | optional | Memo type, one of: `id`, `text`, `hash`
`memo` | optional | Memo value, when `memo_type` is `id` it must be uint64, when `hash` it must be 32 bytes hex value

##### PathPayment operation parameters

name | | description
--- | --- | ---
`send_max` | required | Maximum amount of send_asset to send
`send_asset_code` | optional | Sending asset code (XLM when empty)
`send_asset_issuer` | optional | Account ID of sending asset issuer (XLM when empty)
`destination_amount` | required | Amount of destination_asset to receiving account will get
`destination_asset_code` | optional | Destination asset code (XLM when empty)
`destination_asset_issuer` | optional | Account ID of destination asset issuer (XLM when empty)
`path[n][asset_code]` | optional | Asset code of `n`th asset on the path (XLM when empty, but empty parameter must be sent!)
`path[n][asset_issuer]` | optional | Account ID of `n`th asset issuer (XLM when empty, but empty parameter must be sent!)
`path[n+1][asset_code]` | optional | Asset code of `n+1`th asset on the path (XLM when empty, but empty parameter must be sent!)
`path[n+1][asset_issuer]` | optional | Account ID of `n+1`th asset issuer (XLM when empty, but empty parameter must be sent!)
... | ... | _Up to 5 assets in the path..._

#### Response

Expand Down
178 changes: 147 additions & 31 deletions src/github.com/stellar/gateway/handlers/request_handler_payment.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package handlers

import (
"encoding/hex"
"fmt"
log "github.com/Sirupsen/logrus"
"net/http"
"strconv"
Expand Down Expand Up @@ -37,41 +38,25 @@ func (rh *RequestHandler) Payment(w http.ResponseWriter, r *http.Request) {
return
}

amount := r.PostFormValue("amount")
assetCode := r.PostFormValue("asset_code")
assetIssuer := r.PostFormValue("asset_issuer")

var operationBuilder interface{}
var errorResponse *horizon.SubmitTransactionResponseError

if assetCode != "" && assetIssuer != "" {
issuerKeypair, err := keypair.Parse(assetIssuer)
if err != nil {
log.WithFields(log.Fields{"asset_issuer": assetIssuer}).Print("Invalid asset_issuer parameter")
writeError(w, horizon.PaymentInvalidIssuer)
paymentType := r.PostFormValue("type")
switch paymentType {
case "":
case "payment":
log.Println("payment")
operationBuilder, errorResponse = rh.createPaymentOperation(r, destinationObject)
case "path_payment":
log.Println("path_payment")
operationBuilder, errorResponse = rh.createPathPaymentOperation(r, destinationObject)
default:
writeError(w, horizon.PaymentInvalidType)
return
}

operationBuilder = b.Payment(
b.Destination{destinationObject.AccountId},
b.CreditAmount{assetCode, issuerKeypair.Address(), amount},
)
} else if assetCode == "" && assetIssuer == "" {
mutators := []interface{}{
b.Destination{destinationObject.AccountId},
b.NativeAmount{amount},
}
}

// Check if destination account exist
_, err = rh.Horizon.LoadAccount(destinationObject.AccountId)
if err != nil {
log.WithFields(log.Fields{"error": err}).Error("Error loading account")
operationBuilder = b.CreateAccount(mutators...)
} else {
operationBuilder = b.Payment(mutators...)
}
} else {
log.Print("Missing asset param.")
writeError(w, horizon.PaymentMissingParamAsset)
if errorResponse != nil {
writeError(w, errorResponse)
return
}

Expand Down Expand Up @@ -187,3 +172,134 @@ func (rh *RequestHandler) Payment(w http.ResponseWriter, r *http.Request) {

write(w, submitResponse)
}

func (rh *RequestHandler) createPaymentOperation(r *http.Request, destinationObject StellarDestination) (operationBuilder interface{}, errorResponse *horizon.SubmitTransactionResponseError) {
amount := r.PostFormValue("amount")
assetCode := r.PostFormValue("asset_code")
assetIssuer := r.PostFormValue("asset_issuer")

if assetCode != "" && assetIssuer != "" {
issuerKeypair, err := keypair.Parse(assetIssuer)
if err != nil {
log.WithFields(log.Fields{"asset_issuer": assetIssuer}).Print("Invalid asset_issuer parameter")
errorResponse = horizon.PaymentInvalidIssuer
return
}

operationBuilder = b.Payment(
b.Destination{destinationObject.AccountId},
b.CreditAmount{assetCode, issuerKeypair.Address(), amount},
)

if operationBuilder.(b.PaymentBuilder).Err != nil {
log.WithFields(log.Fields{"err": operationBuilder.(b.PaymentBuilder).Err}).Print("Error building operation")
errorResponse = horizon.ServerError
return
}
} else if assetCode == "" && assetIssuer == "" {
mutators := []interface{}{
b.Destination{destinationObject.AccountId},
b.NativeAmount{amount},
}

// Check if destination account exist
_, err := rh.Horizon.LoadAccount(destinationObject.AccountId)
if err != nil {
log.WithFields(log.Fields{"error": err}).Error("Error loading account")
operationBuilder = b.CreateAccount(mutators...)
if operationBuilder.(b.CreateAccountBuilder).Err != nil {
log.WithFields(log.Fields{"err": operationBuilder.(b.CreateAccountBuilder).Err}).Print("Error building operation")
errorResponse = horizon.ServerError
return
}
} else {
operationBuilder = b.Payment(mutators...)
if operationBuilder.(b.PaymentBuilder).Err != nil {
log.WithFields(log.Fields{"err": operationBuilder.(b.PaymentBuilder).Err}).Print("Error building operation")
errorResponse = horizon.ServerError
return
}
}
} else {
log.Print("Missing asset param.")
errorResponse = horizon.PaymentMissingParamAsset
return
}
return
}

func (rh *RequestHandler) createPathPaymentOperation(r *http.Request, destinationObject StellarDestination) (operationBuilder interface{}, errorResponse *horizon.SubmitTransactionResponseError) {
sendMax := r.PostFormValue("send_max")
sendAssetCode := r.PostFormValue("send_asset_code")
sendAssetIssuer := r.PostFormValue("send_asset_issuer")

var sendAsset b.Asset
if sendAssetCode != "" && sendAssetIssuer != "" {
sendAsset = b.Asset{Code: sendAssetCode, Issuer: sendAssetIssuer}
} else if sendAssetCode == "" && sendAssetIssuer == "" {
sendAsset = b.Asset{Native: true}
} else {
log.Print("Missing send asset param.")
errorResponse = horizon.PaymentMissingParamAsset
return
}

destinationAmount := r.PostFormValue("destination_amount")
destinationAssetCode := r.PostFormValue("destination_asset_code")
destinationAssetIssuer := r.PostFormValue("destination_asset_issuer")

var destinationAsset b.Asset
if destinationAssetCode != "" && destinationAssetIssuer != "" {
destinationAsset = b.Asset{Code: destinationAssetCode, Issuer: destinationAssetIssuer}
} else if destinationAssetCode == "" && destinationAssetIssuer == "" {
destinationAsset = b.Asset{Native: true}
} else {
log.Print("Missing destination asset param.")
errorResponse = horizon.PaymentMissingParamAsset
return
}

// TODO check the fields

var path []b.Asset

for i := 0; ; i++ {
codeFieldName := fmt.Sprintf("path[%d][asset_code]", i)
issuerFieldName := fmt.Sprintf("path[%d][asset_issuer]", i)

// If the element does not exist in PostForm break the loop
if _, exists := r.PostForm[codeFieldName]; !exists {
break
}

code := r.PostFormValue(codeFieldName)
issuer := r.PostFormValue(issuerFieldName)

if code == "" && issuer == "" {
path = append(path, b.Asset{Native: true})
} else {
path = append(path, b.Asset{Code: code, Issuer: issuer})
}
}

operationBuilder = b.PathPayment(
b.Destination{destinationObject.AccountId},
b.PathSend{
Asset: sendAsset,
MaxAmount: sendMax,
},
b.PathDestination{
Asset: destinationAsset,
Amount: destinationAmount,
},
b.Path{Assets: path},
)

if operationBuilder.(b.PathPaymentBuilder).Err != nil {
log.WithFields(log.Fields{"err": operationBuilder.(b.PathPaymentBuilder).Err}).Print("Error building operation")
errorResponse = horizon.ServerError
return
}

return
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ var (
// /send & /payment

// input errors
PaymentInvalidType = &SubmitTransactionResponseError{Code: "invalid_type", Message: "Invalid operation type.", Status: http.StatusBadRequest}
PaymentInvalidSource = &SubmitTransactionResponseError{Code: "invalid_source", Message: "source parameter is invalid.", Status: http.StatusBadRequest}
PaymentCannotResolveDestination = &SubmitTransactionResponseError{Code: "cannot_resolve_destination", Message: "Cannot resolve federated Stellar address.", Status: http.StatusBadRequest}
PaymentInvalidDestination = &SubmitTransactionResponseError{Code: "invalid_destination", Message: "destination parameter is invalid.", Status: http.StatusBadRequest}
Expand Down
23 changes: 23 additions & 0 deletions vendor/src/github.com/stellar/go-stellar-base/build/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ var (
DefaultNetwork = TestNetwork
)

// Asset is struct used in path_payment mutators
type Asset struct {
Native bool
Code string
Issuer string
}

// AllowTrustAsset is a mutator capable of setting the asset on
// an operations that have one.
type AllowTrustAsset struct {
Expand Down Expand Up @@ -92,6 +99,22 @@ type NativeAmount struct {
Amount string
}

type Path struct {
Assets []Asset
}

// PathDestination is a mutator that configures a path_payment's destination asset and amount
type PathDestination struct {
Asset
Amount string
}

// PathSend is a mutator that configures a path_payment's send asset and max amount
type PathSend struct {
Asset
MaxAmount string
}

// Sequence is a mutator that sets the sequence number on a transaction
type Sequence struct {
Sequence uint64
Expand Down
Loading