// 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)
}
Example #3
0
// 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
}