diff --git a/backend-modules/blockfrost/src/main/java/com/bloxbean/cardano/client/backend/blockfrost/service/BFAccountService.java b/backend-modules/blockfrost/src/main/java/com/bloxbean/cardano/client/backend/blockfrost/service/BFAccountService.java index c8efeed4..81e2c4c7 100644 --- a/backend-modules/blockfrost/src/main/java/com/bloxbean/cardano/client/backend/blockfrost/service/BFAccountService.java +++ b/backend-modules/blockfrost/src/main/java/com/bloxbean/cardano/client/backend/blockfrost/service/BFAccountService.java @@ -4,6 +4,7 @@ import com.bloxbean.cardano.client.api.exception.ApiException; import com.bloxbean.cardano.client.api.model.Result; import com.bloxbean.cardano.client.backend.api.AccountService; +import com.bloxbean.cardano.client.backend.api.AddressService; import com.bloxbean.cardano.client.backend.blockfrost.service.http.AccountApi; import com.bloxbean.cardano.client.backend.model.*; import retrofit2.Call; @@ -16,10 +17,12 @@ public class BFAccountService extends BFBaseService implements AccountService { private final AccountApi accountApi; + private final AddressService addressService; - public BFAccountService(String baseUrl, String projectId) { + public BFAccountService(String baseUrl, String projectId, AddressService addressService) { super(baseUrl, projectId); this.accountApi = getRetrofit().create(AccountApi.class); + this.addressService = addressService; } @Override @@ -138,4 +141,56 @@ public Result> getAccountAssets(String stakeAddress, int coun throw new ApiException("Error getting accountInformation", e); } } + + @Override + public Result> getAccountTransactions(String stakeAddress, int count, int page, OrderEnum order, Integer fromBlockHeight, Integer toBlockHeight) throws ApiException { + Result> accountAddressesResult = getAllAccountAddresses(stakeAddress); + if (accountAddressesResult.isSuccessful()) { + List transactionContents = new ArrayList<>(); + List accountAddresses = accountAddressesResult.getValue(); + try { + for (AccountAddress accountAddress : accountAddresses) { + Result> listResult = addressService.getTransactions(accountAddress.getAddress(), 100, page, order, fromBlockHeight.toString(), toBlockHeight.toString()); + if (listResult.isSuccessful()) { + transactionContents.addAll(listResult.getValue()); + } + } + } catch (ApiException e) { + throw new RuntimeException(e); + } + transactionContents.sort((o1, o2) -> + order == OrderEnum.asc ? + Long.compare(o1.getBlockHeight(), o2.getBlockHeight()) : + Long.compare(o2.getBlockHeight(), o1.getBlockHeight())); + return Result.success("SUCCESS").withValue(transactionContents).code(200); + } else { + return Result.error(accountAddressesResult.getResponse()).code(accountAddressesResult.code()); + } + } + + @Override + public Result> getAllAccountTransactions(String stakeAddress, OrderEnum order, Integer fromBlockHeight, Integer toBlockHeight) throws ApiException { + Result> accountAddressesResult = getAllAccountAddresses(stakeAddress); + if (accountAddressesResult.isSuccessful()) { + List transactionContents = new ArrayList<>(); + List accountAddresses = accountAddressesResult.getValue(); + try { + for (AccountAddress accountAddress : accountAddresses) { + Result> listResult = addressService.getAllTransactions(accountAddress.getAddress(), order, fromBlockHeight, toBlockHeight); + if (listResult.isSuccessful()) { + transactionContents.addAll(listResult.getValue()); + } + } + } catch (ApiException e) { + throw new RuntimeException(e); + } + transactionContents.sort((o1, o2) -> + order == OrderEnum.asc ? + Long.compare(o1.getBlockHeight(), o2.getBlockHeight()) : + Long.compare(o2.getBlockHeight(), o1.getBlockHeight())); + return Result.success("SUCCESS").withValue(transactionContents).code(200); + } else { + return Result.error(accountAddressesResult.getResponse()).code(accountAddressesResult.code()); + } + } } diff --git a/backend-modules/blockfrost/src/main/java/com/bloxbean/cardano/client/backend/blockfrost/service/BFAddressService.java b/backend-modules/blockfrost/src/main/java/com/bloxbean/cardano/client/backend/blockfrost/service/BFAddressService.java index d056558a..20cc54bd 100644 --- a/backend-modules/blockfrost/src/main/java/com/bloxbean/cardano/client/backend/blockfrost/service/BFAddressService.java +++ b/backend-modules/blockfrost/src/main/java/com/bloxbean/cardano/client/backend/blockfrost/service/BFAddressService.java @@ -1,17 +1,18 @@ package com.bloxbean.cardano.client.backend.blockfrost.service; -import com.bloxbean.cardano.client.backend.api.AddressService; import com.bloxbean.cardano.client.api.common.OrderEnum; import com.bloxbean.cardano.client.api.exception.ApiException; +import com.bloxbean.cardano.client.api.model.Result; +import com.bloxbean.cardano.client.backend.api.AddressService; import com.bloxbean.cardano.client.backend.blockfrost.service.http.AddressesApi; import com.bloxbean.cardano.client.backend.model.AddressContent; import com.bloxbean.cardano.client.backend.model.AddressDetails; import com.bloxbean.cardano.client.backend.model.AddressTransactionContent; -import com.bloxbean.cardano.client.api.model.Result; import retrofit2.Call; import retrofit2.Response; import java.io.IOException; +import java.util.ArrayList; import java.util.List; public class BFAddressService extends BFBaseService implements AddressService { @@ -68,4 +69,28 @@ public Result> getTransactions(String address, i throw new ApiException("Error getting transactions for the address", e); } } + + public Result> getAllTransactions(String address, OrderEnum order, Integer fromBlockHeight, Integer toBlockHeight) throws ApiException { + List addressTransactionContents = new ArrayList<>(); + int page = 1; + Result> addressTransactionContentsResult = getTransactions(address, 100, page, order, String.valueOf(fromBlockHeight), String.valueOf(toBlockHeight)); + while (addressTransactionContentsResult.isSuccessful()) { + addressTransactionContents.addAll(addressTransactionContentsResult.getValue()); + if (addressTransactionContentsResult.getValue().size() != 100) { + break; + } else { + page++; + addressTransactionContentsResult = getTransactions(address, 100, page, order, String.valueOf(fromBlockHeight), String.valueOf(toBlockHeight)); + } + } + if (!addressTransactionContentsResult.isSuccessful()) { + return addressTransactionContentsResult; + } else { + addressTransactionContents.sort((o1, o2) -> + order == OrderEnum.asc ? + Long.compare(o1.getBlockHeight(), o2.getBlockHeight()) : + Long.compare(o2.getBlockHeight(), o1.getBlockHeight())); + return Result.success(addressTransactionContentsResult.toString()).withValue(addressTransactionContents).code(addressTransactionContentsResult.code()); + } + } } diff --git a/backend-modules/blockfrost/src/main/java/com/bloxbean/cardano/client/backend/blockfrost/service/BFBackendService.java b/backend-modules/blockfrost/src/main/java/com/bloxbean/cardano/client/backend/blockfrost/service/BFBackendService.java index 626a94a8..f6ffa4f1 100644 --- a/backend-modules/blockfrost/src/main/java/com/bloxbean/cardano/client/backend/blockfrost/service/BFBackendService.java +++ b/backend-modules/blockfrost/src/main/java/com/bloxbean/cardano/client/backend/blockfrost/service/BFBackendService.java @@ -45,7 +45,7 @@ public AddressService getAddressService() { @Override public AccountService getAccountService() { - return new BFAccountService(getBaseUrl(), getProjectId()); + return new BFAccountService(getBaseUrl(), getProjectId(), getAddressService()); } @Override diff --git a/backend-modules/koios/src/it/java/com/bloxbean/cardano/client/backend/koios/it/KoiosAccountServiceIT.java b/backend-modules/koios/src/it/java/com/bloxbean/cardano/client/backend/koios/it/KoiosAccountServiceIT.java index a0bab617..a3b95aee 100644 --- a/backend-modules/koios/src/it/java/com/bloxbean/cardano/client/backend/koios/it/KoiosAccountServiceIT.java +++ b/backend-modules/koios/src/it/java/com/bloxbean/cardano/client/backend/koios/it/KoiosAccountServiceIT.java @@ -1,5 +1,6 @@ package com.bloxbean.cardano.client.backend.koios.it; +import com.bloxbean.cardano.client.api.common.OrderEnum; import com.bloxbean.cardano.client.api.exception.ApiException; import com.bloxbean.cardano.client.api.model.Result; import com.bloxbean.cardano.client.backend.api.AccountService; @@ -10,6 +11,7 @@ import java.util.List; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; class KoiosAccountServiceIT extends KoiosBaseTest { @@ -83,4 +85,15 @@ void testGetAllAccountAssets() throws ApiException { System.out.println(JsonUtil.getPrettyJson(accountAssets)); //TODO Find account with more than 1000 assets } + + @Test + void testGetAllAccountTransactions() throws ApiException { + String stakeAddress = "stake_test1upxeg0r67z4wca682l28ghg69jxaxgswdmpvnher7at697qmhymyp"; + Result> result = accountService.getAllAccountTransactions(stakeAddress, OrderEnum.asc, 605746, 615700); + assertTrue(result.isSuccessful()); + List addressTransactionContents = result.getValue(); + assertEquals(22, addressTransactionContents.size()); + assertEquals("bef3e28ad884c3e50d40465726da389c4c288d486a47bc700c5d273d0516ea01", addressTransactionContents.get(0).getTxHash()); + System.out.println(JsonUtil.getPrettyJson(addressTransactionContents)); + } } diff --git a/backend-modules/koios/src/it/java/com/bloxbean/cardano/client/backend/koios/it/KoiosAddressServiceIT.java b/backend-modules/koios/src/it/java/com/bloxbean/cardano/client/backend/koios/it/KoiosAddressServiceIT.java index 15af6c14..5df3ff6d 100644 --- a/backend-modules/koios/src/it/java/com/bloxbean/cardano/client/backend/koios/it/KoiosAddressServiceIT.java +++ b/backend-modules/koios/src/it/java/com/bloxbean/cardano/client/backend/koios/it/KoiosAddressServiceIT.java @@ -78,4 +78,16 @@ public void testGetTransactionsWithOrder_whenFromAndToBlocksProvided() throws Ap assertTrue(txns.get(0).getBlockHeight() != 0); assertTrue(txns.get(0).getBlockTime() != 0); } + + @Test + public void testGetAllTransactions() throws ApiException { + String address = "addr_test1qzx9hu8j4ah3auytk0mwcupd69hpc52t0cw39a65ndrah86djs784u92a3m5w475w3w35tyd6v3qumkze80j8a6h5tuqq5xe8y"; + List txns = addressService.getAllTransactions(address, OrderEnum.desc, 357475, 357479).getValue(); + + System.out.println(txns); + assertThat(txns.size()).isEqualTo(3); + assertEquals("002dbdb2d294a61c03ec7b0876bc5d40ec3ae07ef5b72d08c107cce7566c4f96", txns.get(0).getTxHash()); + assertTrue(txns.get(0).getBlockHeight() != 0); + assertTrue(txns.get(0).getBlockTime() != 0); + } } diff --git a/backend-modules/koios/src/main/java/com/bloxbean/cardano/client/backend/koios/KoiosAccountService.java b/backend-modules/koios/src/main/java/com/bloxbean/cardano/client/backend/koios/KoiosAccountService.java index 5bf8a67c..fe1857db 100644 --- a/backend-modules/koios/src/main/java/com/bloxbean/cardano/client/backend/koios/KoiosAccountService.java +++ b/backend-modules/koios/src/main/java/com/bloxbean/cardano/client/backend/koios/KoiosAccountService.java @@ -3,15 +3,15 @@ import com.bloxbean.cardano.client.api.common.OrderEnum; import com.bloxbean.cardano.client.api.exception.ApiException; import com.bloxbean.cardano.client.api.model.Result; +import com.bloxbean.cardano.client.backend.model.AccountAddress; +import com.bloxbean.cardano.client.backend.model.AccountAsset; +import com.bloxbean.cardano.client.backend.model.AccountHistory; import com.bloxbean.cardano.client.backend.model.*; import rest.koios.client.backend.api.account.AccountService; -import rest.koios.client.backend.api.account.model.AccountHistoryInner; -import rest.koios.client.backend.api.account.model.AccountInfo; -import rest.koios.client.backend.api.account.model.AccountReward; -import rest.koios.client.backend.api.account.model.AccountRewards; -import rest.koios.client.backend.factory.options.Limit; -import rest.koios.client.backend.factory.options.Offset; -import rest.koios.client.backend.factory.options.Options; +import rest.koios.client.backend.api.account.model.*; +import rest.koios.client.backend.factory.options.*; +import rest.koios.client.backend.factory.options.filters.Filter; +import rest.koios.client.backend.factory.options.filters.FilterType; import java.util.ArrayList; import java.util.Collections; @@ -253,6 +253,73 @@ public Result> getAccountAssets(String stakeAddress, int coun } } + @Override + public Result> getAccountTransactions(String stakeAddress, int count, int page, OrderEnum order, Integer fromBlockHeight, Integer toBlockHeight) throws ApiException { + try { + Options options = Options.builder() + .option(Limit.of(count)) + .option(Offset.of((long) (page - 1) * count)) + .build(); + if (order != null) { + options.getOptionList().add(Order.by("block_height", order == OrderEnum.asc ? SortType.ASC : SortType.DESC)); + } + if (toBlockHeight != null) { + options.getOptionList().add(Filter.of("block_height", FilterType.LTE, toBlockHeight.toString())); + } + rest.koios.client.backend.api.base.Result> accountTxsResult = + accountService.getAccountTxs(stakeAddress, fromBlockHeight, options); + if (!accountTxsResult.isSuccessful()) { + return Result.error(accountTxsResult.getResponse()).code(accountTxsResult.getCode()); + } + if (accountTxsResult.getValue().isEmpty()) { + return Result.error("Not Found").code(404); + } + return convertToAddressTransactionContent(accountTxsResult.getValue(), order); + } catch (rest.koios.client.backend.api.base.exception.ApiException e) { + throw new ApiException(e.getMessage(), e); + } + } + + @Override + public Result> getAllAccountTransactions(String stakeAddress, OrderEnum order, Integer fromBlockHeight, Integer toBlockHeight) throws ApiException { + List addressTransactionContents = new ArrayList<>(); + int page = 1; + Result> addressTransactionsResult = getAccountTransactions(stakeAddress, 1000, page, order, fromBlockHeight, toBlockHeight); + while (addressTransactionsResult.isSuccessful()) { + addressTransactionContents.addAll(addressTransactionsResult.getValue()); + if (addressTransactionsResult.getValue().size() != 1000) { + break; + } else { + page++; + addressTransactionsResult = getAccountTransactions(stakeAddress, 1000, page, order, fromBlockHeight, toBlockHeight); + } + } + if (!addressTransactionsResult.isSuccessful()) { + return addressTransactionsResult; + } else { + return Result.success(addressTransactionsResult.toString()).withValue(addressTransactionContents).code(addressTransactionsResult.code()); + } + } + + private Result> convertToAddressTransactionContent(List accountTxs, OrderEnum order) { + List transactionContents = new ArrayList<>(); + if (accountTxs != null) { + accountTxs.forEach(accountTx -> { + AddressTransactionContent transactionContent = new AddressTransactionContent(); + transactionContent.setTxHash(accountTx.getTxHash()); + transactionContent.setBlockHeight(accountTx.getBlockHeight()); + transactionContent.setBlockTime(accountTx.getBlockTime()); + transactionContents.add(transactionContent); + }); + } + Comparator comparator = Comparator.comparing(AddressTransactionContent::getBlockHeight); + if (order != OrderEnum.asc) { + comparator = comparator.reversed(); + } + transactionContents.sort(comparator); + return Result.success("OK").withValue(transactionContents).code(200); + } + private Result> convertToAccountAssets(List accountAssetList) { List accountAssets = new ArrayList<>(); if (accountAssetList != null) { diff --git a/backend-modules/koios/src/main/java/com/bloxbean/cardano/client/backend/koios/KoiosAddressService.java b/backend-modules/koios/src/main/java/com/bloxbean/cardano/client/backend/koios/KoiosAddressService.java index ba643807..88318370 100644 --- a/backend-modules/koios/src/main/java/com/bloxbean/cardano/client/backend/koios/KoiosAddressService.java +++ b/backend-modules/koios/src/main/java/com/bloxbean/cardano/client/backend/koios/KoiosAddressService.java @@ -123,6 +123,31 @@ public Result> getTransactions(String address, i } } + @Override + public Result> getAllTransactions(String address, OrderEnum order, Integer fromBlockHeight, Integer toBlockHeight) throws ApiException { + List addressTransactionContents = new ArrayList<>(); + int page = 1; + Result> addressTransactionsResult = getTransactions(address, 1000, page, order, fromBlockHeight.toString(), toBlockHeight.toString()); + while (addressTransactionsResult.isSuccessful()) { + addressTransactionContents.addAll(addressTransactionsResult.getValue()); + if (addressTransactionsResult.getValue().size() != 1000) { + break; + } else { + page++; + addressTransactionsResult = getTransactions(address, 1000, page, order, fromBlockHeight.toString(), toBlockHeight.toString()); + } + } + if (!addressTransactionsResult.isSuccessful()) { + return addressTransactionsResult; + } else { + addressTransactionContents.sort((o1, o2) -> + order == OrderEnum.asc ? + Long.compare(o1.getBlockHeight(), o2.getBlockHeight()) : + Long.compare(o2.getBlockHeight(), o1.getBlockHeight())); + return Result.success(addressTransactionsResult.toString()).withValue(addressTransactionContents).code(addressTransactionsResult.code()); + } + } + private Result> convertToAddressTransactionContent(List txHashes) throws ParseException { List addressTransactionContents = new ArrayList<>(); for (TxHash txHash : txHashes) { diff --git a/backend/src/main/java/com/bloxbean/cardano/client/backend/api/AccountService.java b/backend/src/main/java/com/bloxbean/cardano/client/backend/api/AccountService.java index 530ebb4b..20b9731a 100644 --- a/backend/src/main/java/com/bloxbean/cardano/client/backend/api/AccountService.java +++ b/backend/src/main/java/com/bloxbean/cardano/client/backend/api/AccountService.java @@ -120,4 +120,25 @@ public interface AccountService { * @return List of Used Addresses */ Result> getAccountAssets(String stakeAddress, int count, int page, OrderEnum order) throws ApiException; + + /** + * Obtain information about transactions associated with a specific account. + * @param stakeAddress Bech32 stake address. + * @param count count + * @param page page + * @param order The ordering of items from the point of view of the blockchain, not the page listing itself. By default, we return oldest first, newest last. + * @param fromBlockHeight from block number + * @param toBlockHeight to block number + * @return List of {@link TransactionContent} + */ + Result> getAccountTransactions(String stakeAddress, int count, int page, OrderEnum order, Integer fromBlockHeight, Integer toBlockHeight) throws ApiException; + + /** + * Obtain All information about transactions associated with a specific account. + * @param stakeAddress Bech32 stake address. + * @param order The ordering of items from the point of view of the blockchain, not the page listing itself. By default, we return oldest first, newest last. + * @param fromBlockHeight from block number + * @return List of {@link TransactionContent} + */ + Result> getAllAccountTransactions(String stakeAddress, OrderEnum order, Integer fromBlockHeight, Integer toBlockHeight) throws ApiException; } diff --git a/backend/src/main/java/com/bloxbean/cardano/client/backend/api/AddressService.java b/backend/src/main/java/com/bloxbean/cardano/client/backend/api/AddressService.java index b1dd7447..50302fe3 100644 --- a/backend/src/main/java/com/bloxbean/cardano/client/backend/api/AddressService.java +++ b/backend/src/main/java/com/bloxbean/cardano/client/backend/api/AddressService.java @@ -70,4 +70,14 @@ public interface AddressService { default Result> getTransactions(String address, int count, int page, OrderEnum order, String from, String to) throws ApiException { return getTransactions(address, count, page, order); } + + /** + * getAllTransactions + * @param address paymentAddress + * @param order order + * @param fromBlockHeight from block number + * @param toBlockHeight to block number + * @return List of {@link AddressTransactionContent} + */ + Result> getAllTransactions(String address, OrderEnum order, Integer fromBlockHeight, Integer toBlockHeight) throws ApiException; } diff --git a/integration-test/src/it/java/com/bloxbean/cardano/client/backend/api/AccountServiceIT.java b/integration-test/src/it/java/com/bloxbean/cardano/client/backend/api/AccountServiceIT.java index 94f2dfde..bdefdab0 100644 --- a/integration-test/src/it/java/com/bloxbean/cardano/client/backend/api/AccountServiceIT.java +++ b/integration-test/src/it/java/com/bloxbean/cardano/client/backend/api/AccountServiceIT.java @@ -1,5 +1,6 @@ package com.bloxbean.cardano.client.backend.api; +import com.bloxbean.cardano.client.api.common.OrderEnum; import com.bloxbean.cardano.client.api.exception.ApiException; import com.bloxbean.cardano.client.api.model.Result; import com.bloxbean.cardano.client.backend.model.*; @@ -9,6 +10,7 @@ import java.util.List; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; class AccountServiceIT extends BaseITTest { @@ -84,5 +86,16 @@ void testGetAllAccountAssets() throws ApiException { System.out.println(JsonUtil.getPrettyJson(accountAssets)); //TODO Find account with more than 100 assets } + + @Test + void testGetAllAccountTransactions() throws ApiException { + String stakeAddress = "stake_test1upxeg0r67z4wca682l28ghg69jxaxgswdmpvnher7at697qmhymyp"; + Result> result = accountService.getAllAccountTransactions(stakeAddress, OrderEnum.asc, 605746, 615700); + assertTrue(result.isSuccessful()); + List addressTransactionContents = result.getValue(); + assertEquals(22, addressTransactionContents.size()); + assertEquals("bef3e28ad884c3e50d40465726da389c4c288d486a47bc700c5d273d0516ea01", addressTransactionContents.get(0).getTxHash()); + System.out.println(JsonUtil.getPrettyJson(addressTransactionContents)); + } } diff --git a/integration-test/src/it/java/com/bloxbean/cardano/client/backend/api/AddressServiceIT.java b/integration-test/src/it/java/com/bloxbean/cardano/client/backend/api/AddressServiceIT.java index e32ff035..e983e974 100644 --- a/integration-test/src/it/java/com/bloxbean/cardano/client/backend/api/AddressServiceIT.java +++ b/integration-test/src/it/java/com/bloxbean/cardano/client/backend/api/AddressServiceIT.java @@ -69,5 +69,17 @@ public void testGetTransactionsWithOrder_whenFromAndToBlocksProvided() throws Ap assertTrue(txns.get(0).getBlockHeight() != 0); assertTrue(txns.get(0).getBlockTime() != 0); } + + @Test + public void testGetAllTransactions() throws ApiException { + String address = "addr_test1qzx9hu8j4ah3auytk0mwcupd69hpc52t0cw39a65ndrah86djs784u92a3m5w475w3w35tyd6v3qumkze80j8a6h5tuqq5xe8y"; + List txns = addressService.getAllTransactions(address, OrderEnum.desc, 357475, 357479).getValue(); + + System.out.println(txns); + assertThat(txns.size()).isEqualTo(3); + assertEquals("002dbdb2d294a61c03ec7b0876bc5d40ec3ae07ef5b72d08c107cce7566c4f96", txns.get(0).getTxHash()); + assertTrue(txns.get(0).getBlockHeight() != 0); + assertTrue(txns.get(0).getBlockTime() != 0); + } }