Retry backoff: 10s, 30s, 60s */ private const RETRY_BACKOFF_MS = [10_000, 30_000, 60_000]; public function __construct( private string $apiKey, ) {} /** * List all crypto wallets with balances. * * @return array{data: array} */ public function getCryptoWallets(): array { return $this->get('/wallets'); } /** * List all fiat wallets with balances. * * @return array{data: array} */ public function getFiatWallets(): array { return $this->get('/fiatwallets'); } /** * List all asset wallets (crypto + commodity) grouped by type. * * @return array */ public function getAssetWallets(): array { return $this->get('/asset-wallets'); } /** * Get Bitpanda's own ticker prices for all assets. * Returns a map of asset symbol to fiat prices (e.g., BTC => {EUR => "56911.68", USD => "67150.45"}). * This is a public endpoint — no authentication required. * * @return array> */ public function getTickerPrices(): array { return $this->get('/ticker'); } /** * List trades with optional cursor-based pagination. * * @return array{data: array, meta: array, links: array} */ public function getTrades(?string $cursor = null, int $pageSize = 25): array { $params = ['page_size' => $pageSize]; if ($cursor) { $params['cursor'] = $cursor; } return $this->get('/trades', $params); } /** * Fetch all trades by paginating through the cursor-based API. * Trades are returned newest-first from the API but this method * reverses them to chronological order (oldest first). * * @param string|null $sinceDate Optional ISO date string (YYYY-MM-DD) to stop fetching older trades * @return array */ public function getAllTrades(?string $sinceDate = null): array { $allTrades = []; $cursor = null; $sinceTimestamp = $sinceDate ? strtotime($sinceDate) : null; do { $response = $this->getTrades($cursor, 100); $trades = $response['data'] ?? []; if (empty($trades)) { break; } foreach ($trades as $trade) { $tradeTimestamp = (int) ($trade['attributes']['time']['unix'] ?? 0); if ($sinceTimestamp && $tradeTimestamp < $sinceTimestamp) { return array_reverse($allTrades); } $allTrades[] = $trade; } $cursor = $response['meta']['next_cursor'] ?? null; } while ($cursor); return array_reverse($allTrades); } /** * Execute an authenticated GET request with retry on rate limiting. */ private function get(string $path, array $params = []): array { return retry( self::RETRY_BACKOFF_MS, function () use ($path, $params) { $response = $this->client()->get($path, $params); $response->throw(); return $response->json(); }, when: fn (Exception $e) => $e instanceof RequestException && $e->response->status() === 429, ); } private function client(): PendingRequest { return Http::baseUrl(self::BASE_URL) ->withHeaders(['X-Api-Key' => $this->apiKey]) ->acceptJson() ->throw(function ($response, $exception) { Log::error('Bitpanda API error', [ 'status' => $response->status(), 'body' => $response->json(), ]); }); } }