diff --git a/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/marketdata/BinanceKline.java b/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/marketdata/BinanceKline.java index ef1bcd451f2..bf503b809f6 100644 --- a/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/marketdata/BinanceKline.java +++ b/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/marketdata/BinanceKline.java @@ -38,7 +38,7 @@ public BinanceKline(Instrument instrument, KlineInterval interval, Object[] obj) this.numberOfTrades = Long.parseLong(obj[8].toString()); this.takerBuyBaseAssetVolume = new BigDecimal(obj[9].toString()); this.takerBuyQuoteAssetVolume = new BigDecimal(obj[10].toString()); - this.closed = (Boolean) obj[11]; + this.closed = Boolean.parseBoolean(obj[11].toString()); } public BigDecimal getAveragePrice() { diff --git a/xchange-coinmate/src/main/java/org/knowm/xchange/coinmate/CoinmateAuthenticated.java b/xchange-coinmate/src/main/java/org/knowm/xchange/coinmate/CoinmateAuthenticated.java index e494e772c57..7c0342114ce 100644 --- a/xchange-coinmate/src/main/java/org/knowm/xchange/coinmate/CoinmateAuthenticated.java +++ b/xchange-coinmate/src/main/java/org/knowm/xchange/coinmate/CoinmateAuthenticated.java @@ -31,11 +31,27 @@ import jakarta.ws.rs.core.MediaType; import java.io.IOException; import java.math.BigDecimal; +import org.knowm.xchange.coinmate.dto.account.AmountType; import org.knowm.xchange.coinmate.dto.account.CoinmateBalance; import org.knowm.xchange.coinmate.dto.account.CoinmateDepositAddresses; import org.knowm.xchange.coinmate.dto.account.CoinmateTradingFeesResponse; import org.knowm.xchange.coinmate.dto.account.FeePriority; -import org.knowm.xchange.coinmate.dto.trade.*; +import org.knowm.xchange.coinmate.dto.account.TransferHistoryOrder; +import org.knowm.xchange.coinmate.dto.account.UnconfirmedDepositsResponse; +import org.knowm.xchange.coinmate.dto.trade.CoinmateBuyFixRateResponse; +import org.knowm.xchange.coinmate.dto.trade.CoinmateCancelOrderResponse; +import org.knowm.xchange.coinmate.dto.trade.CoinmateCancelOrderWithInfoResponse; +import org.knowm.xchange.coinmate.dto.trade.CoinmateOpenOrders; +import org.knowm.xchange.coinmate.dto.trade.CoinmateOrder; +import org.knowm.xchange.coinmate.dto.trade.CoinmateOrderHistory; +import org.knowm.xchange.coinmate.dto.trade.CoinmateOrders; +import org.knowm.xchange.coinmate.dto.trade.CoinmateReplaceResponse; +import org.knowm.xchange.coinmate.dto.trade.CoinmateSellFixRateResponse; +import org.knowm.xchange.coinmate.dto.trade.CoinmateTradeHistory; +import org.knowm.xchange.coinmate.dto.trade.CoinmateTradeResponse; +import org.knowm.xchange.coinmate.dto.trade.CoinmateTransactionHistory; +import org.knowm.xchange.coinmate.dto.trade.CoinmateTransferDetail; +import org.knowm.xchange.coinmate.dto.trade.CoinmateTransferHistory; import si.mazi.rescu.ParamsDigest; import si.mazi.rescu.SynchronizedValueFactory; @@ -266,6 +282,42 @@ CoinmateTradeResponse sellQuickFix( // withdrawal and deposits // bitcoin + + @POST + @Path("withdrawVirtualCurrency") + CoinmateTradeResponse withdrawVirtualCurrency( + @FormParam("publicKey") String publicKey, + @FormParam("clientId") String clientId, + @FormParam("signature") ParamsDigest signer, + @FormParam("nonce") SynchronizedValueFactory nonce, + @FormParam("currencyName") String currencyName, + @FormParam("amount") BigDecimal amount, + @FormParam("destinationTag") String destinationTag, + @FormParam("amountType") AmountType amountType, + @FormParam("address") String address, + @FormParam("feePriority") FeePriority feePriority) + throws IOException; + + @POST + @Path("virtualCurrencyDepositAddresses") + CoinmateDepositAddresses virtualCurrencyDepositAddresses( + @FormParam("publicKey") String publicKey, + @FormParam("clientId") String clientId, + @FormParam("signature") ParamsDigest signer, + @FormParam("nonce") SynchronizedValueFactory nonce, + @FormParam("currencyName") String currencyName) + throws IOException; + + @POST + @Path("unconfirmedVirtualCurrencyDeposits") + UnconfirmedDepositsResponse unconfirmedVirtualCurrencyDeposits( + @FormParam("publicKey") String publicKey, + @FormParam("clientId") String clientId, + @FormParam("signature") ParamsDigest signer, + @FormParam("nonce") SynchronizedValueFactory nonce, + @FormParam("currencyName") String currencyName) + throws IOException; + @POST @Path("bitcoinWithdrawal") CoinmateTradeResponse bitcoinWithdrawal( @@ -352,28 +404,6 @@ CoinmateDepositAddresses rippleDepositAddresses( @FormParam("nonce") SynchronizedValueFactory nonce) throws IOException; - // dash - - @POST - @Path("dashWithdrawal") - CoinmateTradeResponse dashWithdrawal( - @FormParam("publicKey") String publicKey, - @FormParam("clientId") String clientId, - @FormParam("signature") ParamsDigest signer, - @FormParam("nonce") SynchronizedValueFactory nonce, - @FormParam("amount") BigDecimal amount, - @FormParam("address") String address) - throws IOException; - - @POST - @Path("dashDepositAddresses") - CoinmateDepositAddresses dashDepositAddresses( - @FormParam("publicKey") String publicKey, - @FormParam("clientId") String clientId, - @FormParam("signature") ParamsDigest signer, - @FormParam("nonce") SynchronizedValueFactory nonce) - throws IOException; - @POST @Path("adaWithdrawal") CoinmateTradeResponse adaWithdrawal( @@ -452,7 +482,7 @@ CoinmateTransferHistory getTransferHistory( @FormParam("nonce") SynchronizedValueFactory nonce, @FormParam("limit") Integer limit, @FormParam("lastId") Integer lastId, - @FormParam("sort") String sort, + @FormParam("sort") TransferHistoryOrder sort, @FormParam("timestampFrom") Long timestampFrom, @FormParam("timestampTo") Long timestampTo, @FormParam("currency") String currency) diff --git a/xchange-coinmate/src/main/java/org/knowm/xchange/coinmate/dto/account/AmountType.java b/xchange-coinmate/src/main/java/org/knowm/xchange/coinmate/dto/account/AmountType.java new file mode 100644 index 00000000000..e5d8dbd2fba --- /dev/null +++ b/xchange-coinmate/src/main/java/org/knowm/xchange/coinmate/dto/account/AmountType.java @@ -0,0 +1,6 @@ +package org.knowm.xchange.coinmate.dto.account; + +public enum AmountType { + NET, + GROSS +} diff --git a/xchange-coinmate/src/main/java/org/knowm/xchange/coinmate/dto/account/TransferHistoryOrder.java b/xchange-coinmate/src/main/java/org/knowm/xchange/coinmate/dto/account/TransferHistoryOrder.java new file mode 100644 index 00000000000..914b4fc40d1 --- /dev/null +++ b/xchange-coinmate/src/main/java/org/knowm/xchange/coinmate/dto/account/TransferHistoryOrder.java @@ -0,0 +1,5 @@ +package org.knowm.xchange.coinmate.dto.account; + +public enum TransferHistoryOrder { + ASC, DESC +} diff --git a/xchange-coinmate/src/main/java/org/knowm/xchange/coinmate/dto/account/UnconfirmedDeposits.java b/xchange-coinmate/src/main/java/org/knowm/xchange/coinmate/dto/account/UnconfirmedDeposits.java new file mode 100644 index 00000000000..b9ef8a98aff --- /dev/null +++ b/xchange-coinmate/src/main/java/org/knowm/xchange/coinmate/dto/account/UnconfirmedDeposits.java @@ -0,0 +1,41 @@ +package org.knowm.xchange.coinmate.dto.account; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.math.BigDecimal; + +public class UnconfirmedDeposits { + private final Long id; + + private final BigDecimal amount; + + private final String address; + + private final Long confirmations; + + public UnconfirmedDeposits( + @JsonProperty Long id, + @JsonProperty BigDecimal amount, + @JsonProperty String address, + @JsonProperty Long confirmations) { + this.id = id; + this.amount = amount; + this.address = address; + this.confirmations = confirmations; + } + + public Long getId() { + return id; + } + + public BigDecimal getAmount() { + return amount; + } + + public String getAddress() { + return address; + } + + public Long getConfirmations() { + return confirmations; + } +} diff --git a/xchange-coinmate/src/main/java/org/knowm/xchange/coinmate/dto/account/UnconfirmedDepositsResponse.java b/xchange-coinmate/src/main/java/org/knowm/xchange/coinmate/dto/account/UnconfirmedDepositsResponse.java new file mode 100644 index 00000000000..4f16605c40a --- /dev/null +++ b/xchange-coinmate/src/main/java/org/knowm/xchange/coinmate/dto/account/UnconfirmedDepositsResponse.java @@ -0,0 +1,16 @@ +package org.knowm.xchange.coinmate.dto.account; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.ArrayList; +import org.knowm.xchange.coinmate.dto.CoinmateBaseResponse; + +public class UnconfirmedDepositsResponse + extends CoinmateBaseResponse> { + public UnconfirmedDepositsResponse( + @JsonProperty("error") boolean error, + @JsonProperty("errorMessage") String errorMessage, + @JsonProperty("data") ArrayList data) { + + super(error, errorMessage, data); + } +} diff --git a/xchange-coinmate/src/main/java/org/knowm/xchange/coinmate/dto/trade/CoinmateTransferHistoryEntry.java b/xchange-coinmate/src/main/java/org/knowm/xchange/coinmate/dto/trade/CoinmateTransferHistoryEntry.java index 201c7bb7adb..f10d6b49655 100644 --- a/xchange-coinmate/src/main/java/org/knowm/xchange/coinmate/dto/trade/CoinmateTransferHistoryEntry.java +++ b/xchange-coinmate/src/main/java/org/knowm/xchange/coinmate/dto/trade/CoinmateTransferHistoryEntry.java @@ -14,6 +14,7 @@ public class CoinmateTransferHistoryEntry { private final String walletType; private final String destination; private final String destinationTag; + private final String txid; public CoinmateTransferHistoryEntry( @JsonProperty("transactionId") long id, @@ -25,8 +26,9 @@ public CoinmateTransferHistoryEntry( @JsonProperty("amountCurrency") String amountCurrency, @JsonProperty("walletType") String walletType, @JsonProperty("destination") String destination, - @JsonProperty("destinationTag") String destinationTag) { - + @JsonProperty("destinationTag") String destinationTag, + @JsonProperty("txid") String txid + ) { this.fee = fee; this.transferType = transferType; this.timestamp = timestamp; @@ -37,6 +39,7 @@ public CoinmateTransferHistoryEntry( this.walletType = walletType; this.destination = destination; this.destinationTag = destinationTag; + this.txid = txid; } public long getId() { @@ -78,4 +81,6 @@ public String getDestination() { public String getDestinationTag() { return destinationTag; } + + public String getTxid() { return txid; } } diff --git a/xchange-coinmate/src/main/java/org/knowm/xchange/coinmate/service/CoinmateAccountService.java b/xchange-coinmate/src/main/java/org/knowm/xchange/coinmate/service/CoinmateAccountService.java index 78e37dc1dee..e268cdb5a7d 100644 --- a/xchange-coinmate/src/main/java/org/knowm/xchange/coinmate/service/CoinmateAccountService.java +++ b/xchange-coinmate/src/main/java/org/knowm/xchange/coinmate/service/CoinmateAccountService.java @@ -34,14 +34,17 @@ import org.knowm.xchange.Exchange; import org.knowm.xchange.coinmate.CoinmateAdapters; import org.knowm.xchange.coinmate.CoinmateUtils; +import org.knowm.xchange.coinmate.dto.account.AmountType; import org.knowm.xchange.coinmate.dto.account.CoinmateDepositAddresses; import org.knowm.xchange.coinmate.dto.account.CoinmateTradingFeesResponseData; +import org.knowm.xchange.coinmate.dto.account.FeePriority; import org.knowm.xchange.coinmate.dto.trade.CoinmateTradeResponse; import org.knowm.xchange.coinmate.dto.trade.CoinmateTransactionHistory; import org.knowm.xchange.coinmate.dto.trade.CoinmateTransferDetail; import org.knowm.xchange.coinmate.dto.trade.CoinmateTransferHistory; import org.knowm.xchange.currency.Currency; import org.knowm.xchange.dto.account.AccountInfo; +import org.knowm.xchange.dto.account.AddressWithTag; import org.knowm.xchange.dto.account.Fee; import org.knowm.xchange.dto.account.FundingRecord; import org.knowm.xchange.instrument.Instrument; @@ -97,12 +100,13 @@ public String withdrawFunds(Currency currency, BigDecimal amount, String address response = coinmateEthereumWithdrawal(amount, address); } else if (currency.equals(Currency.XRP)) { response = coinmateRippleWithdrawal(amount, address); - } else if (currency.equals(Currency.DASH)) { - response = coinmateDashWithdrawal(amount, address); } else if (currency.equals(Currency.ADA)) { response = coinmateCardanoWithdrawal(amount, address); } else if (currency.equals(Currency.SOL)) { response = coinmateSolanaWithdrawal(amount, address); + } else if (currency.equals(Currency.USDT)) { + Long tradeId = coinmateWithdrawVirtualCurrency(amount, address, Currency.USDT.getCurrencyCode(), AmountType.GROSS, FeePriority.HIGH, null); + return Long.toString(tradeId); } else { throw new IOException( "Wallet for currency" + currency.getCurrencyCode() + " is currently not supported"); @@ -111,10 +115,27 @@ public String withdrawFunds(Currency currency, BigDecimal amount, String address return Long.toString(response.getData()); } + @Override + public String withdrawFunds(Currency currency, BigDecimal amount, AddressWithTag address) + throws IOException { + if (currency.equals(Currency.XRP)) { + Long tradeId = coinmateWithdrawVirtualCurrency(amount, address.getAddress(), currency.getCurrencyCode(), AmountType.GROSS, FeePriority.HIGH, address.getAddressTag()); + return Long.toString(tradeId); + } else { + return withdrawFunds(currency, amount, address.getAddress()); + } + } + @Override public String withdrawFunds(WithdrawFundsParams params) throws IOException { if (params instanceof DefaultWithdrawFundsParams) { DefaultWithdrawFundsParams defaultParams = (DefaultWithdrawFundsParams) params; + + if (defaultParams.getCurrency().equals(Currency.XRP)) { + Long tradeId = coinmateWithdrawVirtualCurrency(defaultParams.getAmount(), defaultParams.getAddress(), defaultParams.getCurrency().getCurrencyCode(), AmountType.GROSS, FeePriority.HIGH, defaultParams.getAddressTag()); + return Long.toString(tradeId); + } + return withdrawFunds( defaultParams.getCurrency(), defaultParams.getAmount(), defaultParams.getAddress()); } @@ -132,12 +153,16 @@ public String requestDepositAddress(Currency currency, String... args) throws IO addresses = coinmateEthereumDepositAddresses(); } else if (currency.equals(Currency.XRP)) { addresses = coinmateRippleDepositAddresses(); - } else if (currency.equals(Currency.DASH)) { - addresses = coinmateDashDepositAddresses(); } else if (currency.equals(Currency.ADA)) { addresses = coinmateCardanoDepositAddresses(); } else if (currency.equals(Currency.SOL)) { addresses = coinmateSolanaDepositAddresses(); + } else if (currency.equals(Currency.USDT)) { + List addressesAll = coinmateVirtualCurrencyDepositAddresses(currency.getCurrencyCode()); + if (addressesAll.isEmpty()) { + return null; + } + return addressesAll.get(0); } else { throw new IOException( "Wallet for currency" + currency.getCurrencyCode() + " is currently not supported"); diff --git a/xchange-coinmate/src/main/java/org/knowm/xchange/coinmate/service/CoinmateAccountServiceRaw.java b/xchange-coinmate/src/main/java/org/knowm/xchange/coinmate/service/CoinmateAccountServiceRaw.java index 5c2579ac18d..e89d6dd016f 100644 --- a/xchange-coinmate/src/main/java/org/knowm/xchange/coinmate/service/CoinmateAccountServiceRaw.java +++ b/xchange-coinmate/src/main/java/org/knowm/xchange/coinmate/service/CoinmateAccountServiceRaw.java @@ -25,14 +25,20 @@ import java.io.IOException; import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; import org.knowm.xchange.Exchange; import org.knowm.xchange.client.ExchangeRestProxyBuilder; import org.knowm.xchange.coinmate.CoinmateAuthenticated; +import org.knowm.xchange.coinmate.dto.account.AmountType; import org.knowm.xchange.coinmate.dto.account.CoinmateBalance; import org.knowm.xchange.coinmate.dto.account.CoinmateDepositAddresses; import org.knowm.xchange.coinmate.dto.account.CoinmateTradingFeesResponse; import org.knowm.xchange.coinmate.dto.account.CoinmateTradingFeesResponseData; import org.knowm.xchange.coinmate.dto.account.FeePriority; +import org.knowm.xchange.coinmate.dto.account.TransferHistoryOrder; +import org.knowm.xchange.coinmate.dto.account.UnconfirmedDeposits; +import org.knowm.xchange.coinmate.dto.account.UnconfirmedDepositsResponse; import org.knowm.xchange.coinmate.dto.trade.CoinmateTradeResponse; import org.knowm.xchange.coinmate.dto.trade.CoinmateTransactionHistory; import org.knowm.xchange.coinmate.dto.trade.CoinmateTransferDetail; @@ -89,6 +95,65 @@ public CoinmateTradingFeesResponseData getCoinmateTraderFees(String currencyPair return response.getData(); } + public Long coinmateWithdrawVirtualCurrency( + BigDecimal amount, + String address, + String currencyName, + AmountType amountType, + FeePriority feePriority, + String destinationTag + ) + throws IOException { + CoinmateTradeResponse response =coinmateAuthenticated.withdrawVirtualCurrency( + exchange.getExchangeSpecification().getApiKey(), + exchange.getExchangeSpecification().getUserName(), + signatureCreator, + exchange.getNonceFactory(), + currencyName, + amount, + destinationTag, + amountType, + address, + feePriority + ); + + throwExceptionIfError(response); + + return response.getData(); + } + + public ArrayList coinmateVirtualCurrencyDepositAddresses(String currencyName) + throws IOException { + CoinmateDepositAddresses response = + coinmateAuthenticated.virtualCurrencyDepositAddresses( + exchange.getExchangeSpecification().getApiKey(), + exchange.getExchangeSpecification().getUserName(), + signatureCreator, + exchange.getNonceFactory(), + currencyName + ); + + throwExceptionIfError(response); + + return response.getData(); + } + + public List coinmateUnconfirmedVirtualCurrencyDeposits(String currencyName) + throws IOException { + UnconfirmedDepositsResponse response = + coinmateAuthenticated.unconfirmedVirtualCurrencyDeposits( + exchange.getExchangeSpecification().getApiKey(), + exchange.getExchangeSpecification().getUserName(), + signatureCreator, + exchange.getNonceFactory(), + currencyName + ); + + throwExceptionIfError(response); + + return response.getData(); + } + public CoinmateTradeResponse coinmateBitcoinWithdrawal(BigDecimal amount, String address) throws IOException { return coinmateBitcoinWithdrawal(amount, address, FeePriority.HIGH); @@ -211,35 +276,6 @@ public CoinmateDepositAddresses coinmateRippleDepositAddresses() throws IOExcept return addresses; } - public CoinmateTradeResponse coinmateDashWithdrawal(BigDecimal amount, String address) - throws IOException { - CoinmateTradeResponse response = - coinmateAuthenticated.dashWithdrawal( - exchange.getExchangeSpecification().getApiKey(), - exchange.getExchangeSpecification().getUserName(), - signatureCreator, - exchange.getNonceFactory(), - amount, - address); - - throwExceptionIfError(response); - - return response; - } - - public CoinmateDepositAddresses coinmateDashDepositAddresses() throws IOException { - CoinmateDepositAddresses addresses = - coinmateAuthenticated.dashDepositAddresses( - exchange.getExchangeSpecification().getApiKey(), - exchange.getExchangeSpecification().getUserName(), - signatureCreator, - exchange.getNonceFactory()); - - throwExceptionIfError(addresses); - - return addresses; - } - public CoinmateTradeResponse coinmateCardanoWithdrawal(BigDecimal amount, String address) throws IOException { CoinmateTradeResponse response = @@ -327,7 +363,7 @@ public CoinmateTransferHistory getTransfersData( public CoinmateTransferHistory getCoinmateTransferHistory( Integer limit, Integer lastId, - String sort, + TransferHistoryOrder sort, Long timestampFrom, Long timestampTo, String currency) diff --git a/xchange-coinmate/src/main/resources/coinmate.json b/xchange-coinmate/src/main/resources/coinmate.json index cc880fea8c0..381f91a4735 100644 --- a/xchange-coinmate/src/main/resources/coinmate.json +++ b/xchange-coinmate/src/main/resources/coinmate.json @@ -1,164 +1,157 @@ { - "currency_pairs" : { - "DASH/BTC" : { - "price_scale" : 5, - "min_amount" : 0.001, - "base_scale" : 8 - }, - "DASH/CZK" : { - "price_scale" : 1, - "min_amount" : 0.001, - "base_scale" : 8 - }, - "ETH/DAI" : { - "price_scale" : 2, - "min_amount" : 0.001, - "base_scale" : 8 - }, - "XRP/EUR" : { - "price_scale" : 5, - "min_amount" : 1, - "base_scale" : 8 - }, - "BCH/EUR" : { - "price_scale" : 2, - "min_amount" : 0.001, - "base_scale" : 8 - }, - "ETH/BTC" : { - "price_scale" : 5, - "min_amount" : 0.001, - "base_scale" : 8 - }, - "LTC/BTC" : { - "price_scale" : 6, - "min_amount" : 0.01, - "base_scale" : 8 - }, - "ADA/CZK" : { - "price_scale" : 4, - "min_amount" : 1, - "base_scale" : 6 - }, - "ETH/CZK" : { - "price_scale" : 1, - "min_amount" : 0.001, - "base_scale" : 8 - }, - "SOL/CZK" : { - "price_scale" : 4, - "min_amount" : 0.01, - "base_scale" : 8 - }, - "BTC/CZK" : { - "price_scale" : 0, - "min_amount" : 0.0001, - "base_scale" : 8 - }, - "BCH/CZK" : { - "price_scale" : 1, - "min_amount" : 0.001, - "base_scale" : 8 - }, - "DASH/EUR" : { - "price_scale" : 2, - "min_amount" : 0.001, - "base_scale" : 8 - }, - "DAI/EUR" : { - "price_scale" : 4, - "min_amount" : 1, - "base_scale" : 2 - }, - "XRP/CZK" : { - "price_scale" : 4, - "min_amount" : 1, - "base_scale" : 8 - }, - "BCH/BTC" : { - "price_scale" : 5, - "min_amount" : 0.001, - "base_scale" : 8 - }, - "BTC/DAI" : { - "price_scale" : 2, - "min_amount" : 0.0001, - "base_scale" : 8 - }, - "LTC/CZK" : { - "price_scale" : 1, - "min_amount" : 0.01, - "base_scale" : 8 - }, - "XRP/BTC" : { - "price_scale" : 8, - "min_amount" : 1, - "base_scale" : 8 - }, - "LTC/EUR" : { - "price_scale" : 2, - "min_amount" : 0.01, - "base_scale" : 8 - }, - "ADA/EUR" : { - "price_scale" : 5, - "min_amount" : 1, - "base_scale" : 6 - }, - "SOL/EUR" : { - "price_scale" : 5, - "min_amount" : 0.01, - "base_scale" : 8 - }, - "BTC/EUR" : { - "price_scale" : 1, - "min_amount" : 0.0001, - "base_scale" : 8 - }, - "ETH/EUR" : { - "price_scale" : 2, - "min_amount" : 0.001, - "base_scale" : 8 + "currency_pairs": { + "XRP/EUR": { + "price_scale": 5, + "min_amount": 1, + "base_scale": 8 + }, + "BTC/USDT": { + "price_scale": 1, + "min_amount": 0.0001, + "base_scale": 8 + }, + "USDT/EUR": { + "price_scale": 4, + "min_amount": 1, + "base_scale": 2 + }, + "ETH/BTC": { + "price_scale": 5, + "min_amount": 0.001, + "base_scale": 8 + }, + "LTC/BTC": { + "price_scale": 6, + "min_amount": 0.01, + "base_scale": 8 + }, + "ADA/CZK": { + "price_scale": 4, + "min_amount": 1, + "base_scale": 6 + }, + "ETH/CZK": { + "price_scale": 1, + "min_amount": 0.001, + "base_scale": 8 + }, + "SOL/CZK": { + "price_scale": 4, + "min_amount": 0.01, + "base_scale": 8 + }, + "BTC/CZK": { + "price_scale": 0, + "min_amount": 0.0001, + "base_scale": 8 + }, + "XRP/CZK": { + "price_scale": 4, + "min_amount": 1, + "base_scale": 8 + }, + "LTC/CZK": { + "price_scale": 1, + "min_amount": 0.01, + "base_scale": 8 + }, + "XRP/BTC": { + "price_scale": 8, + "min_amount": 1, + "base_scale": 8 + }, + "ADA/EUR": { + "price_scale": 5, + "min_amount": 1, + "base_scale": 6 + }, + "LTC/EUR": { + "price_scale": 2, + "min_amount": 0.01, + "base_scale": 8 + }, + "SOL/EUR": { + "price_scale": 5, + "min_amount": 0.01, + "base_scale": 8 + }, + "USDT/CZK": { + "price_scale": 3, + "min_amount": 1, + "base_scale": 2 + }, + "BTC/EUR": { + "price_scale": 1, + "min_amount": 0.0001, + "base_scale": 8 + }, + "ETH/EUR": { + "price_scale": 2, + "min_amount": 0.001, + "base_scale": 8 } }, - "currencies" : { - "BTC" : { - "scale" : 8 + "currencies": { + "EUR": { + "scale": 2 }, - "SOL" : { - "scale" : 8 + "BCH": { + "scale": 8 }, - "EUR" : { - "scale" : 2 + "USD": { + "scale": 2 }, - "CZK" : { - "scale" : 2 + "USDT": { + "scale": 2 }, - "BCH" : { - "scale" : 8 + "DASH": { + "scale": 8 }, - "XRP" : { - "scale" : 8 + "DAI": { + "scale": 2 }, - "ETH" : { - "scale" : 8 + "BTC": { + "scale": 8 }, - "LTC" : { - "scale" : 8 + "SOL": { + "scale": 8 }, - "DASH" : { - "scale" : 8 + "PLN": { + "scale": 2 }, - "DAI" : { - "scale" : 2 + "GBP": { + "scale": 2 }, - "ADA" : { - "scale" : 6 + "CZK": { + "scale": 2 + }, + "XRP": { + "scale": 8 + }, + "ETH": { + "scale": 8 + }, + "HUF": { + "scale": 2 + }, + "LTC": { + "scale": 8 + }, + "TRX": { + "scale": 8 + }, + "ADA": { + "scale": 6 + }, + "LUNA": { + "scale": 8 } }, - "private_rate_limits" : [ { - "calls" : 100, - "time_unit" : "minutes" - } ], - "share_rate_limits" : true + "private_rate_limits": [ + { + "calls": 100, + "time_unit": "minutes" + } + ], + "share_rate_limits": true } \ No newline at end of file diff --git a/xchange-core/src/main/java/org/knowm/xchange/exceptions/DepositAddressAmbiguousException.java b/xchange-core/src/main/java/org/knowm/xchange/exceptions/DepositAddressAmbiguousException.java new file mode 100644 index 00000000000..663cfc28698 --- /dev/null +++ b/xchange-core/src/main/java/org/knowm/xchange/exceptions/DepositAddressAmbiguousException.java @@ -0,0 +1,25 @@ +package org.knowm.xchange.exceptions; + +import java.util.List; +import lombok.Getter; + +/** Exception indicating a requested deposit address has multiple networks and network required */ +@Getter +public class DepositAddressAmbiguousException extends ExchangeException { + private final List networks; + + public DepositAddressAmbiguousException(List networks) { + super("Deposit Address Not Found"); + this.networks = networks; + } + + public DepositAddressAmbiguousException(List networks, String message) { + super(message); + this.networks = networks; + } + + public DepositAddressAmbiguousException(List networks, String message, Throwable cause) { + super(message, cause); + this.networks = networks; + } +} diff --git a/xchange-core/src/main/java/org/knowm/xchange/exceptions/DepositAddressCreationException.java b/xchange-core/src/main/java/org/knowm/xchange/exceptions/DepositAddressCreationException.java new file mode 100644 index 00000000000..aaf34d8e2bb --- /dev/null +++ b/xchange-core/src/main/java/org/knowm/xchange/exceptions/DepositAddressCreationException.java @@ -0,0 +1,17 @@ +package org.knowm.xchange.exceptions; + +/** Exception indicating a deposit address could not be created */ +public class DepositAddressCreationException extends ExchangeException { + + public DepositAddressCreationException() { + super("Deposit Address Could Not Be Created"); + } + + public DepositAddressCreationException(String message) { + super(message); + } + + public DepositAddressCreationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/xchange-core/src/main/java/org/knowm/xchange/exceptions/DepositAddressNotFoundException.java b/xchange-core/src/main/java/org/knowm/xchange/exceptions/DepositAddressNotFoundException.java new file mode 100644 index 00000000000..2575deb6b8e --- /dev/null +++ b/xchange-core/src/main/java/org/knowm/xchange/exceptions/DepositAddressNotFoundException.java @@ -0,0 +1,17 @@ +package org.knowm.xchange.exceptions; + +/** Exception indicating a requested deposit address was not found */ +public class DepositAddressNotFoundException extends ExchangeException { + + public DepositAddressNotFoundException() { + super("Deposit Address Not Found"); + } + + public DepositAddressNotFoundException(String message) { + super(message); + } + + public DepositAddressNotFoundException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/xchange-core/src/main/java/org/knowm/xchange/service/account/AccountService.java b/xchange-core/src/main/java/org/knowm/xchange/service/account/AccountService.java index b10c6ef0ab7..5a3aa9c4bd4 100644 --- a/xchange-core/src/main/java/org/knowm/xchange/service/account/AccountService.java +++ b/xchange-core/src/main/java/org/knowm/xchange/service/account/AccountService.java @@ -15,6 +15,7 @@ import org.knowm.xchange.exceptions.NotYetImplementedForExchangeException; import org.knowm.xchange.instrument.Instrument; import org.knowm.xchange.service.BaseService; +import org.knowm.xchange.service.account.params.RequestDepositAddressParams; import org.knowm.xchange.service.trade.params.DefaultWithdrawFundsParams; import org.knowm.xchange.service.trade.params.TradeHistoryParams; import org.knowm.xchange.service.trade.params.WithdrawFundsParams; @@ -125,6 +126,24 @@ default String requestDepositAddress(Currency currency, String... args) throws I throw new NotYetImplementedForExchangeException("requestDepositAddress"); } + /** + * Request a digital currency address to fund this account. Allows to fund the exchange account + * with digital currency from an external address + * + * @param params The deposit address request parameters + * @return the internal deposit address to send funds to + * @throws ExchangeException - Indication that the exchange reported some kind of error with the + * request or response + * @throws NotAvailableFromExchangeException - Indication that the exchange does not support the + * requested function or data + * @throws NotYetImplementedForExchangeException - Indication that the exchange supports the + * requested function or data, but it has not yet been implemented + * @throws IOException - Indication that a networking error occurred while fetching JSON data + */ + default String requestDepositAddress(RequestDepositAddressParams params) throws IOException { + return requestDepositAddress(params.getCurrency(), params.getExtraArguments()); + } + /** * Request a digital currency address to fund this account. Allows to fund the exchange account * with digital currency from an external address @@ -144,6 +163,25 @@ default AddressWithTag requestDepositAddressData(Currency currency, String... ar throw new NotYetImplementedForExchangeException("requestDepositAddressData"); } + /** + * Request a digital currency address to fund this account. Allows to fund the exchange account + * with digital currency from an external address + * + * @param params The deposit address request parameters + * @return the internal deposit address to send funds to + * @throws ExchangeException - Indication that the exchange reported some kind of error with the + * request or response + * @throws NotAvailableFromExchangeException - Indication that the exchange does not support the + * requested function or data + * @throws NotYetImplementedForExchangeException - Indication that the exchange supports the + * requested function or data, but it has not yet been implemented + * @throws IOException - Indication that a networking error occurred while fetching JSON data + */ + default AddressWithTag requestDepositAddressData(RequestDepositAddressParams params) + throws IOException { + return requestDepositAddressData(params.getCurrency(), params.getExtraArguments()); + } + /** * Create {@link TradeHistoryParams} object specific to this exchange. Object created by this * method may be used to discover supported and required {@link diff --git a/xchange-core/src/main/java/org/knowm/xchange/service/account/params/DefaultRequestDepositAddressParams.java b/xchange-core/src/main/java/org/knowm/xchange/service/account/params/DefaultRequestDepositAddressParams.java new file mode 100644 index 00000000000..291b31e3a5c --- /dev/null +++ b/xchange-core/src/main/java/org/knowm/xchange/service/account/params/DefaultRequestDepositAddressParams.java @@ -0,0 +1,27 @@ +package org.knowm.xchange.service.account.params; + +import lombok.Builder; +import lombok.Value; +import lombok.experimental.NonFinal; +import lombok.extern.jackson.Jacksonized; +import org.knowm.xchange.currency.Currency; + +@Value +@NonFinal +@Builder +@Jacksonized +public class DefaultRequestDepositAddressParams implements RequestDepositAddressParams { + Currency currency; + String network; + + @Builder.Default boolean newAddress = false; + + String[] extraArguments; + + public static DefaultRequestDepositAddressParams create(Currency currency, String... args) { + return DefaultRequestDepositAddressParams.builder() + .currency(currency) + .extraArguments(args) + .build(); + } +} diff --git a/xchange-core/src/main/java/org/knowm/xchange/service/account/params/RequestDepositAddressParams.java b/xchange-core/src/main/java/org/knowm/xchange/service/account/params/RequestDepositAddressParams.java new file mode 100644 index 00000000000..9061ad96216 --- /dev/null +++ b/xchange-core/src/main/java/org/knowm/xchange/service/account/params/RequestDepositAddressParams.java @@ -0,0 +1,13 @@ +package org.knowm.xchange.service.account.params; + +import org.knowm.xchange.currency.Currency; + +public interface RequestDepositAddressParams { + Currency getCurrency(); + + String getNetwork(); + + boolean isNewAddress(); + + String[] getExtraArguments(); +} diff --git a/xchange-core/src/main/java/org/knowm/xchange/utils/BigDecimalUtils.java b/xchange-core/src/main/java/org/knowm/xchange/utils/BigDecimalUtils.java index 8f90c2e25c8..8248e049dda 100644 --- a/xchange-core/src/main/java/org/knowm/xchange/utils/BigDecimalUtils.java +++ b/xchange-core/src/main/java/org/knowm/xchange/utils/BigDecimalUtils.java @@ -14,7 +14,7 @@ public static BigDecimal roundToStepSize(BigDecimal value, BigDecimal stepSize) public static BigDecimal roundToStepSize( BigDecimal value, BigDecimal stepSize, RoundingMode roundingMode) { - BigDecimal divided = value.divide(stepSize, MathContext.DECIMAL32).setScale(0, roundingMode); - return divided.multiply(stepSize, MathContext.DECIMAL32).stripTrailingZeros(); + BigDecimal divided = value.divide(stepSize, MathContext.DECIMAL64).setScale(0, roundingMode); + return divided.multiply(stepSize, MathContext.DECIMAL64).stripTrailingZeros(); } } diff --git a/xchange-kraken/src/main/java/org/knowm/xchange/kraken/KrakenAdapters.java b/xchange-kraken/src/main/java/org/knowm/xchange/kraken/KrakenAdapters.java index 111e6bd6995..5967272aa76 100644 --- a/xchange-kraken/src/main/java/org/knowm/xchange/kraken/KrakenAdapters.java +++ b/xchange-kraken/src/main/java/org/knowm/xchange/kraken/KrakenAdapters.java @@ -18,6 +18,7 @@ import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.Order.OrderStatus; import org.knowm.xchange.dto.Order.OrderType; +import org.knowm.xchange.dto.account.AddressWithTag; import org.knowm.xchange.dto.account.Balance; import org.knowm.xchange.dto.account.Fee; import org.knowm.xchange.dto.account.FundingRecord; @@ -332,8 +333,12 @@ public static OrderType adaptOrderType(KrakenType krakenType) { return krakenType.equals(KrakenType.BUY) ? OrderType.BID : OrderType.ASK; } - public static String adaptKrakenDepositAddress(KrakenDepositAddress[] krakenDepositAddress) { - return krakenDepositAddress[0].getAddress(); + public static AddressWithTag adaptKrakenDepositAddress( + KrakenDepositAddress[] krakenDepositAddress) { + return AddressWithTag.builder() + .address(krakenDepositAddress[0].getAddress()) + .addressTag(krakenDepositAddress[0].getTag()) + .build(); } public static String adaptOrderId(KrakenOrderResponse orderResponse) { diff --git a/xchange-kraken/src/main/java/org/knowm/xchange/kraken/dto/account/LedgerType.java b/xchange-kraken/src/main/java/org/knowm/xchange/kraken/dto/account/LedgerType.java index 6f72ec6d4b9..bef033d0493 100644 --- a/xchange-kraken/src/main/java/org/knowm/xchange/kraken/dto/account/LedgerType.java +++ b/xchange-kraken/src/main/java/org/knowm/xchange/kraken/dto/account/LedgerType.java @@ -23,7 +23,10 @@ public enum LedgerType { STAKING, ROLLOVER, TRANSFER, - ADJUSTMENT; + ADJUSTMENT, + SALE, + SPEND, + RECEIVE; private static final Map fromString = new HashMap<>(); diff --git a/xchange-kraken/src/main/java/org/knowm/xchange/kraken/service/KrakenAccountService.java b/xchange-kraken/src/main/java/org/knowm/xchange/kraken/service/KrakenAccountService.java index 047f92aba3f..cf48b98301a 100644 --- a/xchange-kraken/src/main/java/org/knowm/xchange/kraken/service/KrakenAccountService.java +++ b/xchange-kraken/src/main/java/org/knowm/xchange/kraken/service/KrakenAccountService.java @@ -11,9 +11,12 @@ import org.knowm.xchange.currency.Currency; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.account.AccountInfo; +import org.knowm.xchange.dto.account.AddressWithTag; import org.knowm.xchange.dto.account.Fee; import org.knowm.xchange.dto.account.FundingRecord; import org.knowm.xchange.dto.account.Wallet; +import org.knowm.xchange.exceptions.DepositAddressCreationException; +import org.knowm.xchange.exceptions.DepositAddressNotFoundException; import org.knowm.xchange.instrument.Instrument; import org.knowm.xchange.kraken.KrakenAdapters; import org.knowm.xchange.kraken.KrakenUtils; @@ -22,6 +25,8 @@ import org.knowm.xchange.kraken.dto.account.KrakenTradeBalanceInfo; import org.knowm.xchange.kraken.dto.account.LedgerType; import org.knowm.xchange.service.account.AccountService; +import org.knowm.xchange.service.account.params.DefaultRequestDepositAddressParams; +import org.knowm.xchange.service.account.params.RequestDepositAddressParams; import org.knowm.xchange.service.trade.params.DefaultTradeHistoryParamsTimeSpan; import org.knowm.xchange.service.trade.params.DefaultWithdrawFundsParams; import org.knowm.xchange.service.trade.params.HistoryParamsFundingType; @@ -89,50 +94,88 @@ public String withdrawFunds(WithdrawFundsParams params) throws IOException { throw new IllegalStateException("Don't know how to withdraw: " + params); } + @Override + public AddressWithTag requestDepositAddressData(Currency currency, String... args) + throws IOException { + return requestDepositAddressData(DefaultRequestDepositAddressParams.create(currency, args)); + } + @Override public String requestDepositAddress(Currency currency, String... args) throws IOException { + return requestDepositAddressData(DefaultRequestDepositAddressParams.create(currency, args)) + .getAddress(); + } + + @Override + public String requestDepositAddress(RequestDepositAddressParams requestDepositAddressParams) + throws IOException { + return requestDepositAddressData(requestDepositAddressParams).getAddress(); + } + + @Override + public AddressWithTag requestDepositAddressData( + RequestDepositAddressParams requestDepositAddressParams) throws IOException { + Currency currency = requestDepositAddressParams.getCurrency(); + boolean newAddress = requestDepositAddressParams.isNewAddress(); + String depositMethod = null; + KrakenDepositAddress[] depositAddresses; if (Currency.BTC.equals(currency)) { - depositAddresses = getDepositAddresses(currency.toString(), "Bitcoin", false); + depositAddresses = getDepositAddresses(currency.toString(), "Bitcoin", newAddress); } else if (Currency.LTC.equals(currency)) { - depositAddresses = getDepositAddresses(currency.toString(), "Litecoin", false); + depositAddresses = getDepositAddresses(currency.toString(), "Litecoin", newAddress); } else if (Currency.ETH.equals(currency)) { - depositAddresses = getDepositAddresses(currency.toString(), "Ethereum (ERC20)", false); + depositAddresses = getDepositAddresses(currency.toString(), "Ethereum (ERC20)", newAddress); } else if (Currency.ZEC.equals(currency)) { - depositAddresses = getDepositAddresses(currency.toString(), "Zcash (Transparent)", false); + depositAddresses = + getDepositAddresses(currency.toString(), "Zcash (Transparent)", newAddress); } else if (Currency.ADA.equals(currency)) { - depositAddresses = getDepositAddresses(currency.toString(), "ADA", false); + depositAddresses = getDepositAddresses(currency.toString(), "ADA", newAddress); } else if (Currency.XMR.equals(currency)) { - depositAddresses = getDepositAddresses(currency.toString(), "Monero", false); + depositAddresses = getDepositAddresses(currency.toString(), "Monero", newAddress); } else if (Currency.XRP.equals(currency)) { - depositAddresses = getDepositAddresses(currency.toString(), "Ripple XRP", false); + depositAddresses = getDepositAddresses(currency.toString(), "Ripple XRP", newAddress); } else if (Currency.XLM.equals(currency)) { - depositAddresses = getDepositAddresses(currency.toString(), "Stellar XLM", false); + depositAddresses = getDepositAddresses(currency.toString(), "Stellar XLM", newAddress); } else if (Currency.BCH.equals(currency)) { - depositAddresses = getDepositAddresses(currency.toString(), "Bitcoin Cash", false); + depositAddresses = getDepositAddresses(currency.toString(), "Bitcoin Cash", newAddress); } else if (Currency.REP.equals(currency)) { - depositAddresses = getDepositAddresses(currency.toString(), "REP", false); + depositAddresses = getDepositAddresses(currency.toString(), "REP", newAddress); } else if (Currency.USD.equals(currency)) { - depositAddresses = getDepositAddresses(currency.toString(), "SynapsePay (US Wire)", false); + depositAddresses = + getDepositAddresses(currency.toString(), "SynapsePay (US Wire)", newAddress); } else if (Currency.XDG.equals(currency)) { - depositAddresses = getDepositAddresses(currency.toString(), "Dogecoin", false); + depositAddresses = getDepositAddresses(currency.toString(), "Dogecoin", newAddress); } else if (Currency.MLN.equals(currency)) { - depositAddresses = getDepositAddresses(currency.toString(), "MLN", false); + depositAddresses = getDepositAddresses(currency.toString(), "MLN", newAddress); } else if (Currency.GNO.equals(currency)) { - depositAddresses = getDepositAddresses(currency.toString(), "GNO", false); + depositAddresses = getDepositAddresses(currency.toString(), "GNO", newAddress); } else if (Currency.QTUM.equals(currency)) { - depositAddresses = getDepositAddresses(currency.toString(), "QTUM", false); + depositAddresses = getDepositAddresses(currency.toString(), "QTUM", newAddress); } else if (Currency.XTZ.equals(currency)) { - depositAddresses = getDepositAddresses(currency.toString(), "XTZ", false); + depositAddresses = getDepositAddresses(currency.toString(), "XTZ", newAddress); } else if (Currency.ATOM.equals(currency)) { - depositAddresses = getDepositAddresses(currency.toString(), "Cosmos", false); + depositAddresses = getDepositAddresses(currency.toString(), "Cosmos", newAddress); } else if (Currency.EOS.equals(currency)) { - depositAddresses = getDepositAddresses(currency.toString(), "EOS", false); + depositAddresses = getDepositAddresses(currency.toString(), "EOS", newAddress); } else if (Currency.DASH.equals(currency)) { - depositAddresses = getDepositAddresses(currency.toString(), "Dash", false); + depositAddresses = getDepositAddresses(currency.toString(), "Dash", newAddress); } else { - throw new RuntimeException("Not implemented yet, Kraken works only for BTC and LTC"); + depositMethod = findDepositMethod(currency, requestDepositAddressParams.getNetwork()); + depositAddresses = getDepositAddresses(currency.toString(), depositMethod, newAddress); + } + + if (depositAddresses.length == 0 && !newAddress) { + throw new DepositAddressNotFoundException( + String.format("No deposit addresses found for %s method: %s", currency, depositMethod)); + } + + if (depositAddresses.length == 0) { + throw new DepositAddressCreationException( + String.format( + "Deposit address could not be created for %s method: %s", currency, depositMethod)); } + return KrakenAdapters.adaptKrakenDepositAddress(depositAddresses); } diff --git a/xchange-kraken/src/main/java/org/knowm/xchange/kraken/service/KrakenAccountServiceRaw.java b/xchange-kraken/src/main/java/org/knowm/xchange/kraken/service/KrakenAccountServiceRaw.java index c816d4297f6..726690c2fc9 100644 --- a/xchange-kraken/src/main/java/org/knowm/xchange/kraken/service/KrakenAccountServiceRaw.java +++ b/xchange-kraken/src/main/java/org/knowm/xchange/kraken/service/KrakenAccountServiceRaw.java @@ -2,12 +2,17 @@ import java.io.IOException; import java.math.BigDecimal; +import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; import org.knowm.xchange.Exchange; import org.knowm.xchange.currency.Currency; import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.exceptions.DepositAddressAmbiguousException; import org.knowm.xchange.kraken.KrakenUtils; import org.knowm.xchange.kraken.dto.account.DepostitStatus; import org.knowm.xchange.kraken.dto.account.KrakenDepositAddress; @@ -23,7 +28,6 @@ import org.knowm.xchange.kraken.dto.account.results.DepositStatusResult; import org.knowm.xchange.kraken.dto.account.results.KrakenBalanceResult; import org.knowm.xchange.kraken.dto.account.results.KrakenDepositAddressResult; -import org.knowm.xchange.kraken.dto.account.results.KrakenDepositMethodsResults; import org.knowm.xchange.kraken.dto.account.results.KrakenLedgerResult; import org.knowm.xchange.kraken.dto.account.results.KrakenQueryLedgerResult; import org.knowm.xchange.kraken.dto.account.results.KrakenTradeBalanceInfoResult; @@ -37,15 +41,18 @@ /** * @author jamespedwards42 */ +@Slf4j public class KrakenAccountServiceRaw extends KrakenBaseService { + private ConcurrentHashMap depositMethods = + new ConcurrentHashMap<>(); + /** * Constructor * * @param exchange */ public KrakenAccountServiceRaw(Exchange exchange) { - super(exchange); } @@ -82,14 +89,64 @@ public KrakenDepositAddress[] getDepositAddresses( public KrakenDepositMethods[] getDepositMethods(String assetPairs, String assets) throws IOException { - KrakenDepositMethodsResults depositMethods = + if (shouldCacheDepositMethods()) { + try { + return depositMethods.computeIfAbsent( + String.format("%s%s", assetPairs, assets), + k -> { + try { + return getDepositMethodsFromRemote(assetPairs, assets); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } catch (RuntimeException e) { + if (e.getCause() instanceof IOException) { + throw (IOException) e.getCause(); + } + throw e; + } + } + + return getDepositMethodsFromRemote(assetPairs, assets); + } + + private KrakenDepositMethods[] getDepositMethodsFromRemote(String assetPairs, String assets) + throws IOException { + return checkResult( kraken.getDepositMethods( assetPairs, assets, exchange.getExchangeSpecification().getApiKey(), signatureCreator, - exchange.getNonceFactory()); - return checkResult(depositMethods); + exchange.getNonceFactory())); + } + + protected String findDepositMethod(Currency currency, String network) throws IOException { + KrakenDepositMethods[] depositMethods = getDepositMethods(null, currency.toString()); + + if (depositMethods == null || depositMethods.length == 0) { + return network; + } + + if (depositMethods.length == 1) { + return depositMethods[0].getMethod(); + } + + log.warn( + "Multiple methods for currency {} {}", + currency, + Arrays.stream(depositMethods).map(KrakenDepositMethods::getMethod).toArray()); + + if (network == null) { + throw new DepositAddressAmbiguousException( + Arrays.stream(depositMethods) + .map(KrakenDepositMethods::getMethod) + .collect(Collectors.toList()), + "Multiple deposit methods available for " + currency + ", require to specify network"); + } + + return network; // Use network if requested } public WithdrawInfo getWithdrawInfo( @@ -305,4 +362,12 @@ public KrakenTradeVolume getTradeVolume(CurrencyPair... currencyPairs) throws IO exchange.getNonceFactory()); return checkResult(result); } + + private boolean shouldCacheDepositMethods() { + return (boolean) + exchange + .getExchangeSpecification() + .getExchangeSpecificParameters() + .getOrDefault("cacheDepositMethods", false); + } } diff --git a/xchange-kraken/src/test/java/org/knowm/xchange/kraken/service/BaseWiremockTest.java b/xchange-kraken/src/test/java/org/knowm/xchange/kraken/service/BaseWiremockTest.java index 68fb2ee2515..6c5edf6f182 100644 --- a/xchange-kraken/src/test/java/org/knowm/xchange/kraken/service/BaseWiremockTest.java +++ b/xchange-kraken/src/test/java/org/knowm/xchange/kraken/service/BaseWiremockTest.java @@ -2,8 +2,11 @@ import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import com.fasterxml.jackson.databind.ObjectMapper; import com.github.tomakehurst.wiremock.junit.WireMockRule; import com.google.common.collect.ImmutableMap; +import lombok.SneakyThrows; +import org.apache.commons.io.IOUtils; import org.junit.Rule; import org.knowm.xchange.Exchange; import org.knowm.xchange.ExchangeFactory; @@ -17,6 +20,8 @@ public class BaseWiremockTest { @Rule public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort()); + private final ObjectMapper objectMapper = new ObjectMapper(); + public Exchange createExchange() { KrakenUtils.setKrakenAssets(ASSETS); KrakenUtils.setKrakenAssetPairs(ASSET_PAIRS); @@ -31,6 +36,11 @@ public Exchange createExchange() { return exchange; } + @SneakyThrows + protected byte[] loadFile(String path) { + return IOUtils.toByteArray(getClass().getResourceAsStream(path)); + } + public static final ImmutableMap ASSETS = ImmutableMap.of( "XXBT", new KrakenAsset("XBT", "currency", 8, 6), diff --git a/xchange-kraken/src/test/java/org/knowm/xchange/kraken/service/KrakenAccountServiceTest.java b/xchange-kraken/src/test/java/org/knowm/xchange/kraken/service/KrakenAccountServiceTest.java new file mode 100644 index 00000000000..8f1d262a402 --- /dev/null +++ b/xchange-kraken/src/test/java/org/knowm/xchange/kraken/service/KrakenAccountServiceTest.java @@ -0,0 +1,213 @@ +package org.knowm.xchange.kraken.service; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.junit.Before; +import org.junit.Test; +import org.knowm.xchange.Exchange; +import org.knowm.xchange.currency.Currency; +import org.knowm.xchange.dto.account.AddressWithTag; +import org.knowm.xchange.exceptions.DepositAddressAmbiguousException; +import org.knowm.xchange.service.account.params.DefaultRequestDepositAddressParams; + +@Slf4j +public class KrakenAccountServiceTest extends BaseWiremockTest { + + private KrakenAccountService classUnderTest; + private Exchange exchange; + + @Before + public void setup() { + exchange = createExchange(); + classUnderTest = (KrakenAccountService) exchange.getAccountService(); + } + + @Test + @SneakyThrows + public void testRequestDepositAddressUnknownCurrency() { + stubFor( + post(urlPathEqualTo("/0/private/DepositMethods")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + loadFile( + "/org/knowm/xchange/kraken/dto/account/example-deposit-methods-trx.json")))); + + stubFor( + post(urlPathEqualTo("/0/private/DepositAddresses")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + loadFile( + "/org/knowm/xchange/kraken/dto/account/example-deposit-addresses-trx.json")))); + + DefaultRequestDepositAddressParams params = + DefaultRequestDepositAddressParams.builder().currency(Currency.TRX).build(); + + String address = classUnderTest.requestDepositAddress(params); + + assertThat(address).isEqualTo("TYAnp8VW1aq5Jbtxgoai7BDo3jKSRe6VNR"); + } + + @Test + @SneakyThrows + public void testRequestDepositAddressKnownCurrency() { + stubFor( + post(urlPathEqualTo("/0/private/DepositAddresses")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + loadFile( + "/org/knowm/xchange/kraken/dto/account/example-deposit-addresses.json")))); + + DefaultRequestDepositAddressParams params = + DefaultRequestDepositAddressParams.builder().currency(Currency.BTC).build(); + + String address = classUnderTest.requestDepositAddress(params); + + assertThat(address).isEqualTo("testBtcAddress"); + } + + @Test + @SneakyThrows + public void testRequestDepositAddressUnknownCurrencyMultipleMethods() { + stubFor( + post(urlPathEqualTo("/0/private/DepositMethods")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + loadFile( + "/org/knowm/xchange/kraken/dto/account/example-deposit-methods-usdt.json")))); + + assertThatThrownBy( + () -> { + DefaultRequestDepositAddressParams params = + DefaultRequestDepositAddressParams.builder().currency(Currency.USDT).build(); + + classUnderTest.requestDepositAddress(params); + }) + .isInstanceOf(DepositAddressAmbiguousException.class); + } + + @Test + @SneakyThrows + public void testRequestDepositAddressUnknownCurrencyMultipleMethodsWithNetwork() { + stubFor( + post(urlPathEqualTo("/0/private/DepositMethods")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + loadFile( + "/org/knowm/xchange/kraken/dto/account/example-deposit-methods-xrp.json")))); + + stubFor( + post(urlPathEqualTo("/0/private/DepositAddresses")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + loadFile( + "/org/knowm/xchange/kraken/dto/account/example-deposit-addresses-xrp.json")))); + + DefaultRequestDepositAddressParams params = + DefaultRequestDepositAddressParams.builder().currency(Currency.XRP).build(); + + AddressWithTag address = classUnderTest.requestDepositAddressData(params); + + assertThat(address.getAddress()).isEqualTo("testXrpAddress"); + assertThat(address.getAddressTag()).isEqualTo("123"); + } + + @Test + @SneakyThrows + public void testRequestDepositMethodCaching() { + stubFor( + post(urlPathEqualTo("/0/private/DepositMethods")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + loadFile( + "/org/knowm/xchange/kraken/dto/account/example-deposit-methods-trx.json")))); + + stubFor( + post(urlPathEqualTo("/0/private/DepositAddresses")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + loadFile( + "/org/knowm/xchange/kraken/dto/account/example-deposit-addresses-trx.json")))); + + exchange + .getExchangeSpecification() + .setExchangeSpecificParametersItem("cacheDepositMethods", true); + + DefaultRequestDepositAddressParams params = + DefaultRequestDepositAddressParams.builder().currency(Currency.TRX).build(); + + classUnderTest.requestDepositAddress(params); + classUnderTest.requestDepositAddress(params); + + verify(1, postRequestedFor(urlEqualTo("/0/private/DepositMethods"))); + } + + @Test + @SneakyThrows + public void testRequestDepositMethodNoCache() { + stubFor( + post(urlPathEqualTo("/0/private/DepositMethods")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + loadFile( + "/org/knowm/xchange/kraken/dto/account/example-deposit-methods-trx.json")))); + + stubFor( + post(urlPathEqualTo("/0/private/DepositAddresses")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + loadFile( + "/org/knowm/xchange/kraken/dto/account/example-deposit-addresses-trx.json")))); + + DefaultRequestDepositAddressParams params = + DefaultRequestDepositAddressParams.builder().currency(Currency.TRX).build(); + + exchange + .getExchangeSpecification() + .setExchangeSpecificParametersItem("cacheDepositMethods", false); + + classUnderTest.requestDepositAddress(params); + classUnderTest.requestDepositAddress(params); + + verify(2, postRequestedFor(urlEqualTo("/0/private/DepositMethods"))); + } +} diff --git a/xchange-kraken/src/test/resources/org/knowm/xchange/kraken/dto/account/example-deposit-addresses-trx.json b/xchange-kraken/src/test/resources/org/knowm/xchange/kraken/dto/account/example-deposit-addresses-trx.json new file mode 100644 index 00000000000..406a9f0af40 --- /dev/null +++ b/xchange-kraken/src/test/resources/org/knowm/xchange/kraken/dto/account/example-deposit-addresses-trx.json @@ -0,0 +1,10 @@ +{ + "error": [], + "result": [ + { + "address": "TYAnp8VW1aq5Jbtxgoai7BDo3jKSRe6VNR", + "expiretm": "0", + "new": true + } + ] +} \ No newline at end of file diff --git a/xchange-kraken/src/test/resources/org/knowm/xchange/kraken/dto/account/example-deposit-addresses-xrp.json b/xchange-kraken/src/test/resources/org/knowm/xchange/kraken/dto/account/example-deposit-addresses-xrp.json new file mode 100644 index 00000000000..2ba6c97210f --- /dev/null +++ b/xchange-kraken/src/test/resources/org/knowm/xchange/kraken/dto/account/example-deposit-addresses-xrp.json @@ -0,0 +1,11 @@ +{ + "error": [], + "result": [ + { + "address": "testXrpAddress", + "expiretm": "0", + "new": true, + "tag": "123" + } + ] +} \ No newline at end of file diff --git a/xchange-kraken/src/test/resources/org/knowm/xchange/kraken/dto/account/example-deposit-addresses.json b/xchange-kraken/src/test/resources/org/knowm/xchange/kraken/dto/account/example-deposit-addresses.json new file mode 100644 index 00000000000..531c0c67317 --- /dev/null +++ b/xchange-kraken/src/test/resources/org/knowm/xchange/kraken/dto/account/example-deposit-addresses.json @@ -0,0 +1,10 @@ +{ + "error": [], + "result": [ + { + "address": "testBtcAddress", + "expiretm": "0", + "new": true + } + ] +} \ No newline at end of file diff --git a/xchange-kraken/src/test/resources/org/knowm/xchange/kraken/dto/account/example-deposit-methods-trx.json b/xchange-kraken/src/test/resources/org/knowm/xchange/kraken/dto/account/example-deposit-methods-trx.json new file mode 100644 index 00000000000..48e4bd47000 --- /dev/null +++ b/xchange-kraken/src/test/resources/org/knowm/xchange/kraken/dto/account/example-deposit-methods-trx.json @@ -0,0 +1,11 @@ +{ + "error": [], + "result": [ + { + "method": "Tron", + "limit": false, + "gen-address": true, + "minimum": "2.000000" + } + ] +} \ No newline at end of file diff --git a/xchange-kraken/src/test/resources/org/knowm/xchange/kraken/dto/account/example-deposit-methods-usdt.json b/xchange-kraken/src/test/resources/org/knowm/xchange/kraken/dto/account/example-deposit-methods-usdt.json new file mode 100644 index 00000000000..8a5498cef1d --- /dev/null +++ b/xchange-kraken/src/test/resources/org/knowm/xchange/kraken/dto/account/example-deposit-methods-usdt.json @@ -0,0 +1,42 @@ +{ + "error": [], + "result": [ + { + "method": "USDT - Ethereum (Unified)", + "limit": false, + "gen-address": true, + "minimum": "7.11000000" + }, + { + "method": "Tether USD (TRC20)", + "limit": false, + "gen-address": true, + "minimum": "5.00000000" + }, + { + "method": "Tether USD (SPL)", + "limit": false, + "fee": "0.00000000", + "gen-address": true, + "minimum": "0.40000000" + }, + { + "method": "USDT - Polygon (Unified)", + "limit": false, + "gen-address": true, + "minimum": "2.00000000" + }, + { + "method": "USDT - Arbitrum One (Unified)", + "limit": false, + "gen-address": true, + "minimum": "2.50000000" + }, + { + "method": "USDT - Optimism (Unified)", + "limit": false, + "gen-address": true, + "minimum": "2.50000000" + } + ] +} diff --git a/xchange-kraken/src/test/resources/org/knowm/xchange/kraken/dto/account/example-deposit-methods-xrp.json b/xchange-kraken/src/test/resources/org/knowm/xchange/kraken/dto/account/example-deposit-methods-xrp.json new file mode 100644 index 00000000000..e435b6cb859 --- /dev/null +++ b/xchange-kraken/src/test/resources/org/knowm/xchange/kraken/dto/account/example-deposit-methods-xrp.json @@ -0,0 +1,11 @@ +{ + "error": [], + "result": [ + { + "method": "Ripple XRP", + "limit": false, + "gen-address": true, + "minimum": "1" + } + ] +}