diff --git a/src/main/java/org/stellar/sdk/responses/SubmitTransactionResponse.java b/src/main/java/org/stellar/sdk/responses/SubmitTransactionResponse.java
index 0aebc8c3f..0658c9c31 100644
--- a/src/main/java/org/stellar/sdk/responses/SubmitTransactionResponse.java
+++ b/src/main/java/org/stellar/sdk/responses/SubmitTransactionResponse.java
@@ -2,8 +2,14 @@
import com.google.gson.annotations.SerializedName;
+import org.apache.commons.codec.binary.Base64;
import org.stellar.sdk.Server;
+import org.stellar.sdk.xdr.OperationType;
+import org.stellar.sdk.xdr.TransactionResult;
+import org.stellar.sdk.xdr.XdrDataInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
import java.util.ArrayList;
/**
@@ -15,13 +21,19 @@ public class SubmitTransactionResponse extends Response {
private final String hash;
@SerializedName("ledger")
private final Long ledger;
+ @SerializedName("envelope_xdr")
+ private final String envelopeXdr;
+ @SerializedName("result_xdr")
+ private final String resultXdr;
@SerializedName("extras")
private final Extras extras;
- SubmitTransactionResponse(Extras extras, Long ledger, String hash) {
+ SubmitTransactionResponse(Extras extras, Long ledger, String hash, String envelopeXdr, String resultXdr) {
this.extras = extras;
this.ledger = ledger;
this.hash = hash;
+ this.envelopeXdr = envelopeXdr;
+ this.resultXdr = resultXdr;
}
public boolean isSuccess() {
@@ -36,6 +48,56 @@ public Long getLedger() {
return ledger;
}
+ public String getEnvelopeXdr() {
+ if (this.isSuccess()) {
+ return this.envelopeXdr;
+ } else {
+ return this.getExtras().getEnvelopeXdr();
+ }
+ }
+
+ public String getResultXdr() {
+ if (this.isSuccess()) {
+ return this.resultXdr;
+ } else {
+ return this.getExtras().getResultXdr();
+ }
+ }
+
+ /**
+ * Helper method that returns Offer ID for ManageOffer from TransactionResult Xdr.
+ * This is helpful when you need ID of an offer to update it later.
+ * @param position Position of ManageOffer operation. If ManageOffer is second operation in this transaction this should be equal 1
.
+ * @return Offer ID or null
when operation at position
is not a ManageOffer operation or error has occurred.
+ */
+ public Long getOfferIdFromResult(int position) {
+ if (!this.isSuccess()) {
+ return null;
+ }
+
+ Base64 base64Codec = new Base64();
+ byte[] bytes = base64Codec.decode(this.getResultXdr());
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
+ XdrDataInputStream xdrInputStream = new XdrDataInputStream(inputStream);
+ TransactionResult result;
+
+ try {
+ result = TransactionResult.decode(xdrInputStream);
+ } catch (IOException e) {
+ return null;
+ }
+
+ if (result.getResult().getResults()[position] == null) {
+ return null;
+ }
+
+ if (result.getResult().getResults()[position].getTr().getDiscriminant() != OperationType.MANAGE_OFFER) {
+ return null;
+ }
+
+ return result.getResult().getResults()[0].getTr().getManageOfferResult().getSuccess().getOffer().getOffer().getOfferID().getUint64();
+ }
+
/**
* Additional information returned by a server. This will be null
if transaction succeeded.
*/
diff --git a/src/test/java/org/stellar/sdk/responses/SubmitTransactionResponseTest.java b/src/test/java/org/stellar/sdk/responses/SubmitTransactionResponseTest.java
new file mode 100644
index 000000000..d4f9bd194
--- /dev/null
+++ b/src/test/java/org/stellar/sdk/responses/SubmitTransactionResponseTest.java
@@ -0,0 +1,83 @@
+package org.stellar.sdk.responses;
+
+import junit.framework.TestCase;
+
+import org.junit.Test;
+
+public class SubmitTransactionResponseTest extends TestCase {
+ @Test
+ public void testDeserializeTransactionFailureResponse() {
+ String json = "{\n" +
+ " \"type\": \"https://stellar.org/horizon-errors/transaction_failed\",\n" +
+ " \"title\": \"Transaction Failed\",\n" +
+ " \"status\": 400,\n" +
+ " \"detail\": \"The transaction failed when submitted to the stellar network. The `extras.result_codes` field on this response contains further details. Descriptions of each code can be found at: https://www.stellar.org/developers/learn/concepts/list-of-operations.html\",\n" +
+ " \"instance\": \"horizon-testnet-001.prd.stellar001.internal.stellar-ops.com/4elYz2fHhC-528285\",\n" +
+ " \"extras\": {\n" +
+ " \"envelope_xdr\": \"AAAAAKpmDL6Z4hvZmkTBkYpHftan4ogzTaO4XTB7joLgQnYYAAAAZAAAAAAABeoyAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAABAAAAAD3sEVVGZGi/NoC3ta/8f/YZKMzyi9ZJpOi0H47x7IqYAAAAAAAAAAAF9eEAAAAAAAAAAAA=\",\n" +
+ " \"result_codes\": {\n" +
+ " \"transaction\": \"tx_no_source_account\"\n" +
+ " },\n" +
+ " \"result_xdr\": \"AAAAAAAAAAD////4AAAAAA==\"\n" +
+ " }\n" +
+ "}";
+
+ SubmitTransactionResponse submitTransactionResponse = GsonSingleton.getInstance().fromJson(json, SubmitTransactionResponse.class);
+ assertEquals(submitTransactionResponse.isSuccess(), false);
+ assertEquals(submitTransactionResponse.getEnvelopeXdr(), "AAAAAKpmDL6Z4hvZmkTBkYpHftan4ogzTaO4XTB7joLgQnYYAAAAZAAAAAAABeoyAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAABAAAAAD3sEVVGZGi/NoC3ta/8f/YZKMzyi9ZJpOi0H47x7IqYAAAAAAAAAAAF9eEAAAAAAAAAAAA=");
+ assertEquals(submitTransactionResponse.getResultXdr(), "AAAAAAAAAAD////4AAAAAA==");
+ assertEquals(submitTransactionResponse.getExtras().getResultCodes().getTransactionResultCode(), "tx_no_source_account");
+ }
+
+ @Test
+ public void testDeserializeOperationFailureResponse() {
+ String json = "{\n" +
+ " \"type\": \"https://stellar.org/horizon-errors/transaction_failed\",\n" +
+ " \"title\": \"Transaction Failed\",\n" +
+ " \"status\": 400,\n" +
+ " \"detail\": \"The transaction failed when submitted to the stellar network. The `extras.result_codes` field on this response contains further details. Descriptions of each code can be found at: https://www.stellar.org/developers/learn/concepts/list-of-operations.html\",\n" +
+ " \"instance\": \"horizon-testnet-001.prd.stellar001.internal.stellar-ops.com/4elYz2fHhC-528366\",\n" +
+ " \"extras\": {\n" +
+ " \"envelope_xdr\": \"AAAAAF2O0axA67+p2jMunG6G188kDSHIvqQ13d9l29YCSA/uAAAAZAAvvc0AAAABAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAABAAAAAD3sEVVGZGi/NoC3ta/8f/YZKMzyi9ZJpOi0H47x7IqYAAAAAAAAAAAF9eEAAAAAAAAAAAECSA/uAAAAQFuZVAjftHa+JZes1VxSk8naOfjjAz9V86mY1AZf8Ik6PtTsBpDsCfG57EYsq4jWyZcT+vhXyWsw5evF1ELqMw4=\",\n" +
+ " \"result_codes\": {\n" +
+ " \"transaction\": \"tx_failed\",\n" +
+ " \"operations\": [\n" +
+ " \"op_no_destination\"\n" +
+ " ]\n" +
+ " },\n" +
+ " \"result_xdr\": \"AAAAAAAAAGT/////AAAAAQAAAAAAAAAB////+wAAAAA=\"\n" +
+ " }\n" +
+ "}";
+
+ SubmitTransactionResponse submitTransactionResponse = GsonSingleton.getInstance().fromJson(json, SubmitTransactionResponse.class);
+ assertEquals(submitTransactionResponse.isSuccess(), false);
+ assertEquals(submitTransactionResponse.getEnvelopeXdr(), "AAAAAF2O0axA67+p2jMunG6G188kDSHIvqQ13d9l29YCSA/uAAAAZAAvvc0AAAABAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAABAAAAAD3sEVVGZGi/NoC3ta/8f/YZKMzyi9ZJpOi0H47x7IqYAAAAAAAAAAAF9eEAAAAAAAAAAAECSA/uAAAAQFuZVAjftHa+JZes1VxSk8naOfjjAz9V86mY1AZf8Ik6PtTsBpDsCfG57EYsq4jWyZcT+vhXyWsw5evF1ELqMw4=");
+ assertEquals(submitTransactionResponse.getResultXdr(), "AAAAAAAAAGT/////AAAAAQAAAAAAAAAB////+wAAAAA=");
+ assertEquals(submitTransactionResponse.getExtras().getResultCodes().getTransactionResultCode(), "tx_failed");
+ assertEquals(submitTransactionResponse.getExtras().getResultCodes().getOperationsResultCodes().get(0), "op_no_destination");
+ }
+
+ @Test
+ public void testDeserializeSuccessResponse() {
+ String json = "{\n" +
+ " \"_links\": {\n" +
+ " \"transaction\": {\n" +
+ " \"href\": \"https://horizon-testnet.stellar.org/transactions/ee14b93fcd31d4cfe835b941a0a8744e23a6677097db1fafe0552d8657bed940\"\n" +
+ " }\n" +
+ " },\n" +
+ " \"hash\": \"ee14b93fcd31d4cfe835b941a0a8744e23a6677097db1fafe0552d8657bed940\",\n" +
+ " \"ledger\": 3128812,\n" +
+ " \"envelope_xdr\": \"AAAAADSMMRmQGDH6EJzkgi/7PoKhphMHyNGQgDp2tlS/dhGXAAAAZAAT3TUAAAAwAAAAAAAAAAAAAAABAAAAAAAAAAMAAAABSU5SAAAAAAA0jDEZkBgx+hCc5IIv+z6CoaYTB8jRkIA6drZUv3YRlwAAAAFVU0QAAAAAADSMMRmQGDH6EJzkgi/7PoKhphMHyNGQgDp2tlS/dhGXAAAAAAX14QAAAAAKAAAAAQAAAAAAAAAAAAAAAAAAAAG/dhGXAAAAQLuStfImg0OeeGAQmvLkJSZ1MPSkCzCYNbGqX5oYNuuOqZ5SmWhEsC7uOD9ha4V7KengiwNlc0oMNqBVo22S7gk=\",\n" +
+ " \"result_xdr\": \"AAAAAAAAAGQAAAAAAAAAAQAAAAAAAAADAAAAAAAAAAAAAAAAAAAAADSMMRmQGDH6EJzkgi/7PoKhphMHyNGQgDp2tlS/dhGXAAAAAAAAAPEAAAABSU5SAAAAAAA0jDEZkBgx+hCc5IIv+z6CoaYTB8jRkIA6drZUv3YRlwAAAAFVU0QAAAAAADSMMRmQGDH6EJzkgi/7PoKhphMHyNGQgDp2tlS/dhGXAAAAAAX14QAAAAAKAAAAAQAAAAAAAAAAAAAAAA==\",\n" +
+ " \"result_meta_xdr\": \"AAAAAAAAAAEAAAACAAAAAAAvoHwAAAACAAAAADSMMRmQGDH6EJzkgi/7PoKhphMHyNGQgDp2tlS/dhGXAAAAAAAAAPEAAAABSU5SAAAAAAA0jDEZkBgx+hCc5IIv+z6CoaYTB8jRkIA6drZUv3YRlwAAAAFVU0QAAAAAADSMMRmQGDH6EJzkgi/7PoKhphMHyNGQgDp2tlS/dhGXAAAAAAX14QAAAAAKAAAAAQAAAAAAAAAAAAAAAAAAAAEAL6B8AAAAAAAAAAA0jDEZkBgx+hCc5IIv+z6CoaYTB8jRkIA6drZUv3YRlwAAABZ9zvNAABPdNQAAADAAAAAEAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAA==\"\n" +
+ "}";
+
+ SubmitTransactionResponse submitTransactionResponse = GsonSingleton.getInstance().fromJson(json, SubmitTransactionResponse.class);
+ assertEquals(submitTransactionResponse.isSuccess(), true);
+ assertEquals(submitTransactionResponse.getHash(), "ee14b93fcd31d4cfe835b941a0a8744e23a6677097db1fafe0552d8657bed940");
+ assertEquals(submitTransactionResponse.getLedger(), new Long(3128812));
+ assertEquals(submitTransactionResponse.getEnvelopeXdr(), "AAAAADSMMRmQGDH6EJzkgi/7PoKhphMHyNGQgDp2tlS/dhGXAAAAZAAT3TUAAAAwAAAAAAAAAAAAAAABAAAAAAAAAAMAAAABSU5SAAAAAAA0jDEZkBgx+hCc5IIv+z6CoaYTB8jRkIA6drZUv3YRlwAAAAFVU0QAAAAAADSMMRmQGDH6EJzkgi/7PoKhphMHyNGQgDp2tlS/dhGXAAAAAAX14QAAAAAKAAAAAQAAAAAAAAAAAAAAAAAAAAG/dhGXAAAAQLuStfImg0OeeGAQmvLkJSZ1MPSkCzCYNbGqX5oYNuuOqZ5SmWhEsC7uOD9ha4V7KengiwNlc0oMNqBVo22S7gk=");
+ assertEquals(submitTransactionResponse.getResultXdr(), "AAAAAAAAAGQAAAAAAAAAAQAAAAAAAAADAAAAAAAAAAAAAAAAAAAAADSMMRmQGDH6EJzkgi/7PoKhphMHyNGQgDp2tlS/dhGXAAAAAAAAAPEAAAABSU5SAAAAAAA0jDEZkBgx+hCc5IIv+z6CoaYTB8jRkIA6drZUv3YRlwAAAAFVU0QAAAAAADSMMRmQGDH6EJzkgi/7PoKhphMHyNGQgDp2tlS/dhGXAAAAAAX14QAAAAAKAAAAAQAAAAAAAAAAAAAAAA==");
+ assertEquals(submitTransactionResponse.getOfferIdFromResult(0), new Long(241));
+ }
+}