// BuildTransaction is used in compliance server. The sequence number in built transaction will be equal 0! func BuildTransaction(accountID, networkPassphrase string, operation, memo interface{}) (transaction *xdr.Transaction, err error) { operationMutator, ok := operation.(build.TransactionMutator) if !ok { err = errors.New("Cannot cast operationMutator to build.TransactionMutator") return } mutators := []build.TransactionMutator{ build.SourceAccount{accountID}, build.Sequence{0}, build.Network{networkPassphrase}, operationMutator, } if memo != nil { memoMutator, ok := memo.(build.TransactionMutator) if !ok { err = errors.New("Cannot cast memo to build.TransactionMutator") return } mutators = append(mutators, memoMutator) } txBuilder := build.Transaction(mutators...) return txBuilder.TX, txBuilder.Err }
// SubmitTransaction builds and submits transaction to Stellar network func (ts *TransactionSubmitter) SubmitTransaction(seed string, operation, memo interface{}) (response horizon.SubmitTransactionResponse, err error) { account, err := ts.GetAccount(seed) if err != nil { return } operationMutator, ok := operation.(build.TransactionMutator) if !ok { ts.log.Error("Cannot cast operationMutator to build.TransactionMutator") err = errors.New("Cannot cast operationMutator to build.TransactionMutator") return } mutators := []build.TransactionMutator{ build.SourceAccount{account.Seed}, ts.Network, operationMutator, } if memo != nil { memoMutator, ok := memo.(build.TransactionMutator) if !ok { ts.log.Error("Cannot cast memo to build.TransactionMutator") err = errors.New("Cannot cast memo to build.TransactionMutator") return } mutators = append(mutators, memoMutator) } txBuilder := build.Transaction(mutators...) return ts.SignAndSubmitRawTransaction(seed, txBuilder.TX) }
// ExampleBuildTransaction creates and signs a simple transaction using the build package. // The build package is designed to make it easier and more intuitive to configure and sign // a transaction. func ExampleBuildTransaction() { source := "SA26PHIKZM6CXDGR472SSGUQQRYXM6S437ZNHZGRM6QA4FOPLLLFRGDX" tx := b.Transaction( b.SourceAccount{source}, b.Sequence{1}, b.Payment( b.Destination{"SBQHO2IMYKXAYJFCWGXC7YKLJD2EGDPSK3IUDHVJ6OOTTKLSCK6Z6POM"}, b.NativeAmount{"50.0"}, ), ) txe := tx.Sign(source) txeB64, err := txe.Base64() if err != nil { panic(err) } fmt.Printf("tx base64: %s", txeB64) }
// Builder implements /builder endpoint func (rh *RequestHandler) Builder(w http.ResponseWriter, r *http.Request) { var request bridge.BuilderRequest decoder := json.NewDecoder(r.Body) err := decoder.Decode(&request) if err != nil { log.WithFields(log.Fields{"err": err}).Error("Error decoding request") server.Write(w, protocols.InvalidParameterError) return } err = request.Process() if err != nil { errorResponse := err.(*protocols.ErrorResponse) log.WithFields(errorResponse.LogData).Error(errorResponse.Error()) server.Write(w, errorResponse) return } err = request.Validate() if err != nil { errorResponse := err.(*protocols.ErrorResponse) log.WithFields(errorResponse.LogData).Error(errorResponse.Error()) server.Write(w, errorResponse) return } sequenceNumber, err := strconv.ParseUint(request.SequenceNumber, 10, 64) if err != nil { errorResponse := protocols.NewInvalidParameterError("sequence_number", request.SequenceNumber) log.WithFields(errorResponse.LogData).Error(errorResponse.Error()) server.Write(w, errorResponse) return } mutators := []b.TransactionMutator{ b.SourceAccount{request.Source}, b.Sequence{sequenceNumber}, b.Network{rh.Config.NetworkPassphrase}, } for _, operation := range request.Operations { mutators = append(mutators, operation.Body.ToTransactionMutator()) } tx := b.Transaction(mutators...) if tx.Err != nil { log.WithFields(log.Fields{"err": err, "request": request}).Error("TransactionBuilder returned error") server.Write(w, protocols.InternalServerError) return } txe := tx.Sign(request.Signers...) txeB64, err := txe.Base64() if err != nil { log.WithFields(log.Fields{"err": err, "request": request}).Error("Error encoding transaction envelope") server.Write(w, protocols.InternalServerError) return } server.Write(w, &bridge.BuilderResponse{TransactionEnvelope: txeB64}) }
func (rh *RequestHandler) Payment(w http.ResponseWriter, r *http.Request) { source := r.PostFormValue("source") sourceKeypair, err := keypair.Parse(source) if err != nil { log.WithFields(log.Fields{"source": source}).Print("Invalid source parameter") errorBadRequest(w, errorResponseString("invalid_source", "source parameter is invalid")) return } destination := r.PostFormValue("destination") destinationObject, err := rh.AddressResolver.Resolve(destination) if err != nil { log.WithFields(log.Fields{"destination": destination}).Print("Cannot resolve address") errorBadRequest(w, errorResponseString("invalid_destination", "Cannot resolve destination")) return } _, err = keypair.Parse(destinationObject.AccountId) if err != nil { log.WithFields(log.Fields{"AccountId": destinationObject.AccountId}).Print("Invalid AccountId in destination") errorBadRequest(w, errorResponseString("invalid_destination", "destination parameter is invalid")) return } amount := r.PostFormValue("amount") assetCode := r.PostFormValue("asset_code") assetIssuer := r.PostFormValue("asset_issuer") var operationBuilder interface{} if assetCode != "" && assetIssuer != "" { issuerKeypair, err := keypair.Parse(assetIssuer) if err != nil { log.WithFields(log.Fields{"asset_issuer": assetIssuer}).Print("Invalid asset_issuer parameter") errorBadRequest(w, errorResponseString("invalid_issuer", "asset_issuer parameter is invalid")) 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.") errorBadRequest(w, errorResponseString("asset_missing_param", "When passing asser both params: `asset_code`, `asset_issuer` are required")) return } memoType := r.PostFormValue("memo_type") memo := r.PostFormValue("memo") if !(((memoType == "") && (memo == "")) || ((memoType != "") && (memo != ""))) { log.Print("Missing one of memo params.") errorBadRequest(w, errorResponseString("memo_missing_param", "When passing memo both params: `memo_type`, `memo` are required")) return } if destinationObject.MemoType != nil { if memoType != "" { log.Print("Memo given in request but federation returned memo fields.") errorBadRequest(w, errorResponseString("cannot_use_memo", "Memo given in request but federation returned memo fields")) return } memoType = *destinationObject.MemoType memo = *destinationObject.Memo } var memoMutator interface{} switch { case memoType == "": break case memoType == "id": id, err := strconv.ParseUint(memo, 10, 64) if err != nil { log.WithFields(log.Fields{"memo": memo}).Print("Cannot convert memo_id value to uint64") errorBadRequest(w, errorResponseString("cannot_convert_memo_id", "Cannot convert memo_id value")) return } memoMutator = b.MemoID{id} case memoType == "text": memoMutator = &b.MemoText{memo} default: log.Print("Not supported memo type: ", memoType) errorBadRequest(w, errorResponseString("memo_not_supported", "Not supported memo type")) return } accountResponse, err := rh.Horizon.LoadAccount(sourceKeypair.Address()) if err != nil { log.WithFields(log.Fields{"error": err}).Error("Cannot load source account") errorBadRequest(w, errorResponseString("source_not_exist", "source account does not exist")) return } sequenceNumber, err := strconv.ParseUint(accountResponse.SequenceNumber, 10, 64) if err != nil { log.WithFields(log.Fields{"error": err}).Error("Cannot convert SequenceNumber") errorServerError(w) return } transactionMutators := []b.TransactionMutator{ b.SourceAccount{source}, b.Sequence{sequenceNumber + 1}, b.Network{rh.Config.NetworkPassphrase}, operationBuilder.(b.TransactionMutator), } if memoMutator != nil { transactionMutators = append(transactionMutators, memoMutator.(b.TransactionMutator)) } tx := b.Transaction(transactionMutators...) if tx.Err != nil { log.WithFields(log.Fields{"err": tx.Err}).Print("Transaction builder error") // TODO when build.OperationBuilder interface is ready check for // create_account and payment errors separately switch { case tx.Err.Error() == "Asset code length is invalid": errorBadRequest(w, errorResponseString("asset_code_invalid", "asset_code param is invalid")) case strings.Contains(tx.Err.Error(), "cannot parse amount"): errorBadRequest(w, errorResponseString("invalid_amount", "amount is invalid")) default: log.WithFields(log.Fields{"err": tx.Err}).Print("Transaction builder error") errorServerError(w) } return } txe := tx.Sign(source) txeB64, err := txe.Base64() if err != nil { log.WithFields(log.Fields{"error": err}).Error("Cannot encode transaction envelope") errorServerError(w) return } submitResponse, err := rh.Horizon.SubmitTransaction(txeB64) if err != nil { log.WithFields(log.Fields{"error": err}).Error("Error submitting transaction") errorServerError(w) return } response, err := json.MarshalIndent(submitResponse, "", " ") if err != nil { log.WithFields(log.Fields{"error": err}).Error("Cannot Marshal submitResponse") errorServerError(w) return } if submitResponse.Ledger != nil { w.Write(response) } else { errorBadRequest(w, string(response)) } }
// Payment implements /payment endpoint func (rh *RequestHandler) Payment(w http.ResponseWriter, r *http.Request) { request := &bridge.PaymentRequest{} request.FromRequest(r) err := request.Validate() if err != nil { errorResponse := err.(*protocols.ErrorResponse) log.WithFields(errorResponse.LogData).Error(errorResponse.Error()) server.Write(w, errorResponse) return } if request.Source == "" { request.Source = rh.Config.Accounts.BaseSeed } sourceKeypair, _ := keypair.Parse(request.Source) var submitResponse horizon.SubmitTransactionResponse var submitError error if request.ExtraMemo != "" && rh.Config.Compliance != "" { // Compliance server part sendRequest := request.ToComplianceSendRequest() resp, err := rh.Client.PostForm( rh.Config.Compliance+"/send", sendRequest.ToValues(), ) if err != nil { log.WithFields(log.Fields{"err": err}).Error("Error sending request to compliance server") server.Write(w, protocols.InternalServerError) return } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Error("Error reading compliance server response") server.Write(w, protocols.InternalServerError) return } if resp.StatusCode != 200 { log.WithFields(log.Fields{ "status": resp.StatusCode, "body": string(body), }).Error("Error response from compliance server") server.Write(w, protocols.InternalServerError) return } var complianceSendResponse compliance.SendResponse err = json.Unmarshal(body, &complianceSendResponse) if err != nil { log.Error("Error unmarshalling from compliance server") server.Write(w, protocols.InternalServerError) return } if complianceSendResponse.AuthResponse.InfoStatus == compliance.AuthStatusPending || complianceSendResponse.AuthResponse.TxStatus == compliance.AuthStatusPending { log.WithFields(log.Fields{"response": complianceSendResponse}).Info("Compliance response pending") server.Write(w, bridge.NewPaymentPendingError(complianceSendResponse.AuthResponse.Pending)) return } if complianceSendResponse.AuthResponse.InfoStatus == compliance.AuthStatusDenied || complianceSendResponse.AuthResponse.TxStatus == compliance.AuthStatusDenied { log.WithFields(log.Fields{"response": complianceSendResponse}).Info("Compliance response denied") server.Write(w, bridge.PaymentDenied) return } var tx xdr.Transaction err = xdr.SafeUnmarshalBase64(complianceSendResponse.TransactionXdr, &tx) if err != nil { log.Error("Error unmarshalling transaction returned by compliance server") server.Write(w, protocols.InternalServerError) return } submitResponse, submitError = rh.TransactionSubmitter.SignAndSubmitRawTransaction(request.Source, &tx) } else { // Payment without compliance server destinationObject, _, err := rh.FederationResolver.Resolve(request.Destination) if err != nil { log.WithFields(log.Fields{"destination": request.Destination, "err": err}).Print("Cannot resolve address") server.Write(w, bridge.PaymentCannotResolveDestination) return } _, err = keypair.Parse(destinationObject.AccountID) if err != nil { log.WithFields(log.Fields{"AccountId": destinationObject.AccountID}).Print("Invalid AccountId in destination") server.Write(w, protocols.NewInvalidParameterError("destination", request.Destination)) return } var payWithMutator *b.PayWithPath if request.SendMax != "" { // Path payment var sendAsset b.Asset if request.SendAssetCode == "" && request.SendAssetIssuer == "" { sendAsset = b.NativeAsset() } else { sendAsset = b.CreditAsset(request.SendAssetCode, request.SendAssetIssuer) } payWith := b.PayWith(sendAsset, request.SendMax) 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 == "" { payWith = payWith.Through(b.NativeAsset()) } else { payWith = payWith.Through(b.CreditAsset(code, issuer)) } } payWithMutator = &payWith } var operationBuilder interface{} if request.AssetCode != "" && request.AssetIssuer != "" { mutators := []interface{}{ b.Destination{destinationObject.AccountID}, b.CreditAmount{request.AssetCode, request.AssetIssuer, request.Amount}, } if payWithMutator != nil { mutators = append(mutators, *payWithMutator) } operationBuilder = b.Payment(mutators...) } else { mutators := []interface{}{ b.Destination{destinationObject.AccountID}, b.NativeAmount{request.Amount}, } if payWithMutator != nil { mutators = append(mutators, *payWithMutator) } // 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...) } } memoType := request.MemoType memo := request.Memo if destinationObject.MemoType != "" { if request.MemoType != "" { log.Print("Memo given in request but federation returned memo fields.") server.Write(w, bridge.PaymentCannotUseMemo) return } memoType = destinationObject.MemoType memo = destinationObject.Memo } var memoMutator interface{} switch { case memoType == "": break case memoType == "id": id, err := strconv.ParseUint(memo, 10, 64) if err != nil { log.WithFields(log.Fields{"memo": memo}).Print("Cannot convert memo_id value to uint64") server.Write(w, protocols.NewInvalidParameterError("memo", request.Memo)) return } memoMutator = b.MemoID{id} case memoType == "text": memoMutator = &b.MemoText{memo} case memoType == "hash": memoBytes, err := hex.DecodeString(memo) if err != nil || len(memoBytes) != 32 { log.WithFields(log.Fields{"memo": memo}).Print("Cannot decode hash memo value") server.Write(w, protocols.NewInvalidParameterError("memo", request.Memo)) return } var b32 [32]byte copy(b32[:], memoBytes[0:32]) hash := xdr.Hash(b32) memoMutator = &b.MemoHash{hash} default: log.Print("Not supported memo type: ", memoType) server.Write(w, protocols.NewInvalidParameterError("memo", request.Memo)) return } accountResponse, err := rh.Horizon.LoadAccount(sourceKeypair.Address()) if err != nil { log.WithFields(log.Fields{"error": err}).Error("Cannot load source account") server.Write(w, bridge.PaymentSourceNotExist) return } sequenceNumber, err := strconv.ParseUint(accountResponse.SequenceNumber, 10, 64) if err != nil { log.WithFields(log.Fields{"error": err}).Error("Cannot convert SequenceNumber") server.Write(w, protocols.InternalServerError) return } transactionMutators := []b.TransactionMutator{ b.SourceAccount{request.Source}, b.Sequence{sequenceNumber + 1}, b.Network{rh.Config.NetworkPassphrase}, operationBuilder.(b.TransactionMutator), } if memoMutator != nil { transactionMutators = append(transactionMutators, memoMutator.(b.TransactionMutator)) } tx := b.Transaction(transactionMutators...) if tx.Err != nil { log.WithFields(log.Fields{"err": tx.Err}).Print("Transaction builder error") // TODO when build.OperationBuilder interface is ready check for // create_account and payment errors separately switch { case tx.Err.Error() == "Asset code length is invalid": server.Write( w, protocols.NewInvalidParameterError("asset_code", request.AssetCode), ) case strings.Contains(tx.Err.Error(), "cannot parse amount"): server.Write( w, protocols.NewInvalidParameterError("amount", request.Amount), ) default: log.WithFields(log.Fields{"err": tx.Err}).Print("Transaction builder error") server.Write(w, protocols.InternalServerError) } return } txe := tx.Sign(request.Source) txeB64, err := txe.Base64() if err != nil { log.WithFields(log.Fields{"error": err}).Error("Cannot encode transaction envelope") server.Write(w, protocols.InternalServerError) return } submitResponse, submitError = rh.Horizon.SubmitTransaction(txeB64) } if submitError != nil { log.WithFields(log.Fields{"error": submitError}).Error("Error submitting transaction") server.Write(w, protocols.InternalServerError) return } errorResponse := bridge.ErrorFromHorizonResponse(submitResponse) if errorResponse != nil { log.WithFields(errorResponse.LogData).Error(errorResponse.Error()) server.Write(w, errorResponse) return } // Path payment send amount if submitResponse.ResultXdr != nil { var transactionResult xdr.TransactionResult reader := strings.NewReader(*submitResponse.ResultXdr) b64r := base64.NewDecoder(base64.StdEncoding, reader) _, err := xdr.Unmarshal(b64r, &transactionResult) if err == nil && transactionResult.Result.Code == xdr.TransactionResultCodeTxSuccess { operationResult := (*transactionResult.Result.Results)[0] if operationResult.Tr.PathPaymentResult != nil { sendAmount := operationResult.Tr.PathPaymentResult.SendAmount() submitResponse.SendAmount = amount.String(sendAmount) } } } server.Write(w, &submitResponse) }
func (ts *TransactionSubmitter) SubmitTransaction(seed string, operation, memo interface{}) (response horizon.SubmitTransactionResponse, err error) { account, err := ts.GetAccount(seed) if err != nil { return } var sequenceNumber uint64 account.Mutex.Lock() account.SequenceNumber++ sequenceNumber = account.SequenceNumber account.Mutex.Unlock() operationMutator, ok := operation.(build.TransactionMutator) if !ok { ts.log.Error("Cannot cast operationMutator to build.TransactionMutator") err = errors.New("Cannot cast operationMutator to build.TransactionMutator") return } mutators := []build.TransactionMutator{ build.SourceAccount{account.Seed}, build.Sequence{sequenceNumber}, ts.Network, operationMutator, } if memo != nil { memoMutator, ok := memo.(build.TransactionMutator) if !ok { ts.log.Error("Cannot cast memo to build.TransactionMutator") err = errors.New("Cannot cast memo to build.TransactionMutator") return } mutators = append(mutators, memoMutator) } tx := build.Transaction(mutators...) txe := tx.Sign(seed) txeB64, err := txe.Base64() if err != nil { ts.log.Error("Cannot encode transaction envelope ", err) return } sentTransaction := &db.SentTransaction{ Status: "sending", Source: account.Keypair.Address(), SubmittedAt: time.Now(), EnvelopeXdr: txeB64, } err = ts.EntityManager.Persist(sentTransaction) if err != nil { return } response, err = ts.Horizon.SubmitTransaction(txeB64) if err != nil { ts.log.Error("Error submitting transaction ", err) return } if response.Ledger != nil { sentTransaction.MarkSucceeded(*response.Ledger) } else { sentTransaction.MarkFailed(response.Extras.ResultXdr) } err = ts.EntityManager.Persist(sentTransaction) if err != nil { return } // Sync sequence number if response.Errors != nil && response.Errors.TransactionErrorCode == "transaction_bad_seq" { account.Mutex.Lock() ts.log.Print("Syncing sequence number for ", account.Keypair.Address()) accountResponse, _ := ts.Horizon.LoadAccount(account.Keypair.Address()) account.SequenceNumber, err = strconv.ParseUint(accountResponse.SequenceNumber, 10, 64) account.Mutex.Unlock() } return }