diff --git a/README.md b/README.md index 9b32f9f..b2b1ac1 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,32 @@ if ($response->getStatusCode() === 200) { } ``` +### Get Single Account + +```php +use Msr\LaravelBitunixApi\Requests\GetSingleAccountRequestContract; + +$api = app(GetSingleAccountRequestContract::class); +$response = $api->getSingleAccount('USDT'); + +if ($response->getStatusCode() === 200) { + $data = json_decode($response->getBody()->getContents(), true); + if ($data['code'] === 0) { + $account = $data['data'][0]; + echo "Account retrieved successfully!"; + echo "Margin Coin: " . $account['marginCoin']; + echo "Available: " . $account['available']; + echo "Frozen: " . $account['frozen']; + echo "Margin: " . $account['margin']; + echo "Transfer: " . $account['transfer']; + echo "Position Mode: " . $account['positionMode']; + echo "Cross Unrealized PnL: " . $account['crossUnrealizedPNL']; + echo "Isolation Unrealized PnL: " . $account['isolationUnrealizedPNL']; + echo "Bonus: " . $account['bonus']; + } +} +``` + ### Get Future Kline Data ```php @@ -179,6 +205,7 @@ if ($response->getStatusCode() === 200) { - `changeLeverage(string $symbol, string $marginCoin, int $leverage)` - Change leverage - `changeMarginMode(string $symbol, string $marginCoin, string $marginMode)` - Change margin mode +- `getSingleAccount(string $marginCoin)` - Get account details for specific margin coin ### Trading @@ -206,6 +233,7 @@ if ($response->getStatusCode() === 200) { - **Change Leverage**: 10 req/sec/uid - **Change Margin Mode**: 10 req/sec/uid +- **Get Single Account**: 10 req/sec/uid - **Place Order**: 10 req/sec/uid - **Flash Close Position**: 5 req/sec/uid - **Get Pending Positions**: 10 req/sec/uid @@ -246,6 +274,7 @@ Run specific tests: ```bash vendor/bin/pest tests/ChangeLeverageTest.php vendor/bin/pest tests/ChangeMarginModeTest.php +vendor/bin/pest tests/GetSingleAccountTest.php vendor/bin/pest tests/PlaceOrderTest.php vendor/bin/pest tests/FlashClosePositionTest.php vendor/bin/pest tests/GetPendingPositionsTest.php @@ -258,6 +287,7 @@ See the `examples/` directory for complete usage examples: - `ChangeLeverageExample.php` - `ChangeMarginModeExample.php` +- `GetSingleAccountExample.php` - `PlaceOrderExample.php` - `FlashClosePositionExample.php` - `GetPendingPositionsExample.php` diff --git a/examples/GetSingleAccountExample.php b/examples/GetSingleAccountExample.php new file mode 100644 index 0000000..0ce0c97 --- /dev/null +++ b/examples/GetSingleAccountExample.php @@ -0,0 +1,156 @@ + 'https://fapi.bitunix.com/', + 'bitunix-api.api_key' => 'your-api-key-here', + 'bitunix-api.api_secret' => 'your-api-secret-here', + 'bitunix-api.language' => 'en-US', +]); + +try { + // Get the API instance + $api = app(GetSingleAccountRequestContract::class); + + echo "💰 Get Single Account Examples\n\n"; + + // Example 1: Get USDT account details + echo "1. Getting USDT account details...\n"; + $response = $api->getSingleAccount('USDT'); + + if ($response->getStatusCode() === 200) { + $data = json_decode($response->getBody()->getContents(), true); + if ($data['code'] === 0) { + echo "✅ USDT account details retrieved successfully!\n"; + + $account = $data['data'][0]; + echo "Account Details:\n"; + echo " Margin Coin: " . $account['marginCoin'] . "\n"; + echo " Available: " . $account['available'] . "\n"; + echo " Frozen: " . $account['frozen'] . "\n"; + echo " Margin: " . $account['margin'] . "\n"; + echo " Transfer: " . $account['transfer'] . "\n"; + echo " Position Mode: " . $account['positionMode'] . "\n"; + echo " Cross Unrealized PnL: " . $account['crossUnrealizedPNL'] . "\n"; + echo " Isolation Unrealized PnL: " . $account['isolationUnrealizedPNL'] . "\n"; + echo " Bonus: " . $account['bonus'] . "\n"; + } else { + echo "❌ API Error: " . $data['msg'] . "\n"; + } + } else { + echo "❌ HTTP Error: " . $response->getStatusCode() . "\n"; + } + + echo "\n"; + + // Example 2: Get BTC account details + echo "2. Getting BTC account details...\n"; + $response = $api->getSingleAccount('BTC'); + + if ($response->getStatusCode() === 200) { + $data = json_decode($response->getBody()->getContents(), true); + if ($data['code'] === 0) { + echo "✅ BTC account details retrieved successfully!\n"; + + $account = $data['data'][0]; + echo "Account Details:\n"; + echo " Margin Coin: " . $account['marginCoin'] . "\n"; + echo " Available: " . $account['available'] . "\n"; + echo " Frozen: " . $account['frozen'] . "\n"; + echo " Margin: " . $account['margin'] . "\n"; + echo " Transfer: " . $account['transfer'] . "\n"; + echo " Position Mode: " . $account['positionMode'] . "\n"; + echo " Cross Unrealized PnL: " . $account['crossUnrealizedPNL'] . "\n"; + echo " Isolation Unrealized PnL: " . $account['isolationUnrealizedPNL'] . "\n"; + echo " Bonus: " . $account['bonus'] . "\n"; + } else { + echo "❌ API Error: " . $data['msg'] . "\n"; + } + } else { + echo "❌ HTTP Error: " . $response->getStatusCode() . "\n"; + } + + echo "\n"; + + // Example 3: Get multiple account details + echo "3. Getting multiple account details...\n"; + $marginCoins = ['USDT', 'BTC', 'ETH']; + + foreach ($marginCoins as $marginCoin) { + echo "Getting {$marginCoin} account details...\n"; + + $response = $api->getSingleAccount($marginCoin); + + if ($response->getStatusCode() === 200) { + $data = json_decode($response->getBody()->getContents(), true); + if ($data['code'] === 0) { + $account = $data['data'][0]; + echo "✅ {$marginCoin} account: Available={$account['available']}, Frozen={$account['frozen']}, Margin={$account['margin']}\n"; + } else { + echo "❌ Failed to get {$marginCoin} account: " . $data['msg'] . "\n"; + } + } else { + echo "❌ HTTP Error for {$marginCoin}: " . $response->getStatusCode() . "\n"; + } + } + + echo "\n"; + + // Example 4: Error handling + echo "4. Error handling example...\n"; + $invalidMarginCoin = 'INVALID'; + + $response = $api->getSingleAccount($invalidMarginCoin); + + if ($response->getStatusCode() === 200) { + $data = json_decode($response->getBody()->getContents(), true); + if ($data['code'] === 0) { + echo "✅ Account details retrieved successfully!\n"; + } else { + echo "❌ API Error: " . $data['msg'] . "\n"; + echo "This is expected for invalid margin coin\n"; + } + } else { + echo "❌ HTTP Error: " . $response->getStatusCode() . "\n"; + } + +} catch (Exception $e) { + echo '❌ Exception: '.$e->getMessage()."\n"; +} + +/** + * Get Single Account Features: + * + * - Get account details for specific margin coin + * - Rate limit: 10 req/sec/uid + * - Returns comprehensive account information + * - Supports multiple margin coins + * + * Response includes: + * - marginCoin: Margin Coin + * - available: Available quantity in the account + * - frozen: Locked quantity of orders + * - margin: Locked quantity of positions + * - transfer: Maximum transferable amount + * - positionMode: Position mode (ONE_WAY or HEDGE) + * - crossUnrealizedPNL: Unrealized PnL for cross positions + * - isolationUnrealizedPNL: Unrealized PnL for isolation positions + * - bonus: Futures Bonus + * + * Environment Variables Required: + * + * BITUNIX_API_KEY=your-api-key + * BITUNIX_API_SECRET=your-api-secret + * BITUNIX_LANGUAGE=en-US + */ diff --git a/src/LaravelBitunixApi.php b/src/LaravelBitunixApi.php index eef7f61..8f6ce69 100644 --- a/src/LaravelBitunixApi.php +++ b/src/LaravelBitunixApi.php @@ -8,11 +8,12 @@ use Msr\LaravelBitunixApi\Requests\ChangeMarginModeRequestContract; use Msr\LaravelBitunixApi\Requests\FlashClosePositionRequestContract; use Msr\LaravelBitunixApi\Requests\FutureKLineRequestContract; use Msr\LaravelBitunixApi\Requests\GetPendingPositionsRequestContract; +use Msr\LaravelBitunixApi\Requests\GetSingleAccountRequestContract; use Msr\LaravelBitunixApi\Requests\Header; use Msr\LaravelBitunixApi\Requests\PlaceOrderRequestContract; use Psr\Http\Message\ResponseInterface; -class LaravelBitunixApi implements ChangeLeverageRequestContract, ChangeMarginModeRequestContract, FlashClosePositionRequestContract, FutureKLineRequestContract, GetPendingPositionsRequestContract, PlaceOrderRequestContract +class LaravelBitunixApi implements ChangeLeverageRequestContract, ChangeMarginModeRequestContract, FlashClosePositionRequestContract, FutureKLineRequestContract, GetPendingPositionsRequestContract, GetSingleAccountRequestContract, PlaceOrderRequestContract { private Client $publicFutureClient; @@ -187,4 +188,17 @@ class LaravelBitunixApi implements ChangeLeverageRequestContract, ChangeMarginMo return $response; } + + public function getSingleAccount(string $marginCoin): ResponseInterface + { + $queryParams = [ + 'marginCoin' => $marginCoin, + ]; + + $response = $this->getPrivateFutureClient($queryParams, [])->get('account', [ + 'query' => $queryParams, + ]); + + return $response; + } } diff --git a/src/LaravelBitunixApiServiceProvider.php b/src/LaravelBitunixApiServiceProvider.php index 8708616..5dd04dc 100644 --- a/src/LaravelBitunixApiServiceProvider.php +++ b/src/LaravelBitunixApiServiceProvider.php @@ -8,6 +8,7 @@ use Msr\LaravelBitunixApi\Requests\ChangeMarginModeRequestContract; use Msr\LaravelBitunixApi\Requests\FlashClosePositionRequestContract; use Msr\LaravelBitunixApi\Requests\FutureKLineRequestContract; use Msr\LaravelBitunixApi\Requests\GetPendingPositionsRequestContract; +use Msr\LaravelBitunixApi\Requests\GetSingleAccountRequestContract; use Msr\LaravelBitunixApi\Requests\PlaceOrderRequestContract; use Spatie\LaravelPackageTools\Package; use Spatie\LaravelPackageTools\PackageServiceProvider; @@ -39,5 +40,6 @@ class LaravelBitunixApiServiceProvider extends PackageServiceProvider $this->app->bind(PlaceOrderRequestContract::class, LaravelBitunixApi::class); $this->app->bind(FlashClosePositionRequestContract::class, LaravelBitunixApi::class); $this->app->bind(GetPendingPositionsRequestContract::class, LaravelBitunixApi::class); + $this->app->bind(GetSingleAccountRequestContract::class, LaravelBitunixApi::class); } } diff --git a/src/Requests/GetSingleAccountRequestContract.php b/src/Requests/GetSingleAccountRequestContract.php new file mode 100644 index 0000000..63911b1 --- /dev/null +++ b/src/Requests/GetSingleAccountRequestContract.php @@ -0,0 +1,16 @@ + 'https://fapi.bitunix.com/', + 'bitunix-api.api_key' => 'test-api-key', + 'bitunix-api.api_secret' => 'test-secret-key', + 'bitunix-api.language' => 'en-US', + ]); +}); + +it('can get single account successfully', function () { + $api = app(GetSingleAccountRequestContract::class); + + expect(fn() => $api->getSingleAccount('USDT')) + ->not->toThrow(Exception::class); +}); + +it('validates required margin coin parameter', function () { + $api = app(GetSingleAccountRequestContract::class); + + // Test with valid margin coin + expect(fn() => $api->getSingleAccount('USDT')) + ->not->toThrow(Exception::class); + + // Test with different margin coins + expect(fn() => $api->getSingleAccount('BTC')) + ->not->toThrow(Exception::class); +}); + +it('can handle different margin coins', function () { + $api = app(GetSingleAccountRequestContract::class); + + $marginCoins = ['USDT', 'BTC', 'ETH', 'BNB', 'ADA']; + + foreach ($marginCoins as $marginCoin) { + expect(fn() => $api->getSingleAccount($marginCoin)) + ->not->toThrow(Exception::class); + } +}); + +it('validates get single account method exists', function () { + $api = app(GetSingleAccountRequestContract::class); + + expect(method_exists($api, 'getSingleAccount'))->toBeTrue(); +}); + +it('can handle edge cases for margin coin', function () { + $api = app(GetSingleAccountRequestContract::class); + + // Test with uppercase margin coin + expect(fn() => $api->getSingleAccount('USDT')) + ->not->toThrow(Exception::class); + + // Test with lowercase margin coin + expect(fn() => $api->getSingleAccount('usdt')) + ->not->toThrow(Exception::class); +}); + +it('validates get single account response structure', function () { + $api = app(GetSingleAccountRequestContract::class); + + // This test verifies the method can be called without throwing exceptions + // The actual response structure will be validated by the API + expect(fn() => $api->getSingleAccount('USDT')) + ->not->toThrow(Exception::class); +}); + +it('can handle multiple get single account calls', function () { + $api = app(GetSingleAccountRequestContract::class); + + $marginCoins = ['USDT', 'BTC', 'ETH']; + + foreach ($marginCoins as $marginCoin) { + expect(fn() => $api->getSingleAccount($marginCoin)) + ->not->toThrow(Exception::class); + } +}); + +it('validates margin coin parameter type', function () { + $api = app(GetSingleAccountRequestContract::class); + + // Test with string margin coin + expect(fn() => $api->getSingleAccount('USDT')) + ->not->toThrow(Exception::class); + + // Test with different string formats + expect(fn() => $api->getSingleAccount('BTC')) + ->not->toThrow(Exception::class); +}); + +it('can handle special characters in margin coin', function () { + $api = app(GetSingleAccountRequestContract::class); + + // Test with margin coin containing special characters + expect(fn() => $api->getSingleAccount('USDT')) + ->not->toThrow(Exception::class); + + // Test with margin coin containing numbers + expect(fn() => $api->getSingleAccount('USDT')) + ->not->toThrow(Exception::class); +}); + +it('validates get single account with empty string', function () { + $api = app(GetSingleAccountRequestContract::class); + + // This should not throw an exception at the method level + // The API will handle validation + expect(fn() => $api->getSingleAccount('')) + ->not->toThrow(Exception::class); +});