A production-grade PHP SDK for the Bitget Unified Trading Account (UTA) API v3, with seamless Laravel 10-13 integration. Phase 1 covers Market, Account, and Trade REST services plus a reconnecting WebSocket client with a pluggable transport.
Package: a matching Go SDK is available at tigusigalpa/bitget-go.
- PHP 8.2+,
declare(strict_types=1)everywhere, readonly constructor properties - Strings for every price/quantity/PnL/fee field — no float rounding errors
- Guzzle-based HTTP transport with an injectable
GuzzleHttp\Clientfor tests/proxies - PSR-3 logging (defaults to a no-op
NullLogger); credentials are never logged - A typed exception hierarchy (
AuthenticationException,RateLimitException,InvalidParameterException,InsufficientFundsException,OrderNotFoundException) carrying Bitget's raw error code - A
WebsocketClientwith pluggable transport (ConnectionInterface) — ships with a synchronoustextalk/websocketadapter; implement the interface yourself to run under ReactPHP, Amp, or Laravel Octane's event loop - Laravel service provider, publishable config, and
Bitgetfacade — auto-discovered, but the SDK has no hardilluminate/*dependency outside Laravel apps
composer require tigusigalpa/bitget-phpThe service provider and Bitget facade are auto-discovered. Publish the config file:
php artisan vendor:publish --tag=bitget-configBITGET_API_KEY=your-api-key
BITGET_SECRET_KEY=your-secret-key
BITGET_PASSPHRASE=your-passphrase
BITGET_DEMO=falseuse Tigusigalpa\Bitget\Facades\Bitget;
$tickers = Bitget::market()->getTickers('SPOT', 'BTCUSDT');use Tigusigalpa\Bitget\Client;
$client = new Client(
apiKey: getenv('BITGET_API_KEY'),
secretKey: getenv('BITGET_SECRET_KEY'),
passphrase: getenv('BITGET_PASSPHRASE'),
);
$tickers = $client->market()->getTickers('SPOT', 'BTCUSDT');config/bitget.php key |
Env var | Default |
|---|---|---|
api_key |
BITGET_API_KEY |
'' |
secret_key |
BITGET_SECRET_KEY |
'' |
passphrase |
BITGET_PASSPHRASE |
'' |
demo |
BITGET_DEMO |
false |
base_url |
BITGET_BASE_URL |
https://api.bitget.com |
locale |
BITGET_LOCALE |
en-US |
| Category | Methods | Docs |
|---|---|---|
| Market (public) | getInstruments, getTickers, getOrderBook |
Instruments · Tickers · OrderBook |
| Account (private) | getAssets, getSettings, setLeverage |
Get-Account · Get-Account-Setting · Change-Leverage |
| Trade (private) | placeOrder, modifyOrder, cancelOrder, getOpenOrders, getOrderHistory, getPositions |
Place-Order · Modify-Order · Cancel-Order · Get-Order-Pending · Get-Order-History · Get-Position |
Full mapping with HTTP methods and paths: docs/endpoints.md.
use Tigusigalpa\Bitget\WebsocketClient;
$ws = new WebsocketClient(WebsocketClient::DEFAULT_PUBLIC_URL);
$ws->connect();
$ws->subscribe(['instType' => 'SPOT', 'topic' => 'ticker', 'symbol' => 'BTCUSDT']);
$ws->listen(function (array $push) {
echo json_encode($push), PHP_EOL;
});For private channels (e.g. order fills), pass credentials to the constructor — connect() authenticates automatically:
$ws = new WebsocketClient(
url: WebsocketClient::DEFAULT_PRIVATE_URL,
apiKey: config('bitget.api_key'),
secretKey: config('bitget.secret_key'),
passphrase: config('bitget.passphrase'),
);
$ws->connect();
$ws->subscribe(['instType' => 'UTA', 'topic' => 'fast-fill', 'symbol' => 'default']);
$ws->listen(fn (array $push) => /* handle fill */ null);listen() blocks the current process, answers Bitget's text-frame ping/pong heartbeat, and — on an unexpected disconnect — reconnects with exponential backoff (1s → 60s cap) and resubscribes every previously active channel. The default transport (TextalkConnection) is synchronous; to run under a non-blocking event loop (ReactPHP, Amp, Laravel Octane), implement Tigusigalpa\Bitget\WebSocket\ConnectionInterface and pass it as WebsocketClient's $connection constructor argument.
Implemented private channel: fast-fill. Other channels work through the same subscribe()/listen() API; see docs/endpoints.md.
Set demo: true (or BITGET_DEMO=true) together with a Demo API key from the Bitget console to send paptrading: 1 on every REST request, or use WebsocketClient::DEMO_PUBLIC_URL/DEMO_PRIVATE_URL for WebSocket. Always validate new code against demo credentials before pointing it at a live account:
$client = new Client(
apiKey: config('bitget.api_key'),
secretKey: config('bitget.secret_key'),
passphrase: config('bitget.passphrase'),
demoTrading: true,
);
// Guard order placement behind an explicit opt-in — never wire this to
// production credentials without removing the gate deliberately.
if (getenv('BITGET_ENABLE_TRADING') === '1') {
$client->trade()->placeOrder([
'category' => 'SPOT',
'symbol' => 'BTCUSDT',
'side' => 'buy',
'orderType' => 'limit',
'price' => '10000', // deliberately far below market so it won't fill
'qty' => '0.001',
]);
}use Tigusigalpa\Bitget\Exceptions\AuthenticationException;
use Tigusigalpa\Bitget\Exceptions\InsufficientFundsException;
use Tigusigalpa\Bitget\Exceptions\RateLimitException;
use Tigusigalpa\Bitget\Exceptions\BitgetException;
try {
$client->trade()->placeOrder([...]);
} catch (AuthenticationException $e) {
// invalid API key/secret/passphrase
} catch (InsufficientFundsException $e) {
// not enough balance/margin
} catch (RateLimitException $e) {
// back off and retry
} catch (BitgetException $e) {
// anything else; $e->bitgetCode / $e->rawResponse are available
}composer install
vendor/bin/phpunitUnit tests run fully offline against a mocked Guzzle transport (MockHandler) — no network access or credentials required.
- Fork and branch off
main. - Add/update tests for any behavior change (
vendor/bin/phpunitmust pass). - Every public method must include a
Docs:line in its docblock linking to the exact Bitget API documentation page it implements. - Update docs/endpoints.md for new endpoints.
Found a vulnerability? Email sovletig@gmail.com directly — please don't open a public issue.
MIT. See LICENSE.
Igor Sazonov — @tigusigalpa — sovletig@gmail.com
Not affiliated with Bitget. Test on demo before going live.
