diff --git a/README.md b/README.md
index 0d3fe82..d162bcc 100644
--- a/README.md
+++ b/README.md
@@ -30,9 +30,9 @@ composer require "kucoin/kucoin-php-sdk:~1.1.0"
### Choose environment
-| Environment | BaseUri |
-|---------------|-----------------------------------------------------|
-| *Production* | https://api.kucoin.com |
+| Environment | BaseUri |
+|--------------|------------------------|
+| *Production* | https://api.kucoin.com |
```php
// Switch to the sandbox environment
@@ -306,48 +306,48 @@ go(function () {
KuCoin\SDK\PrivateApi\Order
-| API | Authentication | Description |
-|-------------------------------------------------------------|----------------|---------------------------------------------------------------------------------------------------|
-| KuCoin\SDK\PrivateApi\Order::create() | YES | https://docs.kucoin.com/#place-a-new-order |
-| KuCoin\SDK\PrivateApi\Order::createMulti() | YES | https://docs.kucoin.com/#place-bulk-orders |
-| KuCoin\SDK\PrivateApi\Order::cancel() | YES | https://docs.kucoin.com/#cancel-an-order |
-| KuCoin\SDK\PrivateApi\Order::cancelAll() | YES | https://docs.kucoin.com/#cancel-all-orders |
-| KuCoin\SDK\PrivateApi\Order::getList() | YES | https://docs.kucoin.com/#list-orders |
-| KuCoin\SDK\PrivateApi\Order::getV1List() | YES | `DEPRECATED`https://docs.kucoin.com/#get-v1-historical-orders-list |
-| KuCoin\SDK\PrivateApi\Order::getDetail() | YES | https://docs.kucoin.com/#get-an-order |
-| KuCoin\SDK\PrivateApi\Order::getRecentList() | YES | https://docs.kucoin.com/#recent-orders |
-| KuCoin\SDK\PrivateApi\Order::createMarginOrder() | YES | https://docs.kucoin.com/#place-a-margin-order |
-| KuCoin\SDK\PrivateApi\Order::cancelByClientOid() | YES | https://docs.kucoin.com/#cancel-single-order-by-clientoid |
-| KuCoin\SDK\PrivateApi\Order::getDetailByClientOid() | YES | https://docs.kucoin.com/#get-single-active-order-by-clientoid |
-| KuCoin\SDK\PrivateApi\Order::hfCreate() | YES | https://docs.kucoin.com/spot-hf/#place-hf-order |
-| KuCoin\SDK\PrivateApi\Order::hfSyncCreate() | YES | https://docs.kucoin.com/spot-hf/#sync-place-hf-order |
-| KuCoin\SDK\PrivateApi\Order::hfCreateMulti() | YES | https://docs.kucoin.com/spot-hf/#place-multiple-hf-orders |
-| KuCoin\SDK\PrivateApi\Order::hfSyncCreateMulti() | YES | https://docs.kucoin.com/spot-hf/#sync-place-multiple-hf-orders |
-| KuCoin\SDK\PrivateApi\Order::hfModify() | YES | https://docs.kucoin.com/spot-hf/#modify-order |
-| KuCoin\SDK\PrivateApi\Order::hfCancel() | YES | https://docs.kucoin.com/spot-hf/#cancel-orders-by-orderid |
-| KuCoin\SDK\PrivateApi\Order::hfSyncCancel() | YES | https://docs.kucoin.com/spot-hf/#sync-cancel-orders-by-orderid |
-| KuCoin\SDK\PrivateApi\Order::hfCancelByClientOid() | YES | https://docs.kucoin.com/spot-hf/#cancel-order-by-clientoid |
-| KuCoin\SDK\PrivateApi\Order::hfSyncCancelByClientOid() | YES | https://docs.kucoin.com/spot-hf/#sync-cancel-orders-by-clientoid |
-| KuCoin\SDK\PrivateApi\Order::hfSyncCancelSize() | YES | https://docs.kucoin.com/spot-hf/#cancel-specified-number-of-orders-by-orderid |
-| KuCoin\SDK\PrivateApi\Order::hfSyncCancelAll() | YES | https://docs.kucoin.com/spot-hf/#cancel-all-hf-orders-by-symbol |
-| KuCoin\SDK\PrivateApi\Order::getActiveOrderList() | YES | https://docs.kucoin.com/spot-hf/#obtain-list-of-active-hf-orders |
-| KuCoin\SDK\PrivateApi\Order::getActiveSymbols() | YES | https://docs.kucoin.com/spot-hf/#obtain-list-of-symbol-with-active-hf-orders |
-| KuCoin\SDK\PrivateApi\Order::getDoneOrderList() | YES | https://docs.kucoin.com/spot-hf/#obtain-list-of-filled-hf-orders |
-| KuCoin\SDK\PrivateApi\Order::getHfDetail() | YES | https://docs.kucoin.com/spot-hf/#details-of-a-single-hf-order |
-| KuCoin\SDK\PrivateApi\Order::getHfDetailByClientOid() | YES | https://docs.kucoin.com/spot-hf/#obtain-details-of-a-single-hf-order-using-clientoid |
-| KuCoin\SDK\PrivateApi\Order::hfAutoCancel() | YES | https://docs.kucoin.com/spot-hf/#hf-auto-cancel-setting |
-| KuCoin\SDK\PrivateApi\Order::getHfAutoCancel() | YES | https://docs.kucoin.com/spot-hf/#hf-auto-cancel-order-setting-query |
-| KuCoin\SDK\PrivateApi\Order::getHfFills() | YES | https://docs.kucoin.com/spot-hf/#hf-transaction-records |
-| KuCoin\SDK\PrivateApi\Order::hfCancelAll() | YES | https://www.kucoin.com/docs/rest/spot-trading/spot-hf-trade-pro-account/cancel-all-hf-orders |
-| KuCoin\SDK\PrivateApi\Order::createHfMarginOrder() | YES | https://www.kucoin.com/docs/rest/margin-trading/margin-hf-trade/place-hf-order |
-| KuCoin\SDK\PrivateApi\Order::cancelMarginHfOrder() | YES | https://www.kucoin.com/docs/rest/margin-trading/margin-hf-trade/cancel-hf-order-by-orderid |
-| KuCoin\SDK\PrivateApi\Order::cancelMarginHfOrderByClientOid() | YES | https://www.kucoin.com/docs/rest/margin-trading/margin-hf-trade/cancel-hf-order-by-clientoid |
-| KuCoin\SDK\PrivateApi\Order::cancelAllMarginHfOrder() | YES | https://www.kucoin.com/docs/rest/margin-trading/margin-hf-trade/cancel-all-hf-orders-by-symbol |
-| KuCoin\SDK\PrivateApi\Order::getMarginHfActiveOrders() | YES | https://www.kucoin.com/docs/rest/margin-trading/margin-hf-trade/get-active-hf-orders-list |
-| KuCoin\SDK\PrivateApi\Order::getMarginHfFilledOrders() | YES | https://www.kucoin.com/docs/rest/margin-trading/margin-hf-trade/get-hf-filled-list |
-| KuCoin\SDK\PrivateApi\Order::getMarginHfDetail() | YES | https://www.kucoin.com/docs/rest/margin-trading/margin-hf-trade/get-hf-order-details-by-orderid |
-| KuCoin\SDK\PrivateApi\Order::getMarginHfDetailByClientOid() | YES | https://www.kucoin.com/docs/rest/margin-trading/margin-hf-trade/get-hf-order-details-by-clientoid |
-| KuCoin\SDK\PrivateApi\Order::getMarginHfFills() | YES | https://www.kucoin.com/docs/rest/margin-trading/margin-hf-trade/get-hf-transaction-records |
+| API | Authentication | Description |
+|---------------------------------------------------------------|----------------|---------------------------------------------------------------------------------------------------|
+| KuCoin\SDK\PrivateApi\Order::create() | YES | https://docs.kucoin.com/#place-a-new-order |
+| KuCoin\SDK\PrivateApi\Order::createMulti() | YES | https://docs.kucoin.com/#place-bulk-orders |
+| KuCoin\SDK\PrivateApi\Order::cancel() | YES | https://docs.kucoin.com/#cancel-an-order |
+| KuCoin\SDK\PrivateApi\Order::cancelAll() | YES | https://docs.kucoin.com/#cancel-all-orders |
+| KuCoin\SDK\PrivateApi\Order::getList() | YES | https://docs.kucoin.com/#list-orders |
+| KuCoin\SDK\PrivateApi\Order::getV1List() | YES | `DEPRECATED`https://docs.kucoin.com/#get-v1-historical-orders-list |
+| KuCoin\SDK\PrivateApi\Order::getDetail() | YES | https://docs.kucoin.com/#get-an-order |
+| KuCoin\SDK\PrivateApi\Order::getRecentList() | YES | https://docs.kucoin.com/#recent-orders |
+| KuCoin\SDK\PrivateApi\Order::createMarginOrder() | YES | https://docs.kucoin.com/#place-a-margin-order |
+| KuCoin\SDK\PrivateApi\Order::cancelByClientOid() | YES | https://docs.kucoin.com/#cancel-single-order-by-clientoid |
+| KuCoin\SDK\PrivateApi\Order::getDetailByClientOid() | YES | https://docs.kucoin.com/#get-single-active-order-by-clientoid |
+| KuCoin\SDK\PrivateApi\Order::hfCreate() | YES | https://docs.kucoin.com/spot-hf/#place-hf-order |
+| KuCoin\SDK\PrivateApi\Order::hfSyncCreate() | YES | https://docs.kucoin.com/spot-hf/#sync-place-hf-order |
+| KuCoin\SDK\PrivateApi\Order::hfCreateMulti() | YES | https://docs.kucoin.com/spot-hf/#place-multiple-hf-orders |
+| KuCoin\SDK\PrivateApi\Order::hfSyncCreateMulti() | YES | https://docs.kucoin.com/spot-hf/#sync-place-multiple-hf-orders |
+| KuCoin\SDK\PrivateApi\Order::hfModify() | YES | https://docs.kucoin.com/spot-hf/#modify-order |
+| KuCoin\SDK\PrivateApi\Order::hfCancel() | YES | https://docs.kucoin.com/spot-hf/#cancel-orders-by-orderid |
+| KuCoin\SDK\PrivateApi\Order::hfSyncCancel() | YES | https://docs.kucoin.com/spot-hf/#sync-cancel-orders-by-orderid |
+| KuCoin\SDK\PrivateApi\Order::hfCancelByClientOid() | YES | https://docs.kucoin.com/spot-hf/#cancel-order-by-clientoid |
+| KuCoin\SDK\PrivateApi\Order::hfSyncCancelByClientOid() | YES | https://docs.kucoin.com/spot-hf/#sync-cancel-orders-by-clientoid |
+| KuCoin\SDK\PrivateApi\Order::hfSyncCancelSize() | YES | https://docs.kucoin.com/spot-hf/#cancel-specified-number-of-orders-by-orderid |
+| KuCoin\SDK\PrivateApi\Order::hfSyncCancelAll() | YES | https://docs.kucoin.com/spot-hf/#cancel-all-hf-orders-by-symbol |
+| KuCoin\SDK\PrivateApi\Order::getActiveOrderList() | YES | https://docs.kucoin.com/spot-hf/#obtain-list-of-active-hf-orders |
+| KuCoin\SDK\PrivateApi\Order::getActiveSymbols() | YES | https://docs.kucoin.com/spot-hf/#obtain-list-of-symbol-with-active-hf-orders |
+| KuCoin\SDK\PrivateApi\Order::getDoneOrderList() | YES | https://docs.kucoin.com/spot-hf/#obtain-list-of-filled-hf-orders |
+| KuCoin\SDK\PrivateApi\Order::getHfDetail() | YES | https://docs.kucoin.com/spot-hf/#details-of-a-single-hf-order |
+| KuCoin\SDK\PrivateApi\Order::getHfDetailByClientOid() | YES | https://docs.kucoin.com/spot-hf/#obtain-details-of-a-single-hf-order-using-clientoid |
+| KuCoin\SDK\PrivateApi\Order::hfAutoCancel() | YES | https://docs.kucoin.com/spot-hf/#hf-auto-cancel-setting |
+| KuCoin\SDK\PrivateApi\Order::getHfAutoCancel() | YES | https://docs.kucoin.com/spot-hf/#hf-auto-cancel-order-setting-query |
+| KuCoin\SDK\PrivateApi\Order::getHfFills() | YES | https://docs.kucoin.com/spot-hf/#hf-transaction-records |
+| KuCoin\SDK\PrivateApi\Order::hfCancelAll() | YES | https://www.kucoin.com/docs/rest/spot-trading/spot-hf-trade-pro-account/cancel-all-hf-orders |
+| KuCoin\SDK\PrivateApi\Order::createHfMarginOrder() | YES | https://www.kucoin.com/docs/rest/margin-trading/margin-hf-trade/place-hf-order |
+| KuCoin\SDK\PrivateApi\Order::cancelMarginHfOrder() | YES | https://www.kucoin.com/docs/rest/margin-trading/margin-hf-trade/cancel-hf-order-by-orderid |
+| KuCoin\SDK\PrivateApi\Order::cancelMarginHfOrderByClientOid() | YES | https://www.kucoin.com/docs/rest/margin-trading/margin-hf-trade/cancel-hf-order-by-clientoid |
+| KuCoin\SDK\PrivateApi\Order::cancelAllMarginHfOrder() | YES | https://www.kucoin.com/docs/rest/margin-trading/margin-hf-trade/cancel-all-hf-orders-by-symbol |
+| KuCoin\SDK\PrivateApi\Order::getMarginHfActiveOrders() | YES | https://www.kucoin.com/docs/rest/margin-trading/margin-hf-trade/get-active-hf-orders-list |
+| KuCoin\SDK\PrivateApi\Order::getMarginHfFilledOrders() | YES | https://www.kucoin.com/docs/rest/margin-trading/margin-hf-trade/get-hf-filled-list |
+| KuCoin\SDK\PrivateApi\Order::getMarginHfDetail() | YES | https://www.kucoin.com/docs/rest/margin-trading/margin-hf-trade/get-hf-order-details-by-orderid |
+| KuCoin\SDK\PrivateApi\Order::getMarginHfDetailByClientOid() | YES | https://www.kucoin.com/docs/rest/margin-trading/margin-hf-trade/get-hf-order-details-by-clientoid |
+| KuCoin\SDK\PrivateApi\Order::getMarginHfFills() | YES | https://www.kucoin.com/docs/rest/margin-trading/margin-hf-trade/get-hf-transaction-records |
@@ -378,6 +378,24 @@ go(function () {
+
+KuCoin\SDK\PrivateApi\Earn
+
+| API | Authentication | Description |
+|-----------------------------------------------------|----------------|--------------------------------------------------------------------------------------------|
+| KuCoin\SDK\PrivateApi\Earn::getSavingProducts() | YES | https://www.kucoin.com/docs/rest/earn/kucoin-earn/get-earn-savings-products |
+| KuCoin\SDK\PrivateApi\Earn::getPromotionProducts() | YES | https://www.kucoin.com/docs/rest/earn/kucoin-earn/get-earn-limited-time-promotion-products |
+| KuCoin\SDK\PrivateApi\Earn::getStakingProducts() | YES | https://www.kucoin.com/docs/rest/earn/staking/get-earn-staking-products |
+| KuCoin\SDK\PrivateApi\Earn::getEthStakingProducts() | YES | https://www.kucoin.com/docs/rest/earn/staking/get-earn-eth-staking-products |
+| KuCoin\SDK\PrivateApi\Earn::getKcsStakingProducts() | YES | https://www.kucoin.com/docs/rest/earn/staking/get-earn-kcs-staking-products |
+| KuCoin\SDK\PrivateApi\Earn::subscribe() | YES | https://www.kucoin.com/docs/rest/earn/general/subscribe-to-earn-fixed-income-products |
+| KuCoin\SDK\PrivateApi\Earn::redeemPreview() | YES | https://www.kucoin.com/docs/rest/earn/general/get-earn-redeem-preview-by-holding-id |
+| KuCoin\SDK\PrivateApi\Earn::redeem() | YES | https://www.kucoin.com/docs/rest/earn/general/redeem-by-earn-holding-id |
+| KuCoin\SDK\PrivateApi\Earn::getHoldAssets() | YES | https://www.kucoin.com/docs/rest/earn/kucoin-earn/get-earn-fixed-income-current-holdings |
+
+
+
+
KuCoin\SDK\PrivateApi\StopOrder
diff --git a/src/Api.php b/src/Api.php
index 69ab507..f912855 100644
--- a/src/Api.php
+++ b/src/Api.php
@@ -16,12 +16,12 @@ abstract class Api
/**
* @var string SDK Version
*/
- const VERSION = '1.1.31';
+ const VERSION = '1.1.32';
/**
* @var string SDK update date
*/
- const UPDATE_DATE = '2024.05.28';
+ const UPDATE_DATE = '2024.07.04';
/**
* @var string
diff --git a/src/Enums/AccountType.php b/src/Enums/AccountType.php
new file mode 100644
index 0000000..2efa066
--- /dev/null
+++ b/src/Enums/AccountType.php
@@ -0,0 +1,10 @@
+call(Request::METHOD_GET, '/api/v1/earn/saving/products', compact('currency'))->getApiData();
+ }
+
+ /**
+ * This endpoint retrieves limited-time promotion products. If no products are available, an empty list is returned.
+ *
+ * @param string $currency
+ * @return mixed|null
+ * @throws \KuCoin\SDK\Exceptions\BusinessException
+ * @throws \KuCoin\SDK\Exceptions\HttpException
+ * @throws \KuCoin\SDK\Exceptions\InvalidApiUriException
+ */
+ public function getPromotionProducts($currency = '')
+ {
+ return $this->call(Request::METHOD_GET, '/api/v1/earn/promotion/products', compact('currency'))->getApiData();
+ }
+
+ /**
+ * This endpoint retrieves KCS Staking products. If no KCS Staking products are available, an empty list is returned.
+ *
+ * @param string $currency
+ * @return mixed|null
+ * @throws \KuCoin\SDK\Exceptions\BusinessException
+ * @throws \KuCoin\SDK\Exceptions\HttpException
+ * @throws \KuCoin\SDK\Exceptions\InvalidApiUriException
+ */
+ public function getStakingProducts($currency = '')
+ {
+ return $this->call(Request::METHOD_GET, '/api/v1/earn/staking/products', compact('currency'))->getApiData();
+ }
+
+ /**
+ * This endpoint retrieves ETH Staking products. If no ETH Staking products are available, an empty list is returned.
+ *
+ * @return mixed|null
+ * @throws \KuCoin\SDK\Exceptions\BusinessException
+ * @throws \KuCoin\SDK\Exceptions\HttpException
+ * @throws \KuCoin\SDK\Exceptions\InvalidApiUriException
+ */
+ public function getEthStakingProducts()
+ {
+ return $this->call(Request::METHOD_GET, '/api/v1/earn/eth-staking/products')->getApiData();
+ }
+
+ /**
+ * This endpoint retrieves KCS Staking products. If no KCS Staking products are available, an empty list is returned.
+ *
+ * @return mixed|null
+ * @throws \KuCoin\SDK\Exceptions\BusinessException
+ * @throws \KuCoin\SDK\Exceptions\HttpException
+ * @throws \KuCoin\SDK\Exceptions\InvalidApiUriException
+ */
+ public function getKcsStakingProducts()
+ {
+ return $this->call(Request::METHOD_GET, '/api/v1/earn/kcs-staking/products')->getApiData();
+ }
+
+ /**
+ * This endpoint allows subscribing to fixed income products. If the subscription fails, it returns the corresponding error code.
+ *
+ * @param string $productId
+ * @param string $amount
+ * @param string $accountType
+ * @return mixed|null
+ * @throws \KuCoin\SDK\Exceptions\BusinessException
+ * @throws \KuCoin\SDK\Exceptions\HttpException
+ * @throws \KuCoin\SDK\Exceptions\InvalidApiUriException
+ */
+ public function subscribe($productId, $amount, $accountType = AccountType::MAIN)
+ {
+ $parameters = compact('productId', 'amount', 'accountType');
+ return $this->call(Request::METHOD_POST, '/api/v1/earn/orders', $parameters)->getApiData();
+ }
+
+ /**
+ * This endpoint allows initiating redemption by holding ID. If the current holding is fully redeemed or in the process of being redeemed, it indicates that the holding does not exist.
+ *
+ * @param string $orderId
+ * @param string $amount
+ * @param string $fromAccountType
+ * @param string $confirmPunishRedeem
+ * @return mixed|null
+ * @throws \KuCoin\SDK\Exceptions\BusinessException
+ * @throws \KuCoin\SDK\Exceptions\HttpException
+ * @throws \KuCoin\SDK\Exceptions\InvalidApiUriException
+ */
+ public function redeem($orderId, $amount, $fromAccountType = AccountType::MAIN, $confirmPunishRedeem = 0)
+ {
+ $parameters = compact('orderId', 'amount', 'fromAccountType', 'confirmPunishRedeem');
+ return $this->call(Request::METHOD_DELETE, '/api/v1/earn/orders', $parameters)->getApiData();
+ }
+
+ /**
+ * This endpoint retrieves redemption preview information by holding ID. If the current holding is fully redeemed or in the process of being redeemed, it indicates that the holding does not exist.
+ *
+ * @param $orderId
+ * @param $fromAccountType
+ * @return mixed|null
+ * @throws \KuCoin\SDK\Exceptions\BusinessException
+ * @throws \KuCoin\SDK\Exceptions\HttpException
+ * @throws \KuCoin\SDK\Exceptions\InvalidApiUriException
+ */
+ public function redeemPreview($orderId, $fromAccountType = AccountType::MAIN)
+ {
+ $parameters = compact('orderId', 'fromAccountType');
+ return $this->call(Request::METHOD_GET, '/api/v1/earn/redeem-preview', $parameters)->getApiData();
+ }
+
+ /**
+ * This endpoint retrieves current holding assets of fixed income products. If no current holding assets are available, an empty list is returned.
+ *
+ * @param array $params
+ * @param array $pagination
+ * @return \KuCoin\SDK\Http\ApiResponse
+ * @throws \KuCoin\SDK\Exceptions\HttpException
+ * @throws \KuCoin\SDK\Exceptions\InvalidApiUriException
+ */
+ public function getHoldAssets(array $params = [], array $pagination = [])
+ {
+ return $this->call(Request::METHOD_GET, '/api/v1/earn/hold-assets', array_merge($params, $pagination))->getApiData();
+ }
+}
\ No newline at end of file
diff --git a/tests/PrivateApi/EarnTest.php b/tests/PrivateApi/EarnTest.php
new file mode 100644
index 0000000..a526a46
--- /dev/null
+++ b/tests/PrivateApi/EarnTest.php
@@ -0,0 +1,310 @@
+getSavingProducts();
+ $this->assertInternalType('array', $products);
+ foreach ($products as $product) {
+ $this->assertProduct($product);
+ }
+
+ $currencyProducts = $earn->getSavingProducts('USDT');
+ $this->assertInternalType('array', $currencyProducts);
+ $currency = array_unique(array_column($currencyProducts, 'currency'));
+ $this->assertLessThanOrEqual(1, count($currency));
+ foreach ($currencyProducts as $currencyProduct) {
+ $this->assertProduct($currencyProduct);
+ }
+ }
+
+ /**
+ * @dataProvider apiProvider
+ *
+ * @param Earn $earn
+ * @return void
+ * @throws \KuCoin\SDK\Exceptions\BusinessException
+ * @throws \KuCoin\SDK\Exceptions\HttpException
+ * @throws \KuCoin\SDK\Exceptions\InvalidApiUriException
+ */
+ public function testGetPromotionProducts(Earn $earn)
+ {
+ $products = $earn->getPromotionProducts();
+ $this->assertInternalType('array', $products);
+ foreach ($products as $product) {
+ $this->assertProduct($product);
+ }
+
+ $currencyProducts = $earn->getPromotionProducts('USDT');
+ $this->assertInternalType('array', $currencyProducts);
+ $currency = array_unique(array_column($currencyProducts, 'currency'));
+ $this->assertLessThanOrEqual(1, count($currency));
+ foreach ($currencyProducts as $currencyProduct) {
+ $this->assertProduct($currencyProduct);
+ }
+ }
+
+ /**
+ * @dataProvider apiProvider
+ *
+ * @param Earn $earn
+ * @return void
+ * @throws \KuCoin\SDK\Exceptions\BusinessException
+ * @throws \KuCoin\SDK\Exceptions\HttpException
+ * @throws \KuCoin\SDK\Exceptions\InvalidApiUriException
+ */
+ public function testGetStakingProducts(Earn $earn)
+ {
+ $products = $earn->getStakingProducts();
+ $this->assertInternalType('array', $products);
+ foreach ($products as $product) {
+ $this->assertProduct($product);
+ }
+
+ $currencyProducts = $earn->getStakingProducts('DOT');
+ $this->assertInternalType('array', $currencyProducts);
+ $currency = array_unique(array_column($currencyProducts, 'currency'));
+ $this->assertLessThanOrEqual(1, count($currency));
+ foreach ($currencyProducts as $currencyProduct) {
+ $this->assertProduct($currencyProduct);
+ }
+ }
+
+ /**
+ * @dataProvider apiProvider
+ *
+ * @param Earn $earn
+ * @return void
+ * @throws \KuCoin\SDK\Exceptions\BusinessException
+ * @throws \KuCoin\SDK\Exceptions\HttpException
+ * @throws \KuCoin\SDK\Exceptions\InvalidApiUriException
+ */
+ public function testGetEthStakingProducts(Earn $earn)
+ {
+ $products = $earn->getEthStakingProducts();
+ $this->assertInternalType('array', $products);
+ $currency = array_unique(array_column($products, 'currency'));
+ $this->assertLessThanOrEqual(1, count($currency));
+ foreach ($products as $product) {
+ $this->assertProduct($product);
+ }
+ }
+
+ /**
+ * @dataProvider apiProvider
+ *
+ * @param Earn $earn
+ * @return void
+ * @throws \KuCoin\SDK\Exceptions\BusinessException
+ * @throws \KuCoin\SDK\Exceptions\HttpException
+ * @throws \KuCoin\SDK\Exceptions\InvalidApiUriException
+ */
+ public function testGetKcsStakingProducts(Earn $earn)
+ {
+ $products = $earn->getKcsStakingProducts();
+ $this->assertInternalType('array', $products);
+ $currency = array_unique(array_column($products, 'currency'));
+ $this->assertLessThanOrEqual(1, count($currency));
+ foreach ($products as $product) {
+ $this->assertProduct($product);
+ }
+ }
+
+ /**
+ * @dataProvider apiProvider
+ *
+ * @param Earn $earn
+ * @return void
+ * @throws \KuCoin\SDK\Exceptions\BusinessException
+ * @throws \KuCoin\SDK\Exceptions\HttpException
+ * @throws \KuCoin\SDK\Exceptions\InvalidApiUriException
+ */
+ public function testSubscribe(Earn $earn)
+ {
+ $subscribeOptions = $this->savingProductsSubscribeOption($earn);
+ $order = $earn->subscribe($subscribeOptions['productId'], $subscribeOptions['amount']);
+ $this->assertInternalType('array', $order);
+ $this->assertArrayHasKey('orderId', $order);
+ $this->assertArrayHasKey('orderTxId', $order);
+ }
+
+ /**
+ * @dataProvider apiProvider
+ *
+ * @param Earn $earn
+ * @return void
+ * @throws \KuCoin\SDK\Exceptions\HttpException
+ * @throws \KuCoin\SDK\Exceptions\InvalidApiUriException
+ */
+ public function testGetHoldAssets(Earn $earn)
+ {
+ $holdAssets = $earn->getHoldAssets([], ['pageSize' => 10]);
+ $assertCallback = function ($item) {
+ $this->assertArrayHasKey('orderId', $item);
+ $this->assertArrayHasKey('productId', $item);
+ $this->assertArrayHasKey('productCategory', $item);
+ $this->assertArrayHasKey('productType', $item);
+ $this->assertArrayHasKey('currency', $item);
+ $this->assertArrayHasKey('incomeCurrency', $item);
+ $this->assertArrayHasKey('returnRate', $item);
+ $this->assertArrayHasKey('holdAmount', $item);
+ $this->assertArrayHasKey('redeemedAmount', $item);
+ $this->assertArrayHasKey('redeemingAmount', $item);
+ $this->assertArrayHasKey('lockStartTime', $item);
+ $this->assertArrayHasKey('lockEndTime', $item);
+ $this->assertArrayHasKey('purchaseTime', $item);
+ $this->assertArrayHasKey('redeemPeriod', $item);
+ $this->assertArrayHasKey('status', $item);
+ $this->assertArrayHasKey('earlyRedeemSupported', $item);
+ };
+
+ $this->assertPagination($holdAssets);
+ foreach ($holdAssets['items'] as $item) {
+ $assertCallback($item);
+ }
+
+ $filterHoldAssets = $earn->getHoldAssets(['currency' => 'USDT']);
+ $this->assertPagination($filterHoldAssets);
+ foreach ($filterHoldAssets['items'] as $item) {
+ $assertCallback($item);
+ }
+
+ $currency = array_unique(array_column($filterHoldAssets['items'], 'currency'));
+ $this->assertLessThanOrEqual(1, count($currency));
+ }
+
+ /**
+ * @dataProvider apiProvider
+ *
+ * @param Earn $earn
+ * @return void
+ * @throws \KuCoin\SDK\Exceptions\BusinessException
+ * @throws \KuCoin\SDK\Exceptions\HttpException
+ * @throws \KuCoin\SDK\Exceptions\InvalidApiUriException
+ */
+ public function testRedeemPreview(Earn $earn)
+ {
+ $subscribeOptions = $this->timeProductsSubscribeOption($earn);
+ $order = $earn->subscribe($subscribeOptions['productId'], $subscribeOptions['amount']);
+ $redeemPreview = $earn->redeemPreview($order['orderId']);
+ $this->assertInternalType('array', $redeemPreview);
+ $this->assertArrayHasKey('currency', $redeemPreview);
+ $this->assertArrayHasKey('redeemAmount', $redeemPreview);
+ $this->assertArrayHasKey('penaltyInterestAmount', $redeemPreview);
+ $this->assertArrayHasKey('redeemPeriod', $redeemPreview);
+ $this->assertArrayHasKey('deliverTime', $redeemPreview);
+ $this->assertArrayHasKey('manualRedeemable', $redeemPreview);
+ $this->assertArrayHasKey('redeemAll', $redeemPreview);
+ }
+
+ /**
+ * @dataProvider apiProvider
+ *
+ * @param Earn $earn
+ * @return void
+ * @throws \KuCoin\SDK\Exceptions\BusinessException
+ * @throws \KuCoin\SDK\Exceptions\HttpException
+ * @throws \KuCoin\SDK\Exceptions\InvalidApiUriException
+ */
+ public function testRedeem(Earn $earn)
+ {
+ $subscribeOptions = $this->savingProductsSubscribeOption($earn);
+ $order = $earn->subscribe($subscribeOptions['productId'], $subscribeOptions['amount']);
+ $redeem = $earn->redeem($order['orderId'], $subscribeOptions['amount']);
+ $assertCallback = function ($redeem) {
+ $this->assertInternalType('array', $redeem);
+ $this->assertArrayHasKey('orderTxId', $redeem);
+ $this->assertArrayHasKey('deliverTime', $redeem);
+ $this->assertArrayHasKey('status', $redeem);
+ $this->assertArrayHasKey('amount', $redeem);
+ };
+
+ $assertCallback($redeem);
+ $timeSubscribeOptions = $this->timeProductsSubscribeOption($earn);
+ $timeOrder = $earn->subscribe($timeSubscribeOptions['productId'], $timeSubscribeOptions['amount']);
+ $earlyRedeem = $earn->redeem($timeOrder['orderId'], $timeSubscribeOptions['amount'], AccountType::MAIN, 1);
+ $assertCallback($earlyRedeem);
+ }
+
+ protected function savingProductsSubscribeOption(Earn $earn, $currency = 'USDT')
+ {
+ $products = $earn->getSavingProducts($currency);
+ if (empty($products)) {
+ return [];
+ }
+
+ $product = $products[0];
+ return [
+ 'productId' => $product['id'],
+ 'amount' => $product['userLowerLimit'],
+ ];
+ }
+
+
+ protected function timeProductsSubscribeOption(Earn $earn)
+ {
+ $products = $earn->getPromotionProducts();
+ $products = array_filter($products, static function ($item) {
+ return !empty($item['earlyRedeemSupported']);
+ });
+
+ $products = array_column($products, null, 'currency');
+ $currency = ['USDT', 'MATIC', 'DOT', 'NEAR', 'ATOM', 'ADA'];
+ foreach ($currency as $item) {
+ if (isset($products[$item])) {
+ return [
+ 'productId' => $products[$item]['id'],
+ 'amount' => $products[$item]['userLowerLimit'],
+ ];
+ }
+ }
+ }
+
+ /**
+ * @param array $product
+ * @return void
+ */
+ protected function assertProduct(array $product)
+ {
+ $this->assertArrayHasKey('id', $product);
+ $this->assertArrayHasKey('currency', $product);
+ $this->assertArrayHasKey('category', $product);
+ $this->assertArrayHasKey('type', $product);
+ $this->assertArrayHasKey('precision', $product);
+ $this->assertArrayHasKey('productUpperLimit', $product);
+ $this->assertArrayHasKey('userUpperLimit', $product);
+ $this->assertArrayHasKey('userLowerLimit', $product);
+ $this->assertArrayHasKey('redeemPeriod', $product);
+ $this->assertArrayHasKey('lockStartTime', $product);
+ $this->assertArrayHasKey('lockEndTime', $product);
+ $this->assertArrayHasKey('applyStartTime', $product);
+ $this->assertArrayHasKey('applyEndTime', $product);
+ $this->assertArrayHasKey('returnRate', $product);
+ $this->assertArrayHasKey('incomeCurrency', $product);
+ $this->assertArrayHasKey('earlyRedeemSupported', $product);
+ $this->assertArrayHasKey('productRemainAmount', $product);
+ $this->assertArrayHasKey('status', $product);
+ $this->assertArrayHasKey('redeemType', $product);
+ $this->assertArrayHasKey('incomeReleaseType', $product);
+ $this->assertArrayHasKey('interestDate', $product);
+ $this->assertArrayHasKey('duration', $product);
+ $this->assertArrayHasKey('newUserOnly', $product);
+ }
+}