diff --git a/src/main/java/org/stellar/sdk/responses/TransactionResponse.java b/src/main/java/org/stellar/sdk/responses/TransactionResponse.java index 3c591e3c9..77ed26f69 100644 --- a/src/main/java/org/stellar/sdk/responses/TransactionResponse.java +++ b/src/main/java/org/stellar/sdk/responses/TransactionResponse.java @@ -2,7 +2,7 @@ import com.google.common.base.Optional; import com.google.gson.annotations.SerializedName; - +import lombok.Value; import org.stellar.sdk.Memo; import java.math.BigInteger; @@ -49,6 +49,8 @@ public class TransactionResponse extends Response implements Pageable { private List signatures; @SerializedName("fee_bump_transaction") private FeeBumpTransaction feeBumpTransaction; + @SerializedName("preconditions") + private Preconditions preconditions; @SerializedName("inner_transaction") private InnerTransaction innerTransaction; @SerializedName("account_muxed") @@ -112,6 +114,10 @@ public Optional getInner() { return Optional.fromNullable(this.innerTransaction); } + public Optional getPreconditions() { + return Optional.fromNullable(this.preconditions); + } + public String getPagingToken() { return pagingToken; } @@ -164,6 +170,41 @@ public Links getLinks() { return links; } + /** + * Preconditions of a transaction per CAP-21 + */ + @Value + public static class Preconditions { + @SerializedName("timebounds") + TimeBounds timeBounds; + @SerializedName("ledgerbounds") + LedgerBounds ledgerBounds; + @SerializedName("min_account_sequence") + Long minAccountSequence; + @SerializedName("min_account_sequence_age") + long minAccountSequenceAge; + @SerializedName("min_account_sequence_ledger_gap") + long minAccountSequenceLedgerGap; + @SerializedName("extra_signers") + List signatures; + + @Value + public static class TimeBounds { + @SerializedName("min_time") + long minTime; + @SerializedName("max_time") + long maxTime; + } + + @Value + public static class LedgerBounds { + @SerializedName("min_ledger") + long minTime; + @SerializedName("max_ledger") + long maxTime; + } + } + /** * FeeBumpTransaction is only present in a TransactionResponse if the transaction is a fee bump transaction or is * wrapped by a fee bump transaction. The object has two fields: the hash of the fee bump transaction and the diff --git a/src/test/java/org/stellar/sdk/responses/TransactionDeserializerTest.java b/src/test/java/org/stellar/sdk/responses/TransactionDeserializerTest.java index 489ade149..2ef4628aa 100644 --- a/src/test/java/org/stellar/sdk/responses/TransactionDeserializerTest.java +++ b/src/test/java/org/stellar/sdk/responses/TransactionDeserializerTest.java @@ -7,6 +7,8 @@ import org.stellar.sdk.MemoHash; import org.stellar.sdk.MemoNone; +import java.util.Arrays; + import static java.math.BigInteger.valueOf; public class TransactionDeserializerTest extends TestCase { @@ -61,6 +63,7 @@ public void testDeserialize() { assertTrue(transaction.getMemo() instanceof MemoHash); MemoHash memo = (MemoHash) transaction.getMemo(); assertEquals("51041644e83d6ac868c849418b6392ddbe9df53f000000000000000000000000", memo.getHexValue()); + assertFalse(transaction.getPreconditions().isPresent()); assertEquals(transaction.getLinks().getAccount().getHref(), "/accounts/GCUB7JL4APK7LKJ6MZF7Q2JTLHAGNBIUA7XIXD5SQTG52GQ2DAT6XZMK"); assertEquals(transaction.getLinks().getEffects().getHref(), "/transactions/5c2e4dad596941ef944d72741c8f8f1a4282f8f2f141e81d827f44bf365d626b/effects{?cursor,limit,order}"); @@ -84,13 +87,39 @@ public void testDeserializeMuxed() { assertEquals(transaction.getFeeAccountMuxed().get().getId(), valueOf(420l)); } - @Test + @Test public void testDeserializeWithoutMemo() { TransactionResponse transaction = GsonSingleton.getInstance().fromJson(jsonMemoNone, TransactionResponse.class); assertTrue(transaction.getMemo() instanceof MemoNone); assertEquals(transaction.isSuccessful().booleanValue(), false); } + @Test + public void testDeserializePreconditions() { + TransactionResponse transaction = GsonSingleton.getInstance().fromJson(jsonPreconditions, TransactionResponse.class); + assertTrue(transaction.getPreconditions().isPresent()); + assertEquals(transaction.getPreconditions().get().getMinAccountSequence(), Long.valueOf(1)); + assertEquals(transaction.getPreconditions().get().getMinAccountSequenceAge(), 2); + assertEquals(transaction.getPreconditions().get().getMinAccountSequenceLedgerGap(), 3); + assertEquals(transaction.getPreconditions().get().getTimeBounds(), new TransactionResponse.Preconditions.TimeBounds(4,5)); + assertEquals(transaction.getPreconditions().get().getLedgerBounds(), new TransactionResponse.Preconditions.LedgerBounds(6,7)); + assertEquals(transaction.getPreconditions().get().getSignatures(), Arrays.asList("GCUB7JL4APK7LKJ6MZF7Q2JTLHAGNBIUA7XIXD5SQTG52GQ2DAT6XZMK")); + } + + @Test + public void testDeserializePreconditionsEmptySigners() { + TransactionResponse transaction = GsonSingleton.getInstance().fromJson(jsonPreconditionsEmptySigners, TransactionResponse.class); + assertTrue(transaction.getPreconditions().isPresent()); + assertEquals(transaction.getPreconditions().get().getSignatures().size(), 0); + } + + @Test + public void testDeserializePreconditionsUnsetMinAccountSequence() { + TransactionResponse transaction = GsonSingleton.getInstance().fromJson(jsonPreconditionsUnsetMinAccountSeq, TransactionResponse.class); + assertTrue(transaction.getPreconditions().isPresent()); + assertNull(transaction.getPreconditions().get().getMinAccountSequence()); + } + String json = "{\n" + " \"_links\": {\n" + " \"account\": {\n" + @@ -184,6 +213,187 @@ public void testDeserializeWithoutMemo() { " ]\n" + "}"; + String jsonPreconditions = "{\n" + + " \"_links\": {\n" + + " \"account\": {\n" + + " \"href\": \"/accounts/GCUB7JL4APK7LKJ6MZF7Q2JTLHAGNBIUA7XIXD5SQTG52GQ2DAT6XZMK\"\n" + + " },\n" + + " \"effects\": {\n" + + " \"href\": \"/transactions/5c2e4dad596941ef944d72741c8f8f1a4282f8f2f141e81d827f44bf365d626b/effects{?cursor,limit,order}\",\n" + + " \"templated\": true\n" + + " },\n" + + " \"ledger\": {\n" + + " \"href\": \"/ledgers/915744\"\n" + + " },\n" + + " \"operations\": {\n" + + " \"href\": \"/transactions/5c2e4dad596941ef944d72741c8f8f1a4282f8f2f141e81d827f44bf365d626b/operations{?cursor,limit,order}\",\n" + + " \"templated\": true\n" + + " },\n" + + " \"precedes\": {\n" + + " \"href\": \"/transactions?cursor=3933090531512320\\u0026order=asc\"\n" + + " },\n" + + " \"self\": {\n" + + " \"href\": \"/transactions/5c2e4dad596941ef944d72741c8f8f1a4282f8f2f141e81d827f44bf365d626b\"\n" + + " },\n" + + " \"succeeds\": {\n" + + " \"href\": \"/transactions?cursor=3933090531512320\\u0026order=desc\"\n" + + " }\n" + + " },\n" + + " \"id\": \"5c2e4dad596941ef944d72741c8f8f1a4282f8f2f141e81d827f44bf365d626b\",\n" + + " \"paging_token\": \"3933090531512320\",\n" + + " \"successful\": false,\n" + + " \"hash\": \"5c2e4dad596941ef944d72741c8f8f1a4282f8f2f141e81d827f44bf365d626b\",\n" + + " \"ledger\": 915744,\n" + + " \"created_at\": \"2015-11-20T17:01:28Z\",\n" + + " \"source_account\": \"GCUB7JL4APK7LKJ6MZF7Q2JTLHAGNBIUA7XIXD5SQTG52GQ2DAT6XZMK\",\n" + + " \"source_account_sequence\": 2373051035426646,\n" + + " \"max_fee\": 200,\n" + + " \"fee_charged\": 100,\n" + + " \"operation_count\": 1,\n" + + " \"envelope_xdr\": \"AAAAAKgfpXwD1fWpPmZL+GkzWcBmhRQH7ouPsoTN3RoaGCfrAAAAZAAIbkcAAB9WAAAAAAAAAANRBBZE6D1qyGjISUGLY5Ldvp31PwAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAP1qe44j+i4uIT+arbD4QDQBt8ryEeJd7a0jskQ3nwDeAAAAAAAAAADA7RnarSzCwj3OT+M2btCMFpVBdqxJS+Sr00qBjtFv7gAAAABLCs/QAAAAAAAAAAEaGCfrAAAAQG/56Cj2J8W/KCZr+oC4sWND1CTGWfaccHNtuibQH8kZIb+qBSDY94g7hiaAXrlIeg9b7oz/XuP3x9MWYw2jtwM=\",\n" + + " \"result_xdr\": \"AAAAAAAAAGQAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAA=\",\n" + + " \"result_meta_xdr\": \"AAAAAAAAAAEAAAACAAAAAAAN+SAAAAAAAAAAAMDtGdqtLMLCPc5P4zZu0IwWlUF2rElL5KvTSoGO0W/uAAAAAEsKz9AADfkgAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAQAN+SAAAAAAAAAAAP1qe44j+i4uIT+arbD4QDQBt8ryEeJd7a0jskQ3nwDeAAHp6WMr55YACD1BAAAAHgAAAAoAAAAAAAAAAAAAAAABAAAAAAAACgAAAAARC07BokpLTOF+/vVKBwiAlop7hHGJTNeGGlY4MoPykwAAAAEAAAAAK+Lzfd3yDD+Ov0GbYu1g7SaIBrKZeBUxoCunkLuI7aoAAAABAAAAAERmsKL73CyLV/HvjyQCERDXXpWE70Xhyb6MR5qPO3yQAAAAAQAAAABSORGwAdyuanN3sNOHqNSpACyYdkUM3L8VafUu69EvEgAAAAEAAAAAeCzqJNkMM/jLvyuMIfyFHljBlLCtDyj17RMycPuNtRMAAAABAAAAAIEi4R7juq15ymL00DNlAddunyFT4FyUD4muC4t3bobdAAAAAQAAAACaNpLL5YMfjOTdXVEqrAh99LM12sN6He6pHgCRAa1f1QAAAAEAAAAAqB+lfAPV9ak+Zkv4aTNZwGaFFAfui4+yhM3dGhoYJ+sAAAABAAAAAMNJrEvdMg6M+M+n4BDIdzsVSj/ZI9SvAp7mOOsvAD/WAAAAAQAAAADbHA6xiKB1+G79mVqpsHMOleOqKa5mxDpP5KEp/Xdz9wAAAAEAAAAAAAAAAA==\",\n" + + " \"memo_type\": \"none\",\n" + + " \"signatures\": [\n" + + " \"b/noKPYnxb8oJmv6gLixY0PUJMZZ9pxwc226JtAfyRkhv6oFINj3iDuGJoBeuUh6D1vujP9e4/fH0xZjDaO3Aw==\"\n" + + " ],\n" + + " \"preconditions\": {\n" + + " \"timebounds\": {\n" + + " \"min_time\": \"4\",\n" + + " \"max_time\": \"5\"\n" + + " },\n" + + " \"ledgerbounds\": {\n" + + " \"min_ledger\": 6,\n" + + " \"max_ledger\": 7\n" + + " },\n" + + " \"min_account_sequence\": \"1\",\n" + + " \"min_account_sequence_age\": \"2\",\n" + + " \"min_account_sequence_ledger_gap\": 3,\n" + + " \"extra_signers\": [\n" + + " \"GCUB7JL4APK7LKJ6MZF7Q2JTLHAGNBIUA7XIXD5SQTG52GQ2DAT6XZMK\"\n" + + " ]\n" + + " }\n" + + "}"; + + String jsonPreconditionsEmptySigners = "{\n" + + " \"_links\": {\n" + + " \"account\": {\n" + + " \"href\": \"/accounts/GCUB7JL4APK7LKJ6MZF7Q2JTLHAGNBIUA7XIXD5SQTG52GQ2DAT6XZMK\"\n" + + " },\n" + + " \"effects\": {\n" + + " \"href\": \"/transactions/5c2e4dad596941ef944d72741c8f8f1a4282f8f2f141e81d827f44bf365d626b/effects{?cursor,limit,order}\",\n" + + " \"templated\": true\n" + + " },\n" + + " \"ledger\": {\n" + + " \"href\": \"/ledgers/915744\"\n" + + " },\n" + + " \"operations\": {\n" + + " \"href\": \"/transactions/5c2e4dad596941ef944d72741c8f8f1a4282f8f2f141e81d827f44bf365d626b/operations{?cursor,limit,order}\",\n" + + " \"templated\": true\n" + + " },\n" + + " \"precedes\": {\n" + + " \"href\": \"/transactions?cursor=3933090531512320\\u0026order=asc\"\n" + + " },\n" + + " \"self\": {\n" + + " \"href\": \"/transactions/5c2e4dad596941ef944d72741c8f8f1a4282f8f2f141e81d827f44bf365d626b\"\n" + + " },\n" + + " \"succeeds\": {\n" + + " \"href\": \"/transactions?cursor=3933090531512320\\u0026order=desc\"\n" + + " }\n" + + " },\n" + + " \"id\": \"5c2e4dad596941ef944d72741c8f8f1a4282f8f2f141e81d827f44bf365d626b\",\n" + + " \"paging_token\": \"3933090531512320\",\n" + + " \"successful\": false,\n" + + " \"hash\": \"5c2e4dad596941ef944d72741c8f8f1a4282f8f2f141e81d827f44bf365d626b\",\n" + + " \"ledger\": 915744,\n" + + " \"created_at\": \"2015-11-20T17:01:28Z\",\n" + + " \"source_account\": \"GCUB7JL4APK7LKJ6MZF7Q2JTLHAGNBIUA7XIXD5SQTG52GQ2DAT6XZMK\",\n" + + " \"source_account_sequence\": 2373051035426646,\n" + + " \"max_fee\": 200,\n" + + " \"fee_charged\": 100,\n" + + " \"operation_count\": 1,\n" + + " \"envelope_xdr\": \"AAAAAKgfpXwD1fWpPmZL+GkzWcBmhRQH7ouPsoTN3RoaGCfrAAAAZAAIbkcAAB9WAAAAAAAAAANRBBZE6D1qyGjISUGLY5Ldvp31PwAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAP1qe44j+i4uIT+arbD4QDQBt8ryEeJd7a0jskQ3nwDeAAAAAAAAAADA7RnarSzCwj3OT+M2btCMFpVBdqxJS+Sr00qBjtFv7gAAAABLCs/QAAAAAAAAAAEaGCfrAAAAQG/56Cj2J8W/KCZr+oC4sWND1CTGWfaccHNtuibQH8kZIb+qBSDY94g7hiaAXrlIeg9b7oz/XuP3x9MWYw2jtwM=\",\n" + + " \"result_xdr\": \"AAAAAAAAAGQAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAA=\",\n" + + " \"result_meta_xdr\": \"AAAAAAAAAAEAAAACAAAAAAAN+SAAAAAAAAAAAMDtGdqtLMLCPc5P4zZu0IwWlUF2rElL5KvTSoGO0W/uAAAAAEsKz9AADfkgAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAQAN+SAAAAAAAAAAAP1qe44j+i4uIT+arbD4QDQBt8ryEeJd7a0jskQ3nwDeAAHp6WMr55YACD1BAAAAHgAAAAoAAAAAAAAAAAAAAAABAAAAAAAACgAAAAARC07BokpLTOF+/vVKBwiAlop7hHGJTNeGGlY4MoPykwAAAAEAAAAAK+Lzfd3yDD+Ov0GbYu1g7SaIBrKZeBUxoCunkLuI7aoAAAABAAAAAERmsKL73CyLV/HvjyQCERDXXpWE70Xhyb6MR5qPO3yQAAAAAQAAAABSORGwAdyuanN3sNOHqNSpACyYdkUM3L8VafUu69EvEgAAAAEAAAAAeCzqJNkMM/jLvyuMIfyFHljBlLCtDyj17RMycPuNtRMAAAABAAAAAIEi4R7juq15ymL00DNlAddunyFT4FyUD4muC4t3bobdAAAAAQAAAACaNpLL5YMfjOTdXVEqrAh99LM12sN6He6pHgCRAa1f1QAAAAEAAAAAqB+lfAPV9ak+Zkv4aTNZwGaFFAfui4+yhM3dGhoYJ+sAAAABAAAAAMNJrEvdMg6M+M+n4BDIdzsVSj/ZI9SvAp7mOOsvAD/WAAAAAQAAAADbHA6xiKB1+G79mVqpsHMOleOqKa5mxDpP5KEp/Xdz9wAAAAEAAAAAAAAAAA==\",\n" + + " \"memo_type\": \"none\",\n" + + " \"signatures\": [\n" + + " \"b/noKPYnxb8oJmv6gLixY0PUJMZZ9pxwc226JtAfyRkhv6oFINj3iDuGJoBeuUh6D1vujP9e4/fH0xZjDaO3Aw==\"\n" + + " ],\n" + + " \"preconditions\": {\n" + + " \"timebounds\": {\n" + + " \"min_time\": \"4\",\n" + + " \"max_time\": \"5\"\n" + + " },\n" + + " \"ledgerbounds\": {\n" + + " \"min_ledger\": 6,\n" + + " \"max_ledger\": 7\n" + + " },\n" + + " \"min_account_sequence\": \"1\",\n" + + " \"min_account_sequence_age\": \"2\",\n" + + " \"min_account_sequence_ledger_gap\": 3,\n" + + " \"extra_signers\": [\n]\n" + + " }\n" + + "}"; + + String jsonPreconditionsUnsetMinAccountSeq = "{\n" + + " \"_links\": {\n" + + " \"account\": {\n" + + " \"href\": \"/accounts/GCUB7JL4APK7LKJ6MZF7Q2JTLHAGNBIUA7XIXD5SQTG52GQ2DAT6XZMK\"\n" + + " },\n" + + " \"effects\": {\n" + + " \"href\": \"/transactions/5c2e4dad596941ef944d72741c8f8f1a4282f8f2f141e81d827f44bf365d626b/effects{?cursor,limit,order}\",\n" + + " \"templated\": true\n" + + " },\n" + + " \"ledger\": {\n" + + " \"href\": \"/ledgers/915744\"\n" + + " },\n" + + " \"operations\": {\n" + + " \"href\": \"/transactions/5c2e4dad596941ef944d72741c8f8f1a4282f8f2f141e81d827f44bf365d626b/operations{?cursor,limit,order}\",\n" + + " \"templated\": true\n" + + " },\n" + + " \"precedes\": {\n" + + " \"href\": \"/transactions?cursor=3933090531512320\\u0026order=asc\"\n" + + " },\n" + + " \"self\": {\n" + + " \"href\": \"/transactions/5c2e4dad596941ef944d72741c8f8f1a4282f8f2f141e81d827f44bf365d626b\"\n" + + " },\n" + + " \"succeeds\": {\n" + + " \"href\": \"/transactions?cursor=3933090531512320\\u0026order=desc\"\n" + + " }\n" + + " },\n" + + " \"id\": \"5c2e4dad596941ef944d72741c8f8f1a4282f8f2f141e81d827f44bf365d626b\",\n" + + " \"paging_token\": \"3933090531512320\",\n" + + " \"successful\": false,\n" + + " \"hash\": \"5c2e4dad596941ef944d72741c8f8f1a4282f8f2f141e81d827f44bf365d626b\",\n" + + " \"ledger\": 915744,\n" + + " \"created_at\": \"2015-11-20T17:01:28Z\",\n" + + " \"source_account\": \"GCUB7JL4APK7LKJ6MZF7Q2JTLHAGNBIUA7XIXD5SQTG52GQ2DAT6XZMK\",\n" + + " \"source_account_sequence\": 2373051035426646,\n" + + " \"max_fee\": 200,\n" + + " \"fee_charged\": 100,\n" + + " \"operation_count\": 1,\n" + + " \"envelope_xdr\": \"AAAAAKgfpXwD1fWpPmZL+GkzWcBmhRQH7ouPsoTN3RoaGCfrAAAAZAAIbkcAAB9WAAAAAAAAAANRBBZE6D1qyGjISUGLY5Ldvp31PwAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAP1qe44j+i4uIT+arbD4QDQBt8ryEeJd7a0jskQ3nwDeAAAAAAAAAADA7RnarSzCwj3OT+M2btCMFpVBdqxJS+Sr00qBjtFv7gAAAABLCs/QAAAAAAAAAAEaGCfrAAAAQG/56Cj2J8W/KCZr+oC4sWND1CTGWfaccHNtuibQH8kZIb+qBSDY94g7hiaAXrlIeg9b7oz/XuP3x9MWYw2jtwM=\",\n" + + " \"result_xdr\": \"AAAAAAAAAGQAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAA=\",\n" + + " \"result_meta_xdr\": \"AAAAAAAAAAEAAAACAAAAAAAN+SAAAAAAAAAAAMDtGdqtLMLCPc5P4zZu0IwWlUF2rElL5KvTSoGO0W/uAAAAAEsKz9AADfkgAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAQAN+SAAAAAAAAAAAP1qe44j+i4uIT+arbD4QDQBt8ryEeJd7a0jskQ3nwDeAAHp6WMr55YACD1BAAAAHgAAAAoAAAAAAAAAAAAAAAABAAAAAAAACgAAAAARC07BokpLTOF+/vVKBwiAlop7hHGJTNeGGlY4MoPykwAAAAEAAAAAK+Lzfd3yDD+Ov0GbYu1g7SaIBrKZeBUxoCunkLuI7aoAAAABAAAAAERmsKL73CyLV/HvjyQCERDXXpWE70Xhyb6MR5qPO3yQAAAAAQAAAABSORGwAdyuanN3sNOHqNSpACyYdkUM3L8VafUu69EvEgAAAAEAAAAAeCzqJNkMM/jLvyuMIfyFHljBlLCtDyj17RMycPuNtRMAAAABAAAAAIEi4R7juq15ymL00DNlAddunyFT4FyUD4muC4t3bobdAAAAAQAAAACaNpLL5YMfjOTdXVEqrAh99LM12sN6He6pHgCRAa1f1QAAAAEAAAAAqB+lfAPV9ak+Zkv4aTNZwGaFFAfui4+yhM3dGhoYJ+sAAAABAAAAAMNJrEvdMg6M+M+n4BDIdzsVSj/ZI9SvAp7mOOsvAD/WAAAAAQAAAADbHA6xiKB1+G79mVqpsHMOleOqKa5mxDpP5KEp/Xdz9wAAAAEAAAAAAAAAAA==\",\n" + + " \"memo_type\": \"none\",\n" + + " \"signatures\": [\n" + + " \"b/noKPYnxb8oJmv6gLixY0PUJMZZ9pxwc226JtAfyRkhv6oFINj3iDuGJoBeuUh6D1vujP9e4/fH0xZjDaO3Aw==\"\n" + + " ],\n" + + " \"preconditions\": {\n" + + " \"timebounds\": {\n" + + " \"min_time\": \"4\",\n" + + " \"max_time\": \"5\"\n" + + " },\n" + + " \"ledgerbounds\": {\n" + + " \"min_ledger\": 6,\n" + + " \"max_ledger\": 7\n" + + " },\n" + + " \"min_account_sequence_age\": \"2\",\n" + + " \"min_account_sequence_ledger_gap\": 3,\n" + + " \"extra_signers\": [\n]\n" + + " }\n" + + "}"; + String jsonFeeBump = "{\n" + " \"_links\": {\n" + " \"self\": {\n" +